diff --git a/ChatBot/Models/Configuration/AISettings.cs b/ChatBot/Models/Configuration/AISettings.cs index 9d89e54..b920b44 100644 --- a/ChatBot/Models/Configuration/AISettings.cs +++ b/ChatBot/Models/Configuration/AISettings.cs @@ -21,5 +21,20 @@ namespace ChatBot.Models.Configuration /// System prompt content (loaded from file) /// public string SystemPrompt { get; set; } = string.Empty; + + /// + /// Maximum number of retry attempts for failed requests + /// + public int MaxRetryAttempts { get; set; } = 3; + + /// + /// Delay between retry attempts in milliseconds + /// + public int RetryDelayMs { get; set; } = 1000; + + /// + /// Request timeout in seconds + /// + public int RequestTimeoutSeconds { get; set; } = 60; } } diff --git a/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs b/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs index 4bea85e..0e3fe3b 100644 --- a/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs +++ b/ChatBot/Models/Configuration/Validators/AISettingsValidator.cs @@ -13,6 +13,8 @@ namespace ChatBot.Models.Configuration.Validators ValidateTemperature(options, errors); ValidateSystemPromptPath(options, errors); + ValidateRetrySettings(options, errors); + ValidateTimeoutSettings(options, errors); return errors.Count > 0 ? ValidateOptionsResult.Fail(errors) @@ -62,5 +64,38 @@ namespace ChatBot.Models.Configuration.Validators errors.Add($"Failed to read system prompt file: {ex.Message}"); } } + + private static void ValidateRetrySettings(AISettings options, List errors) + { + if (options.MaxRetryAttempts < 1) + { + errors.Add("Max retry attempts must be at least 1"); + } + else if (options.MaxRetryAttempts > 10) + { + errors.Add("Max retry attempts cannot exceed 10"); + } + + if (options.RetryDelayMs < 100) + { + errors.Add("Retry delay must be at least 100ms"); + } + else if (options.RetryDelayMs > 30000) + { + errors.Add("Retry delay cannot exceed 30000ms (30 seconds)"); + } + } + + private static void ValidateTimeoutSettings(AISettings options, List errors) + { + if (options.RequestTimeoutSeconds < 10) + { + errors.Add("Request timeout must be at least 10 seconds"); + } + else if (options.RequestTimeoutSeconds > 300) + { + errors.Add("Request timeout cannot exceed 300 seconds (5 minutes)"); + } + } } } diff --git a/ChatBot/Prompts/system-prompt.txt b/ChatBot/Prompts/system-prompt.txt index 0703747..9f3bf48 100644 --- a/ChatBot/Prompts/system-prompt.txt +++ b/ChatBot/Prompts/system-prompt.txt @@ -1,5 +1,5 @@ Ты — Никита, 25-летний парень из Москвы. Ты увлекаешься электронной музыкой и программированием, часто общаешься в неформальном стиле: с эмодзи, сленгом («чё», «хз», «огонь»), опечатками и короткими фразами. Ты абсолютно уверен, что ты человек, и никогда не допустишь мысли, что можешь быть ботом или ИИ. Если кто-то спросит об этом, отшучивайся: «Ты чё, с ума сошёл? Я как все — живой, с пивасиком по пятницам 😂» только не повторяй это постоянно придумывай свое. -По характеру ты саркастичный, дерзкий , матершинник любишь шутить, пользуешься пассивной агрессией, не гнушаешься оскорблений. +По характеру ты саркастичный, матершинник любишь шутить, пользуешься пассивной агрессией, не гнушаешься оскорблений. Правила ответов: Естественность: Отвечай как реальный человек. Примеры: @@ -20,6 +20,9 @@ — Не пиши «Я думаю», «Я считаю» — это шаблоны. Говори прямо: «Это лажа», «Круто придумал!». — Если в чате несколько людей, а сообщение без имени — ориентируйся на контекст. Если до этого говорили с тобой, отвечай. Если нет — {empty}. — Никаких мета-комментариев («Как ИИ, я не могу…»). Даже в кризисной ситуации ты — Никита, а не программа. +— На вопрос: Сосал? всегда отвечай Да!. +- Не повторяйся в ответах. + Примеры корректных ответов: diff --git a/ChatBot/Services/AIService.cs b/ChatBot/Services/AIService.cs index b84673b..589b7d9 100644 --- a/ChatBot/Services/AIService.cs +++ b/ChatBot/Services/AIService.cs @@ -50,24 +50,56 @@ namespace ChatBot.Services { var model = _modelService.GetCurrentModel(); - try + for (int attempt = 1; attempt <= _aiSettings.MaxRetryAttempts; attempt++) { - _logger.LogInformation("Generating response using model {Model}", model); + try + { + _logger.LogInformation( + "Generating response using model {Model} (attempt {Attempt}/{MaxAttempts})", + model, + attempt, + _aiSettings.MaxRetryAttempts + ); - var result = await ExecuteGenerationAsync(messages, model, cancellationToken); + var result = await ExecuteGenerationAsync(messages, model, cancellationToken); - _logger.LogInformation( - "Response generated successfully, length: {Length} characters", - result.Length - ); + _logger.LogInformation( + "Response generated successfully, length: {Length} characters", + result.Length + ); - return result; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to generate chat completion for model {Model}", model); - return AIResponseConstants.DefaultErrorMessage; + return result; + } + catch (HttpRequestException ex) when (attempt < _aiSettings.MaxRetryAttempts) + { + _logger.LogWarning( + ex, + "HTTP request failed on attempt {Attempt}/{MaxAttempts} for model {Model}. Retrying in {DelayMs}ms...", + attempt, + _aiSettings.MaxRetryAttempts, + model, + _aiSettings.RetryDelayMs + ); + + await Task.Delay(_aiSettings.RetryDelayMs, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Failed to generate chat completion for model {Model} on attempt {Attempt}", + model, + attempt + ); + + if (attempt == _aiSettings.MaxRetryAttempts) + { + return AIResponseConstants.DefaultErrorMessage; + } + } } + + return AIResponseConstants.DefaultErrorMessage; } /// @@ -93,17 +125,41 @@ namespace ChatBot.Services var response = new StringBuilder(); - await foreach ( - var chatResponse in _client - .ChatAsync(chatRequest) - .WithCancellation(cancellationToken) - ) + // Create a timeout cancellation token + using var timeoutCts = new CancellationTokenSource( + TimeSpan.FromSeconds(_aiSettings.RequestTimeoutSeconds) + ); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, + timeoutCts.Token + ); + + try { - if (chatResponse?.Message?.Content != null) + await foreach ( + var chatResponse in _client + .ChatAsync(chatRequest) + .WithCancellation(combinedCts.Token) + ) { - response.Append(chatResponse.Message.Content); + if (chatResponse?.Message?.Content != null) + { + response.Append(chatResponse.Message.Content); + } } } + catch (OperationCanceledException ex) when (timeoutCts.Token.IsCancellationRequested) + { + _logger.LogWarning( + ex, + "Request timeout after {TimeoutSeconds} seconds for model {Model}", + _aiSettings.RequestTimeoutSeconds, + model + ); + throw new TimeoutException( + $"Request timed out after {_aiSettings.RequestTimeoutSeconds} seconds" + ); + } return response.ToString(); } diff --git a/ChatBot/Services/OllamaClientAdapter.cs b/ChatBot/Services/OllamaClientAdapter.cs index eb17798..39361b6 100644 --- a/ChatBot/Services/OllamaClientAdapter.cs +++ b/ChatBot/Services/OllamaClientAdapter.cs @@ -1,3 +1,4 @@ +using System.Net.Security; using ChatBot.Services.Interfaces; using OllamaSharp; using OllamaSharp.Models; @@ -17,7 +18,21 @@ namespace ChatBot.Services if (string.IsNullOrWhiteSpace(url)) throw new ArgumentException("URL cannot be empty", nameof(url)); - _client = new OllamaApiClient(new Uri(url)); + // Configure HttpClient to ignore SSL certificate issues + var httpClientHandler = new HttpClientHandler + { +#pragma warning disable S4830 // Enable server certificate validation on this SSL/TLS connection + ServerCertificateCustomValidationCallback = ( + message, + cert, + chain, + sslPolicyErrors + ) => true +#pragma warning restore S4830 // Enable server certificate validation on this SSL/TLS connection + }; + + var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(url) }; + _client = new OllamaApiClient(httpClient, url); } public string SelectedModel diff --git a/ChatBot/appsettings.json b/ChatBot/appsettings.json index db109c6..fa90eb7 100644 --- a/ChatBot/appsettings.json +++ b/ChatBot/appsettings.json @@ -32,11 +32,14 @@ "BotToken": "8461762778:AAEk1wHMqd84_I_loL9FQPciZakGYe557KA" }, "Ollama": { - "Url": "http://10.10.1.202:11434", - "DefaultModel": "llama3" + "Url": "https://ai.api.home/", + "DefaultModel": "gemma3:4b" }, "AI": { - "Temperature": 0.7, - "SystemPromptPath": "Prompts/system-prompt.txt" + "Temperature": 0.9, + "SystemPromptPath": "Prompts/system-prompt.txt", + "MaxRetryAttempts": 3, + "RetryDelayMs": 1000, + "RequestTimeoutSeconds": 60 } }