From bdf31cc73cf663497ac612646d86e9075d67f43d Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 15 Oct 2025 20:36:55 +0300 Subject: [PATCH] add model settings --- ChatBot/Models/Configuration/ModelSettings.cs | 33 ++++ .../Configuration/OpenRouterSettings.cs | 11 +- .../Validators/ConfigurationValidator.cs | 39 ++++ ChatBot/Services/AIService.cs | 20 +- ChatBot/Services/ModelService.cs | 186 +++++++++++------- ChatBot/appsettings.json | 30 +++ 6 files changed, 239 insertions(+), 80 deletions(-) create mode 100644 ChatBot/Models/Configuration/ModelSettings.cs diff --git a/ChatBot/Models/Configuration/ModelSettings.cs b/ChatBot/Models/Configuration/ModelSettings.cs new file mode 100644 index 0000000..d7f861b --- /dev/null +++ b/ChatBot/Models/Configuration/ModelSettings.cs @@ -0,0 +1,33 @@ +namespace ChatBot.Models.Configuration +{ + /// + /// Настройки конкретной модели ИИ + /// + public class ModelSettings + { + /// + /// Название модели + /// + public string Name { get; set; } = string.Empty; + + /// + /// Максимальное количество токенов для этой модели + /// + public int MaxTokens { get; set; } = 1000; + + /// + /// Температура генерации для этой модели (креативность от 0.0 до 2.0) + /// + public double Temperature { get; set; } = 0.7; + + /// + /// Описание модели + /// + public string Description { get; set; } = string.Empty; + + /// + /// Является ли модель активной (доступной для использования) + /// + public bool IsEnabled { get; set; } = true; + } +} diff --git a/ChatBot/Models/Configuration/OpenRouterSettings.cs b/ChatBot/Models/Configuration/OpenRouterSettings.cs index 74323fb..870edb1 100644 --- a/ChatBot/Models/Configuration/OpenRouterSettings.cs +++ b/ChatBot/Models/Configuration/OpenRouterSettings.cs @@ -16,10 +16,15 @@ namespace ChatBot.Models.Configuration public string Url { get; set; } = string.Empty; /// - /// Список доступных моделей ИИ + /// Список доступных моделей ИИ (для обратной совместимости) /// public List AvailableModels { get; set; } = new(); + /// + /// Настройки для каждой модели отдельно + /// + public List ModelConfigurations { get; set; } = new(); + /// /// Модель по умолчанию для генерации ответов /// @@ -31,12 +36,12 @@ namespace ChatBot.Models.Configuration public int MaxRetries { get; set; } = 3; /// - /// Максимальное количество токенов в ответе + /// Максимальное количество токенов в ответе (по умолчанию, если не задано для конкретной модели) /// public int MaxTokens { get; set; } = 1000; /// - /// Температура генерации (креативность ответов от 0.0 до 2.0) + /// Температура генерации по умолчанию (креативность ответов от 0.0 до 2.0) /// public double Temperature { get; set; } = 0.7; } diff --git a/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs b/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs index 0f99c2f..bf438b9 100644 --- a/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs +++ b/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs @@ -68,6 +68,7 @@ namespace ChatBot.Models.Configuration.Validators ValidateToken(settings.Token, errors); ValidateUrl(settings.Url, errors); ValidateAvailableModels(settings.AvailableModels, errors); + ValidateModelConfigurations(settings.ModelConfigurations, errors); ValidateDefaultModel(settings.DefaultModel, settings.AvailableModels, errors); ValidateNumericSettings(settings, errors); @@ -137,6 +138,44 @@ namespace ChatBot.Models.Configuration.Validators } } + /// + /// /// Валидирует конфигурации моделей + /// + /// Конфигурации моделей + /// Список ошибок валидации + private static void ValidateModelConfigurations( + IEnumerable modelConfigurations, + List errors + ) + { + if (modelConfigurations == null) + { + return; // Конфигурации моделей необязательны + } + + foreach (var modelConfig in modelConfigurations) + { + if (string.IsNullOrWhiteSpace(modelConfig.Name)) + { + errors.Add("OpenRouter:ModelConfigurations contains model with empty name"); + } + + if (modelConfig.MaxTokens < 1 || modelConfig.MaxTokens > 100000) + { + errors.Add( + $"OpenRouter:ModelConfigurations model '{modelConfig.Name}' MaxTokens must be between 1 and 100000" + ); + } + + if (modelConfig.Temperature < 0.0 || modelConfig.Temperature > 2.0) + { + errors.Add( + $"OpenRouter:ModelConfigurations model '{modelConfig.Name}' Temperature must be between 0.0 and 2.0" + ); + } + } + } + /// /// Валидирует модель по умолчанию /// diff --git a/ChatBot/Services/AIService.cs b/ChatBot/Services/AIService.cs index 6483062..7621cb3 100644 --- a/ChatBot/Services/AIService.cs +++ b/ChatBot/Services/AIService.cs @@ -39,8 +39,9 @@ namespace ChatBot.Services int? maxTokens = null ) { - var tokens = maxTokens ?? _openRouterSettings.MaxTokens; - var model = _modelService.GetCurrentModel(); + var modelSettings = _modelService.GetCurrentModelSettings(); + var tokens = maxTokens ?? modelSettings.MaxTokens; + var model = modelSettings.Name; try { @@ -51,10 +52,10 @@ namespace ChatBot.Services Model = model, Messages = [new() { Role = role, Content = prompt }], MaxTokens = tokens, - Temperature = _openRouterSettings.Temperature, + Temperature = modelSettings.Temperature, } ); - return result.Choices.First().Message.Content; + return result.Choices[0].Message.Content; } catch (Exception ex) { @@ -83,9 +84,10 @@ namespace ChatBot.Services double? temperature = null ) { - var tokens = maxTokens ?? _openRouterSettings.MaxTokens; - var temp = temperature ?? _openRouterSettings.Temperature; - var model = _modelService.GetCurrentModel(); + var modelSettings = _modelService.GetCurrentModelSettings(); + var tokens = maxTokens ?? modelSettings.MaxTokens; + var temp = temperature ?? modelSettings.Temperature; + var model = modelSettings.Name; for (int attempt = 1; attempt <= _openRouterSettings.MaxRetries; attempt++) { @@ -101,12 +103,13 @@ namespace ChatBot.Services Temperature = temp, } ); - return result.Choices.First().Message.Content; + return result.Choices[0].Message.Content; } catch (Exception ex) when (ex.Message.Contains("429") || ex.Message.Contains("Too Many Requests")) { _logger.LogWarning( + ex, "Rate limit exceeded (429) on attempt {Attempt}/{MaxRetries} for model {Model}. Retrying...", attempt, _openRouterSettings.MaxRetries, @@ -116,6 +119,7 @@ namespace ChatBot.Services if (attempt == _openRouterSettings.MaxRetries) { _logger.LogError( + ex, "Failed to generate text after {MaxRetries} attempts due to rate limiting for model {Model}", _openRouterSettings.MaxRetries, model diff --git a/ChatBot/Services/ModelService.cs b/ChatBot/Services/ModelService.cs index b317955..b92a8dc 100644 --- a/ChatBot/Services/ModelService.cs +++ b/ChatBot/Services/ModelService.cs @@ -29,76 +29,12 @@ namespace ChatBot.Services { try { - // Получаем доступные модели с OpenRouter API - var response = await _client.GetAsync("/v1/models"); - - if (response != null) - { - // Парсим ответ и извлекаем названия моделей - var models = new List(); - - // Если ответ содержит массив моделей - if (response is System.Text.Json.JsonElement jsonElement) - { - if ( - jsonElement.TryGetProperty("data", out var dataElement) - && dataElement.ValueKind == System.Text.Json.JsonValueKind.Array - ) - { - foreach (var modelElement in dataElement.EnumerateArray()) - { - if (modelElement.TryGetProperty("id", out var idElement)) - { - var modelId = idElement.GetString(); - if (!string.IsNullOrEmpty(modelId)) - { - models.Add(modelId); - } - } - } - } - } - - // Если получили модели с API, используем их, иначе используем из конфига - if (models.Any()) - { - _availableModels = models; - _logger.LogInformation( - "Loaded {Count} models from OpenRouter API", - models.Count - ); - } - else - { - _availableModels = _openRouterSettings.AvailableModels.ToList(); - _logger.LogInformation( - "Using {Count} models from configuration", - _availableModels.Count - ); - } - } - else - { - _availableModels = _openRouterSettings.AvailableModels.ToList(); - _logger.LogInformation( - "Using {Count} models from configuration (API unavailable)", - _availableModels.Count - ); - } - - // Устанавливаем модель по умолчанию - if ( - !string.IsNullOrEmpty(_openRouterSettings.DefaultModel) - && _availableModels.Contains(_openRouterSettings.DefaultModel) - ) - { - _currentModelIndex = _availableModels.IndexOf(_openRouterSettings.DefaultModel); - } - else if (_availableModels.Any()) - { - _currentModelIndex = 0; - } + var models = await LoadModelsFromApiAsync(); + _availableModels = models.Any() + ? models + : _openRouterSettings.AvailableModels.ToList(); + SetDefaultModel(); _logger.LogInformation("Current model: {Model}", GetCurrentModel()); } catch (Exception ex) @@ -109,11 +45,123 @@ namespace ChatBot.Services } } + private async Task> LoadModelsFromApiAsync() + { + var response = await _client.GetAsync("/v1/models"); + if (response == null) + { + _logger.LogInformation( + "Using {Count} models from configuration (API unavailable)", + _openRouterSettings.AvailableModels.Count + ); + return new List(); + } + + var models = ParseModelsFromResponse(response); + if (models.Any()) + { + _logger.LogInformation( + "Loaded {Count} models from OpenRouter API", + (int)models.Count + ); + return models; + } + + _logger.LogInformation( + "Using {Count} models from configuration", + _openRouterSettings.AvailableModels.Count + ); + return new List(); + } + + private static List ParseModelsFromResponse(dynamic response) + { + var models = new List(); + + if (response is not System.Text.Json.JsonElement jsonElement) + return models; + + if ( + !jsonElement.TryGetProperty("data", out var dataElement) + || dataElement.ValueKind != System.Text.Json.JsonValueKind.Array + ) + return models; + + foreach (var modelElement in dataElement.EnumerateArray()) + { + if (modelElement.TryGetProperty("id", out var idElement)) + { + var modelId = idElement.GetString(); + if (!string.IsNullOrEmpty(modelId)) + { + models.Add(modelId); + } + } + } + + return models; + } + + private void SetDefaultModel() + { + if ( + string.IsNullOrEmpty(_openRouterSettings.DefaultModel) + || !_availableModels.Contains(_openRouterSettings.DefaultModel) + ) + { + _currentModelIndex = 0; + return; + } + + _currentModelIndex = _availableModels.IndexOf(_openRouterSettings.DefaultModel); + } + public string GetCurrentModel() { return _availableModels.Count > 0 ? _availableModels[_currentModelIndex] : string.Empty; } + /// + /// Получает настройки для текущей модели + /// + /// Настройки модели или настройки по умолчанию + public ModelSettings GetCurrentModelSettings() + { + var currentModel = GetCurrentModel(); + if (string.IsNullOrEmpty(currentModel)) + { + return GetDefaultModelSettings(); + } + + // Ищем настройки для текущей модели + var modelConfig = _openRouterSettings.ModelConfigurations.FirstOrDefault(m => + m.Name.Equals(currentModel, StringComparison.OrdinalIgnoreCase) + ); + + if (modelConfig != null) + { + return modelConfig; + } + + // Если настройки не найдены, возвращаем настройки по умолчанию + return GetDefaultModelSettings(); + } + + /// + /// Получает настройки по умолчанию + /// + /// Настройки по умолчанию + private ModelSettings GetDefaultModelSettings() + { + return new ModelSettings + { + Name = GetCurrentModel(), + MaxTokens = _openRouterSettings.MaxTokens, + Temperature = _openRouterSettings.Temperature, + IsEnabled = true, + }; + } + public bool TrySwitchToNextModel() { if (_availableModels.Count <= 1) diff --git a/ChatBot/appsettings.json b/ChatBot/appsettings.json index da24670..2e67921 100644 --- a/ChatBot/appsettings.json +++ b/ChatBot/appsettings.json @@ -40,6 +40,36 @@ "microsoft/phi-3-mini-128k-instruct:free", "google/gemma-2-2b-it:free" ], + "ModelConfigurations": [ + { + "Name": "qwen/qwen3-4b:free", + "MaxTokens": 2000, + "Temperature": 0.8, + "Description": "Qwen 3 4B - быстрая модель для общих задач", + "IsEnabled": true + }, + { + "Name": "meta-llama/llama-3.1-8b-instruct:free", + "MaxTokens": 1500, + "Temperature": 0.7, + "Description": "Llama 3.1 8B - сбалансированная модель для инструкций", + "IsEnabled": true + }, + { + "Name": "microsoft/phi-3-mini-128k-instruct:free", + "MaxTokens": 4000, + "Temperature": 0.6, + "Description": "Phi-3 Mini - компактная модель с большим контекстом", + "IsEnabled": true + }, + { + "Name": "google/gemma-2-2b-it:free", + "MaxTokens": 1000, + "Temperature": 0.9, + "Description": "Gemma 2 2B - легкая модель для быстрых ответов", + "IsEnabled": true + } + ], "DefaultModel": "qwen/qwen3-4b:free", "MaxRetries": 3, "MaxTokens": 1000,