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,