140 lines
4.7 KiB
C#
140 lines
4.7 KiB
C#
using OpenAI;
|
|
using OpenAI.Chat;
|
|
using System.ClientModel;
|
|
using Telegram.Bot;
|
|
using Telegram.Bot.Types;
|
|
using Telegram.Bot.Types.Enums;
|
|
using File = System.IO.File;
|
|
|
|
string GetEnv(string name) => Environment.GetEnvironmentVariable(name) ?? throw new Exception($"Environment variable {name} is not set");
|
|
|
|
string baseUrl = GetEnv("OPENAI_BASE_URL");
|
|
string model = GetEnv("OPENAI_MODEL");
|
|
string apiKey = GetEnv("OPENAI_API_KEY");
|
|
Console.WriteLine("Starting the bot...");
|
|
Console.WriteLine(
|
|
$"""
|
|
Base URL: {baseUrl}
|
|
Model: {model}
|
|
API Key: {apiKey}
|
|
""");
|
|
|
|
string nemesisPrompt = File.ReadAllText($"prompt/{GetEnv("NEMESIS_PROMPT_FILE")}");
|
|
string krolikPrompt = File.ReadAllText($"prompt/{GetEnv("KROLIK_PROMPT_FILE")}");
|
|
|
|
Dictionary<long, List<ChatMessage>> oaiChats = new();
|
|
|
|
//Ratelimit
|
|
TimeSpan rateLimit = new(0, 0, 0, 30);
|
|
Dictionary<long, DateTime> lastMessage = new();
|
|
HashSet<long> unlimitedChats = new();
|
|
|
|
bool IsRateLimited(long chatId) {
|
|
if (lastMessage.ContainsKey(chatId) && DateTime.Now - lastMessage[chatId] < rateLimit) return true;
|
|
|
|
lastMessage[chatId] = DateTime.Now;
|
|
return false;
|
|
}
|
|
|
|
|
|
var options = new OpenAIClientOptions() {
|
|
Endpoint = new(baseUrl),
|
|
NetworkTimeout = new TimeSpan(0, 0, 15)
|
|
};
|
|
|
|
var openAiApiKey = new ApiKeyCredential(apiKey);
|
|
var openAiClient = new OpenAIClient(openAiApiKey, options);
|
|
var chatClient = openAiClient.GetChatClient(model);
|
|
|
|
string token = GetEnv("TELEGRAM_BOT_TOKEN") ?? string.Empty;
|
|
Console.WriteLine("OpenAI Chat Client created");
|
|
|
|
using var cts = new CancellationTokenSource();
|
|
var bot = new TelegramBotClient(token, cancellationToken:cts.Token);
|
|
await bot.DropPendingUpdates();
|
|
var me = bot.GetMe();
|
|
bot.OnMessage += OnMessage;
|
|
Console.WriteLine("Bot running");
|
|
Thread.Sleep(Timeout.Infinite);
|
|
cts.Cancel(); // stop the bot
|
|
|
|
async Task OnMessage(Message msg, UpdateType type)
|
|
{
|
|
//Discard any message that is not a text message
|
|
if (msg.Type != MessageType.Text) return;
|
|
//Check if the message contains the bot's username or a reply to a message sent by the bot or a private chat
|
|
if (msg.Text!.Contains(me.Result.FirstName!, StringComparison.OrdinalIgnoreCase) ||
|
|
msg.ReplyToMessage != null && msg.ReplyToMessage.From!.Id == me.Result.Id ||
|
|
msg.Chat.Type == ChatType.Private) {
|
|
var chatid = msg.Chat.Id;
|
|
Console.WriteLine(
|
|
$"""
|
|
Received message from {chatid} Type: {type}
|
|
Message: {msg.Text}
|
|
""");
|
|
//Check if the chat (group) is rate limited
|
|
if (IsRateLimited(chatid)) {
|
|
Console.WriteLine("No response due to ratelimit.");
|
|
return;
|
|
}
|
|
|
|
//Check if the message is a reset command
|
|
if (msg.Text.StartsWith("/reset")) {
|
|
ResetChat(chatid);
|
|
await bot.SendMessage(chatid, "Chat context has been reset");
|
|
return;
|
|
}
|
|
// Otherwise process it normally
|
|
await AnswerChat(chatid, msg.Text);
|
|
}
|
|
}
|
|
|
|
async Task AnswerChat(long chatId, string input) {
|
|
//Check if the chat is already in the dictionary
|
|
if (!oaiChats.ContainsKey(chatId))
|
|
AddChatToDictionary(chatId);
|
|
|
|
//Limit the message to 1024 characters to avoid out of context jump
|
|
string text = input;
|
|
if (input.Length > 1024) text = input.Substring(0, 1024);
|
|
|
|
//Add the current message to the chat
|
|
ChatMessageRotate(chatId, new UserChatMessage(text));
|
|
|
|
//fetch existing messages history, append hint of the speaker to the message:
|
|
var messages = oaiChats[chatId];
|
|
messages.Add(new AssistantChatMessage("Nemesis: "));
|
|
|
|
//Fetch the response from the model
|
|
var result = chatClient.CompleteChat(messages).Value.Content[0].Text;
|
|
|
|
//Add the response to the chat
|
|
Console.WriteLine("Replying with: " + result);
|
|
oaiChats[chatId].Add(new AssistantChatMessage(result));
|
|
|
|
//Send the response to the user
|
|
await bot.SendMessage(chatId, result);
|
|
}
|
|
|
|
void AddChatToDictionary(long id) {
|
|
//Create a new chat object
|
|
var chat = new List<ChatMessage>();
|
|
chat.Add(new SystemChatMessage(nemesisPrompt));
|
|
//add the entry to the dictionary
|
|
oaiChats.Add(id, chat);
|
|
}
|
|
|
|
void ChatMessageRotate(long chatId, ChatMessage message){
|
|
//Remove the first message from the chat if the chat has more than 5 couples of messages (0 is our prompt)
|
|
if (oaiChats[chatId].Count > 10) oaiChats[chatId].RemoveRange(1, 2);
|
|
|
|
//Add the new message to the chat
|
|
oaiChats[chatId].Add(message);
|
|
}
|
|
|
|
void ResetChat(long chatId) {
|
|
//Remove the chat from the dictionary
|
|
oaiChats.Remove(chatId);
|
|
//Add the chat back to the dictionary
|
|
AddChatToDictionary(chatId);
|
|
} |