diff --git a/.env b/.env index 9c4c6d0..0c25042 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ MODEL_PATH=./model -MODEL_NAME=Qwen2.5-7B-Instruct-Q8.gguf \ No newline at end of file +MODEL_NAME=Qwen2.5-7B-Instruct-Q8.gguf +CONTEXT_SIZE=4096 \ No newline at end of file diff --git a/NemesisAI.sln b/NemesisAI.sln index f1c54ff..b46483d 100644 --- a/NemesisAI.sln +++ b/NemesisAI.sln @@ -5,7 +5,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0C9A5F05-7CED-450F-9757-C641D75F8787}" ProjectSection(SolutionItems) = preProject compose.yaml = compose.yaml - prompt\nemesis.txt = prompt\nemesis.txt EndProjectSection EndProject Global diff --git a/TelegramBot/Agent.cs b/TelegramBot/Agent.cs index 3b68012..2d770ec 100644 --- a/TelegramBot/Agent.cs +++ b/TelegramBot/Agent.cs @@ -2,10 +2,12 @@ namespace TelegramBot; -public class Agent(Actor actor, long telegramId, string name, string username, TelegramBotClient bot) { +public class Agent(Actor actor, long telegramId, string name, string username, TelegramBotClient bot, string systemPrompt, int tokenLenght) { public Actor Actor { get; } = actor; public long TelegramId { get; } = telegramId; public string Username { get; } = username; public string Name { get; } = name; public TelegramBotClient Bot { get; } = bot; + public string SystemPrompt { get; } = systemPrompt; + public int SystemPromptLength { get; } = tokenLenght; } \ No newline at end of file diff --git a/TelegramBot/OpenAIAgent.cs b/TelegramBot/OpenAIAgent.cs index c7ce149..50d9664 100644 --- a/TelegramBot/OpenAIAgent.cs +++ b/TelegramBot/OpenAIAgent.cs @@ -6,14 +6,16 @@ using OpenAI.Chat; namespace TelegramBot; -public class OpenAiAgent -{ +public class OpenAiAgent { private ApiKeyCredential apikey; private OpenAIClient oaiClient; private ChatClient chatClient; private HttpClient httpClient; + private Dictionary<(long, Actor), List<(ChatMessage, int)>> oaiChats = new(); + public int ContextSize { get; set; } = 4096; + public OpenAiAgent(string baseUrl, string apiKey, string model) { OpenAIClientOptions options = new OpenAIClientOptions() { Endpoint = new(baseUrl), @@ -79,8 +81,29 @@ public class OpenAiAgent return chat!; } - public string GetChatResponse(long chatId, Actor actor) { - return String.Empty; + public string GetChatResponse(long chatId, Agent agent) { + int currentContextSize = agent.SystemPromptLength; + List chatHistory = new(); + chatHistory.Add(new SystemChatMessage(agent.SystemPrompt)); + + //Fetch the chat history from the dictionary trimming to the context size + var history = GetChatHistory(chatId, agent.Actor).ToList(); + history.Reverse(); + //Add the chat history to the list until the context size is reached + foreach (var (message, tokenLenght) in history) { + if (currentContextSize + tokenLenght > ContextSize) break; + chatHistory.Add(message); + currentContextSize += tokenLenght; + } + //Reverse the chat history to get the correct order + chatHistory.Reverse(1, chatHistory.Count - 1); + + var completion = chatClient.CompleteChat(chatHistory).Value.Content[0].Text; + + //Add the response to the chat history + ChatHistoryAppend(agent.Actor, chatId, completion); + + return completion; } public void AddChatToDictionary(long id) { diff --git a/TelegramBot/Program.cs b/TelegramBot/Program.cs index 749a65f..cc9fbab 100644 --- a/TelegramBot/Program.cs +++ b/TelegramBot/Program.cs @@ -1,13 +1,15 @@ using OpenAI.Chat; using Telegram.Bot; using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums;using TelegramBot; +using Telegram.Bot.Types.Enums; +using TelegramBot; using File = System.IO.File; -string baseUrl = Env.Get("OPENAI_BASE_URL"); -string apiKey = Env.Get("OPENAI_API_KEY"); -string model = Env.Get("OPENAI_MODEL"); -var oaiAgent = new OpenAiAgent(baseUrl, apiKey, model); +string baseUrl = Env.Get("OPENAI_BASE_URL"); +string apiKey = Env.Get("OPENAI_API_KEY"); +string model = Env.Get("OPENAI_MODEL"); +var oaiAgent = new OpenAiAgent(baseUrl, apiKey, model); +oaiAgent.ContextSize = Int32.Parse(Env.Get("CONTEXT_SIZE")); Console.WriteLine("Starting the bot..."); @@ -43,7 +45,9 @@ Agent Nemesis = new( nemProfile.Result.Id, nemProfile.Result.FirstName, nemProfile.Result.Username!, - nemesisBot + nemesisBot, + nemesisPrompt, + oaiAgent.GetTokenLenght(nemesisPrompt) ); var kroProfile = krolikBot.GetMe(); @@ -52,7 +56,9 @@ Agent Krolik = new( kroProfile.Result.Id, kroProfile.Result.FirstName, kroProfile.Result.Username!, - krolikBot + krolikBot, + krolikPrompt, + oaiAgent.GetTokenLenght(krolikPrompt) ); nemesisBot.OnMessage += OnNemMessage; @@ -72,14 +78,14 @@ krocts.Cancel(); // stop krobot async Task OnNemMessage(Message msg, UpdateType type) { //Discard any message that is not a text message if (msg.Type != MessageType.Text) return; - + await OnMessage(msg, Nemesis); } async Task OnKroMessage(Message msg, UpdateType type) { //Discard any message that is not a text message if (msg.Type != MessageType.Text) return; - + await OnMessage(msg, Krolik); } @@ -87,45 +93,46 @@ async Task OnKroMessage(Message msg, UpdateType type) { // We should also take in account the last x messages in groups to add more context async Task OnMessage(Message msg, Agent agent) { //Check if the message contains the bot's username or a reply to a message sent by the bot or a private chat - - var chatid = msg.Chat.Id; - var tokenlenght = oaiAgent.GetTokenLenght(msg.Text); - Console.WriteLine( - $""" - {agent.Name} has received message from {chatid} TokenLenght: {tokenlenght} - Message: {msg.Text} - """); - //Add the message to the chat history - oaiAgent.ChatHistoryAppend(Actor.User, chatid, "User: "+msg.Text); - //Check if the message is a reset command - if (msg.Text == "/reset" || msg.Text == "/reset@"+agent.Username) { - oaiAgent.ResetChat(chatid); - await agent.Bot.SendMessage(chatid, "Chat context has been reset"); - return; - } - - // Otherwise process it normally + var chatid = msg.Chat.Id; + var tokenlenght = oaiAgent.GetTokenLenght(msg.Text); + Console.WriteLine( + $""" + {agent.Name} has received message from {chatid} TokenLenght: {tokenlenght} + Message: {msg.Text} + """); + //Add the message to the chat history + oaiAgent.ChatHistoryAppend(Actor.User, chatid, "User: " + msg.Text); + + //Check if the message is a reset command + if (msg.Text == "/reset" || msg.Text == "/reset@" + agent.Username) { + oaiAgent.ResetChat(chatid); + await agent.Bot.SendMessage(chatid, "Chat context has been reset"); + return; + } + + // Otherwise process it normally if (msg.Text!.Contains(agent.Name, StringComparison.OrdinalIgnoreCase) || - msg.ReplyToMessage?.From?.Id == agent.TelegramId || msg.Chat.Type == ChatType.Private) { - + msg.ReplyToMessage?.From?.Id == agent.TelegramId || + msg.Chat.Type == ChatType.Private) { //Check if the chat (group) is rate limited - if (IsRateLimited(chatid)) { + /*if (IsRateLimited(chatid)) { Console.WriteLine("No response due to ratelimit."); return; } - await AnswerChat(chatid, msg.Text, tokenlenght, agent.Actor); + */ + await AnswerChat(chatid, agent); } } -async Task AnswerChat(long chatId, string input, int tokenLenght, Actor actor) { - //Limit the message to 1024 characters to avoid out of context jump - string text = input; - if (input.Length > 1024) text = input.Substring(0, 1024); - +async Task AnswerChat(long chatId, Agent agent) { //Get the response from the OpenAI API - var result = oaiAgent.GetChatResponse(chatId, actor); + var result = oaiAgent.GetChatResponse(chatId, agent); + Console.WriteLine( + $""" + {agent.Name} has responded with: {result} + """); //Send the response to the user - await krolikBot.SendMessage(chatId, result); + await agent.Bot.SendMessage(chatId, result); } \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index cb75225..1fa7c90 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,6 +6,7 @@ context: . dockerfile: TelegramBot/Dockerfile env_file: + - .env - TelegramBot/.env llm-server: @@ -15,7 +16,7 @@ - ${MODEL_PATH}:/models ports: - "80:80" - command: -m /models/${MODEL_NAME} --port 80 --host 0.0.0.0 -n 128 -c 4096 --no-mmap -ngl 50 -fa -np 4 + command: -m /models/${MODEL_NAME} --port 80 --host 0.0.0.0 -n 128 -c ${CONTEXT_SIZE} --no-mmap -ngl 50 -fa -np 4 deploy: resources: reservations: