Skip to content

Commit

Permalink
AI memory
Browse files Browse the repository at this point in the history
  • Loading branch information
AngryJKirk committed Nov 13, 2024
1 parent 3c28a8b commit 8521e4a
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 7 deletions.
3 changes: 2 additions & 1 deletion scripts/db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ VALUES (1, '/stats_month'),
(33, '/enable_gpt4'),
(34, '/debug_gpt'),
(35, '/story'),
(36, '/privacy')
(36, '/privacy'),
(37, '/memory')

;
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ enum class Command(val command: String, val id: Int) {
ENABLE_GPT4("/enable_gpt4", 33),
DEBUG_GPT("/debug_gpt", 34),
STORY("/story", 35),
PRIVACY("/privacy", 36);
PRIVACY("/privacy", 36),
MEMORY("/memory", 37);

companion object {
val LOOKUP = entries.associateBy(Command::command)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.storozhenko.familybot.feature.ai

import dev.storozhenko.familybot.BotConfig
import dev.storozhenko.familybot.common.extensions.send
import dev.storozhenko.familybot.core.executors.Executor
import dev.storozhenko.familybot.core.keyvalue.EasyKeyValueService
import dev.storozhenko.familybot.core.routers.models.ExecutorContext
import dev.storozhenko.familybot.core.routers.models.Priority
import dev.storozhenko.familybot.feature.settings.models.ChatGPTMemory
import org.springframework.stereotype.Component

@Component
class MemoryAddExecutor(
private val easyKeyValueService: EasyKeyValueService,
private val botConfig: BotConfig
) : Executor {


override suspend fun execute(context: ExecutorContext) {
val text = context.message.text
if (text.isNullOrBlank()) {
context.client.send(context, "Пришли нормально")
return
}
if (text.length > 300) {
context.client.send(context, "Говорили же меньше 300 символов. Ты прислал ${text.length}")
return
}

val currentMemory = easyKeyValueService.get(ChatGPTMemory, context.chatKey, "")
easyKeyValueService.put(ChatGPTMemory, context.chatKey, "$currentMemory\n$text")
context.client.send(context, "Готово")
}

override fun canExecute(context: ExecutorContext): Boolean {
val replyToMessage = context.update.message?.replyToMessage
return replyToMessage != null && replyToMessage.from.userName == botConfig.botName
&& replyToMessage.text == "Напиши что добавить в ответ на это сообщение, 300 символов максимум"
}

override fun priority(context: ExecutorContext) = Priority.MEDIUM
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dev.storozhenko.familybot.feature.ai

import dev.storozhenko.familybot.common.extensions.send
import dev.storozhenko.familybot.core.executors.CommandExecutor
import dev.storozhenko.familybot.core.models.telegram.Command
import dev.storozhenko.familybot.core.routers.models.ExecutorContext
import org.springframework.stereotype.Component
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardRow

@Component
class MemoryCommandExecutor : CommandExecutor() {
override fun command() = Command.MEMORY

override suspend fun execute(context: ExecutorContext) {
context.client.send(context, "Какое действие с ИИ памятью вы хотите выполнить?", customization = {
replyMarkup = InlineKeyboardMarkup(
listOf(
InlineKeyboardRow(
listOf(
InlineKeyboardButton("Добавить")
.apply { callbackData = "add" },
InlineKeyboardButton("Показать что есть")
.apply { callbackData = "show" },
InlineKeyboardButton("Стереть все")
.apply { callbackData = "clear" },
)
)
)
)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package dev.storozhenko.familybot.feature.ai

import dev.storozhenko.familybot.BotConfig
import dev.storozhenko.familybot.common.extensions.isFromAdmin
import dev.storozhenko.familybot.common.extensions.send
import dev.storozhenko.familybot.core.executors.ContinuousConversationExecutor
import dev.storozhenko.familybot.core.keyvalue.EasyKeyValueService
import dev.storozhenko.familybot.core.models.telegram.Command
import dev.storozhenko.familybot.core.routers.models.ExecutorContext
import dev.storozhenko.familybot.feature.settings.models.ChatGPTMemory
import org.springframework.stereotype.Component
import org.telegram.telegrambots.meta.api.methods.AnswerCallbackQuery
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage

@Component
class MemoryContinuousExecutor(
private val easyKeyValueService: EasyKeyValueService,
botConfig: BotConfig
) : ContinuousConversationExecutor(botConfig) {
override fun getDialogMessages(context: ExecutorContext): Set<String> {
return setOf("Какое действие с ИИ памятью вы хотите выполнить?")
}

override fun command() = Command.MEMORY

override suspend fun execute(context: ExecutorContext) {
context.client.execute(AnswerCallbackQuery(context.update.callbackQuery.id))
val data = context.update.callbackQuery.data
when (data) {
"add" -> add(context)
"show" -> show(context)
"clear" -> clear(context)
}
context.client.execute(DeleteMessage(context.chat.idString, context.message.messageId))
}

private suspend fun show(context: ExecutorContext) {
val memory = easyKeyValueService.get(ChatGPTMemory, context.chatKey)
if (memory != null) {
context.client.send(context, "Текущая память: $memory")
} else {
context.client.send(context, "Память отсутствует.")
}
}

private suspend fun add(context: ExecutorContext) {
context.client.send(context, "Напиши что добавить в ответ на это сообщение, 300 символов максимум")
}

private suspend fun clear(context: ExecutorContext) {
if (context.client.isFromAdmin(context)) {
val memory = easyKeyValueService.getAndRemove(ChatGPTMemory, context.chatKey)
if (memory != null) {
context.client.send(context, "Память удалена. Вот на память бэкап: $memory")
} else {
context.client.send(context, "Нечего чистить, памяти нет.")
}
} else {
context.client.send(context, "Чистить память можно только админам")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ object ChatGPTSummaryCooldown : BooleanKeyType<ChatEasyKey>
object ChatGPTReactionsCooldown : BooleanKeyType<ChatEasyKey>
object ChatGPT4Enabled : BooleanKeyType<ChatEasyKey>
object ChatGPTTalkingDisabled : BooleanKeyType<ChatEasyKey>
object ChatGPTMemory: StringKeyType<ChatEasyKey>
object IGCookie : StringKeyType<PlainKey>
object PaymentKey : StringKeyType<PlainKey>
object RefundNeedsToPressTime : LongKeyType<PlainKey>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dev.storozhenko.familybot.common.extensions.untilNextMonth
import dev.storozhenko.familybot.core.keyvalue.EasyKeyValueService
import dev.storozhenko.familybot.core.routers.models.ExecutorContext
import dev.storozhenko.familybot.core.telegram.FamilyBot
import dev.storozhenko.familybot.feature.settings.models.ChatGPTMemory
import dev.storozhenko.familybot.feature.settings.models.ChatGPTStyle
import dev.storozhenko.familybot.feature.settings.models.ChatGPTTokenUsageByChat
import org.springframework.stereotype.Component
Expand All @@ -32,7 +33,8 @@ class TalkingServiceChatGpt(
) : TalkingService {
companion object {
private const val SIZE_LIMITER =
"В ответах говори исключительно в мужском роде. Отвечай максимум двумя предложениями. Не используй markdown или html."
"В ответах говори исключительно в мужском роде. Отвечай максимум двумя предложениями. Не используй markdown или html. " +
"В начале каждого сообщения от пользователя идет его имя. В своих сообщениях так делать не надо."
private val codeMarkupPattern = Regex("`{1,3}([^`]+)`{1,3}")
}

Expand All @@ -53,11 +55,11 @@ class TalkingServiceChatGpt(

val style = getStyle(context)
val chatMessages = getPastMessages(style, context)
val systemMessage = getSystemMessage(style)
val systemMessage = getSystemMessage(style, context)
if (text == "/debug") {
return chatMessages.plus(systemMessage).joinToString("\n", transform = ChatMessage::toString)
}
chatMessages.add(ChatMessage(Role.User, content = text))
chatMessages.add(ChatMessage(Role.User, content = "${context.user.getGeneralName(false)} says: $text"))
chatMessages.add(0, systemMessage)
val request = createRequest(chatMessages, useGpt4 = false)
val response = getOpenAIService().chatCompletion(request)
Expand Down Expand Up @@ -101,12 +103,14 @@ class TalkingServiceChatGpt(
}

private fun getSystemMessage(
style: GptStyle
style: GptStyle,
context: ExecutorContext
): ChatMessage {
return ChatMessage(
Role.System, content = listOf(
gptSettingsReader.getUniverseValue(style.universe).trimIndent(),
gptSettingsReader.getStyleValue(style),
getMemory(context),
SIZE_LIMITER
).joinToString("\n")
)
Expand Down Expand Up @@ -140,13 +144,23 @@ class TalkingServiceChatGpt(
?: GptStyle.RUDE
}

private fun getMemory(context: ExecutorContext): String {
val memoryValue = easyKeyValueService.get(ChatGPTMemory, context.chatKey) ?: return ""

return "У тебя есть память о пользователях чата, применяй ее. Вот она: \n|НАЧАЛО ПАМЯТИ|\n$memoryValue\n|КОНЕЦ ПАМЯТИ|\n"
}

private var openAI: OpenAI? = null

private fun getOpenAIService(): OpenAI {
if (openAI == null) {
val token = botConfig.openAiToken
?: throw FamilyBot.InternalException("Open AI token is not available, check config")
openAI = OpenAI(token = token, timeout = Timeout(socket = 60.seconds), logging = LoggingConfig(logLevel = LogLevel.None))
openAI = OpenAI(
token = token,
timeout = Timeout(socket = 60.seconds),
logging = LoggingConfig(logLevel = LogLevel.None)
)
}
return openAI as OpenAI
}
Expand Down

0 comments on commit 8521e4a

Please sign in to comment.