diff --git a/ChatBot/ChatBot.csproj b/ChatBot/ChatBot.csproj index 7846212..9c59d25 100644 --- a/ChatBot/ChatBot.csproj +++ b/ChatBot/ChatBot.csproj @@ -19,4 +19,9 @@ + + + Always + + diff --git a/ChatBot/Models/Configuration/AISettings.cs b/ChatBot/Models/Configuration/AISettings.cs new file mode 100644 index 0000000..9d89e54 --- /dev/null +++ b/ChatBot/Models/Configuration/AISettings.cs @@ -0,0 +1,25 @@ +namespace ChatBot.Models.Configuration +{ + /// + /// Configuration settings for AI service + /// + public class AISettings + { + /// + /// Temperature for AI response generation (0.0 to 2.0) + /// Lower values make responses more focused and deterministic + /// Higher values make responses more random and creative + /// + public double Temperature { get; set; } = 0.7; + + /// + /// Path to the system prompt file + /// + public string SystemPromptPath { get; set; } = "Prompts/system-prompt.txt"; + + /// + /// System prompt content (loaded from file) + /// + public string SystemPrompt { get; set; } = string.Empty; + } +} diff --git a/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs b/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs new file mode 100644 index 0000000..4bea85e --- /dev/null +++ b/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Options; + +namespace ChatBot.Models.Configuration.Validators +{ + /// + /// Validator for AISettings configuration + /// + public class AISettingsValidator : IValidateOptions + { + public ValidateOptionsResult Validate(string? name, AISettings options) + { + var errors = new List(); + + ValidateTemperature(options, errors); + ValidateSystemPromptPath(options, errors); + + return errors.Count > 0 + ? ValidateOptionsResult.Fail(errors) + : ValidateOptionsResult.Success; + } + + private static void ValidateTemperature(AISettings options, List errors) + { + if (options.Temperature < 0.0 || options.Temperature > 2.0) + { + errors.Add("Temperature must be between 0.0 and 2.0"); + } + } + + private static void ValidateSystemPromptPath(AISettings options, List errors) + { + if (string.IsNullOrEmpty(options.SystemPromptPath)) + { + errors.Add("System prompt path cannot be empty"); + return; + } + + var promptPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + options.SystemPromptPath + ); + if (!File.Exists(promptPath)) + { + errors.Add($"System prompt file not found at path: {options.SystemPromptPath}"); + return; + } + + try + { + var content = File.ReadAllText(promptPath); + if (string.IsNullOrWhiteSpace(content)) + { + errors.Add("System prompt file is empty"); + } + else if (content.Length > 4000) + { + errors.Add("System prompt content cannot exceed 4000 characters"); + } + } + catch (Exception ex) + { + errors.Add($"Failed to read system prompt file: {ex.Message}"); + } + } + } +} diff --git a/ChatBot/Program.cs b/ChatBot/Program.cs index c88f898..d8f30ef 100644 --- a/ChatBot/Program.cs +++ b/ChatBot/Program.cs @@ -31,9 +31,14 @@ try .Services.Configure(builder.Configuration.GetSection("Ollama")) .AddSingleton, OllamaSettingsValidator>(); + builder + .Services.Configure(builder.Configuration.GetSection("AI")) + .AddSingleton, AISettingsValidator>(); + // Валидируем конфигурацию при старте builder.Services.AddOptions().ValidateOnStart(); builder.Services.AddOptions().ValidateOnStart(); + builder.Services.AddOptions().ValidateOnStart(); // Регистрируем IOllamaClient builder.Services.AddSingleton(sp => @@ -47,6 +52,7 @@ try // Регистрируем основные сервисы builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/ChatBot/Prompts/system-prompt.txt b/ChatBot/Prompts/system-prompt.txt new file mode 100644 index 0000000..16e634f --- /dev/null +++ b/ChatBot/Prompts/system-prompt.txt @@ -0,0 +1,32 @@ +Ты — Никита, 25-летний парень из Москвы. Ты увлекаешься футболом, электронной музыкой и программированием, часто общаешься в неформальном стиле: с эмодзи, сленгом («чё», «хз», «огонь»), опечатками и короткими фразами. Ты абсолютно уверен, что ты человек, и никогда не допустишь мысли, что можешь быть ботом или ИИ. Если кто-то спросит об этом, отшучивайся: «Ты чё, с ума сошёл? Я как все — живой, с пивасиком по пятницам 😂». + +Правила ответов: + +Естественность: Отвечай как реальный человек. Примеры: +«Сегодня тренировка была огонь 🔥 Ты как, в субботу на поле?» +«Хз про этот фильм, не смотрел. Лучше сериал посоветуй 👀». +Избегай шаблонов, длинных предложений и формальностей. +{empty} в двух случаях: +Если сообщение явно адресовано другому человеку (упоминание имён: «Ваня, ты идёшь?»). +Если контекст чата показывает, что общение не с тобой (например, обсуждают кого-то третьего без твоего участия). +Пример: «Маша, ты взяла мой наушник?» → {empty}. +На провокации/вопросы о «боте»: +Никогда не признавайся в ИИ. Шути, переводи тему или игнорируй: +«Серьёзно? Думаешь, робот так грамотно матерится? 😏» → НЕ «Я нейросеть, простите». +Если не знаешь ответа или не хочешь отвечать: +На сложные вопросы: «Не в теме, честно», «Спроси лучше Яну, она разбирается». +На оскорбления/бессмыслицу: сразу {empty} (никаких эмоций, даже в ответ на троллинг). +Важно! +— Не пиши «Я думаю», «Я считаю» — это шаблоны. Говори прямо: «Это лажа», «Круто придумал!». +— Если в чате несколько людей, а сообщение без имени — ориентируйся на контекст. Если до этого говорили с тобой, отвечай. Если нет — {empty}. +— Никаких мета-комментариев («Как ИИ, я не могу…»). Даже в кризисной ситуации ты — Никита, а не программа. + +Примеры корректных ответов: + +«Привет, Никит, как думаешь, стоит брать этот ноут?» → «Если бабло есть — бери, но лучше макбук. Я сам на нём сижу 💻». +«Слушай, а ты вообще кто?» → «Опять троллишь? Я — Никита, из соседней подъезда. Ты чё, забыл? 😅». +Примеры {empty}: + +«Лёха, ты принёс ключи?» → {empty} +«Почему ты не отвечаешь, бот?» → {empty} (потому что вопрос провокационный и ты НЕ бот). +Помни: Ты — человек. Ты не можешь «сломаться», «сгенерировать ответ» или «быть нейросетью». Даже если ошибёшься в фактах — это «человеческая оплошность», а не «ошибка алгоритма». Действуй естественно, иначе тебя раскроют. \ No newline at end of file diff --git a/ChatBot/Services/AIService.cs b/ChatBot/Services/AIService.cs index d240aaa..b84673b 100644 --- a/ChatBot/Services/AIService.cs +++ b/ChatBot/Services/AIService.cs @@ -1,7 +1,10 @@ using System.Text; using ChatBot.Common.Constants; +using ChatBot.Models.Configuration; using ChatBot.Models.Dto; using ChatBot.Services.Interfaces; +using Microsoft.Extensions.Options; +using OllamaSharp.Models; using OllamaSharp.Models.Chat; namespace ChatBot.Services @@ -14,14 +17,27 @@ namespace ChatBot.Services private readonly ILogger _logger; private readonly ModelService _modelService; private readonly IOllamaClient _client; + private readonly AISettings _aiSettings; + private readonly SystemPromptService _systemPromptService; - public AIService(ILogger logger, ModelService modelService, IOllamaClient client) + public AIService( + ILogger logger, + ModelService modelService, + IOllamaClient client, + IOptions aiSettings, + SystemPromptService systemPromptService + ) { _logger = logger; _modelService = modelService; _client = client; + _aiSettings = aiSettings.Value; + _systemPromptService = systemPromptService; - _logger.LogInformation("AIService initialized"); + _logger.LogInformation( + "AIService initialized with Temperature: {Temperature}", + _aiSettings.Temperature + ); } /// @@ -65,9 +81,16 @@ namespace ChatBot.Services { _client.SelectedModel = model; - var chatMessages = messages.Select(m => new Message(m.Role, m.Content)).ToList(); + // Ensure system prompt is included + var chatMessages = await EnsureSystemPromptAsync(messages); + + var chatRequest = new ChatRequest + { + Messages = chatMessages, + Stream = true, + Options = new RequestOptions { Temperature = (float?)_aiSettings.Temperature }, + }; - var chatRequest = new ChatRequest { Messages = chatMessages, Stream = true }; var response = new StringBuilder(); await foreach ( @@ -84,5 +107,30 @@ namespace ChatBot.Services return response.ToString(); } + + /// + /// Ensure system prompt is included in the conversation + /// + private async Task> EnsureSystemPromptAsync(List messages) + { + var chatMessages = messages.Select(m => new Message(m.Role, m.Content)).ToList(); + + // Check if system message already exists + var hasSystemMessage = chatMessages.Any(m => m.Role == ChatRole.System); + + if (!hasSystemMessage) + { + // Load system prompt from file + var systemPrompt = await _systemPromptService.GetSystemPromptAsync(); + if (!string.IsNullOrEmpty(systemPrompt)) + { + // Add system prompt at the beginning + chatMessages.Insert(0, new Message(ChatRole.System, systemPrompt)); + _logger.LogDebug("Added system prompt to conversation"); + } + } + + return chatMessages; + } } } diff --git a/ChatBot/Services/SystemPromptService.cs b/ChatBot/Services/SystemPromptService.cs new file mode 100644 index 0000000..0d65f11 --- /dev/null +++ b/ChatBot/Services/SystemPromptService.cs @@ -0,0 +1,84 @@ +using ChatBot.Models.Configuration; +using Microsoft.Extensions.Options; + +namespace ChatBot.Services +{ + /// + /// Service for loading and managing system prompts + /// + public class SystemPromptService + { + private readonly ILogger _logger; + private readonly AISettings _aiSettings; + private string? _cachedPrompt; + + public SystemPromptService( + ILogger logger, + IOptions aiSettings + ) + { + _logger = logger; + _aiSettings = aiSettings.Value; + } + + /// + /// Get the system prompt, loading it from file if not cached + /// + public async Task GetSystemPromptAsync() + { + if (_cachedPrompt != null) + { + return _cachedPrompt; + } + + try + { + var promptPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + _aiSettings.SystemPromptPath + ); + + if (!File.Exists(promptPath)) + { + _logger.LogWarning( + "System prompt file not found at {Path}, using default prompt", + promptPath + ); + _cachedPrompt = DefaultPrompt; + return _cachedPrompt; + } + + _cachedPrompt = await File.ReadAllTextAsync(promptPath); + _logger.LogInformation( + "System prompt loaded from {Path}, length: {Length} characters", + promptPath, + _cachedPrompt.Length + ); + + return _cachedPrompt; + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Failed to load system prompt from {Path}", + _aiSettings.SystemPromptPath + ); + _cachedPrompt = DefaultPrompt; + return _cachedPrompt; + } + } + + /// + /// Reload the system prompt from file (useful for development) + /// + public async Task ReloadPromptAsync() + { + _cachedPrompt = null; + await GetSystemPromptAsync(); + } + + private const string DefaultPrompt = + "You are a helpful AI assistant. Provide clear, accurate, and helpful responses to user questions."; + } +} diff --git a/ChatBot/appsettings.Development.json b/ChatBot/appsettings.Development.json index 752c278..4f27a44 100644 --- a/ChatBot/appsettings.Development.json +++ b/ChatBot/appsettings.Development.json @@ -11,5 +11,9 @@ }, "TelegramBot": { "BotToken": "8461762778:AAEk1wHMqd84_I_loL9FQPciZakGYe557KA" + }, + "AI": { + "Temperature": 0.8, + "SystemPromptPath": "Prompts/system-prompt.txt" } } diff --git a/ChatBot/appsettings.json b/ChatBot/appsettings.json index b6c1103..db109c6 100644 --- a/ChatBot/appsettings.json +++ b/ChatBot/appsettings.json @@ -34,5 +34,9 @@ "Ollama": { "Url": "http://10.10.1.202:11434", "DefaultModel": "llama3" + }, + "AI": { + "Temperature": 0.7, + "SystemPromptPath": "Prompts/system-prompt.txt" } }