add model settings

This commit is contained in:
Leonid Pershin
2025-10-15 20:36:55 +03:00
parent ecc518701c
commit bdf31cc73c
6 changed files with 239 additions and 80 deletions

View File

@@ -0,0 +1,33 @@
namespace ChatBot.Models.Configuration
{
/// <summary>
/// Настройки конкретной модели ИИ
/// </summary>
public class ModelSettings
{
/// <summary>
/// Название модели
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Максимальное количество токенов для этой модели
/// </summary>
public int MaxTokens { get; set; } = 1000;
/// <summary>
/// Температура генерации для этой модели (креативность от 0.0 до 2.0)
/// </summary>
public double Temperature { get; set; } = 0.7;
/// <summary>
/// Описание модели
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// Является ли модель активной (доступной для использования)
/// </summary>
public bool IsEnabled { get; set; } = true;
}
}

View File

@@ -16,10 +16,15 @@ namespace ChatBot.Models.Configuration
public string Url { get; set; } = string.Empty;
/// <summary>
/// Список доступных моделей ИИ
/// Список доступных моделей ИИ (для обратной совместимости)
/// </summary>
public List<string> AvailableModels { get; set; } = new();
/// <summary>
/// Настройки для каждой модели отдельно
/// </summary>
public List<ModelSettings> ModelConfigurations { get; set; } = new();
/// <summary>
/// Модель по умолчанию для генерации ответов
/// </summary>
@@ -31,12 +36,12 @@ namespace ChatBot.Models.Configuration
public int MaxRetries { get; set; } = 3;
/// <summary>
/// Максимальное количество токенов в ответе
/// Максимальное количество токенов в ответе (по умолчанию, если не задано для конкретной модели)
/// </summary>
public int MaxTokens { get; set; } = 1000;
/// <summary>
/// Температура генерации (креативность ответов от 0.0 до 2.0)
/// Температура генерации по умолчанию (креативность ответов от 0.0 до 2.0)
/// </summary>
public double Temperature { get; set; } = 0.7;
}

View File

@@ -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
}
}
/// <summary>
/// /// Валидирует конфигурации моделей
/// </summary>
/// <param name="modelConfigurations">Конфигурации моделей</param>
/// <param name="errors">Список ошибок валидации</param>
private static void ValidateModelConfigurations(
IEnumerable<ModelSettings> modelConfigurations,
List<string> 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"
);
}
}
}
/// <summary>
/// Валидирует модель по умолчанию
/// </summary>

View File

@@ -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

View File

@@ -29,76 +29,12 @@ namespace ChatBot.Services
{
try
{
// Получаем доступные модели с OpenRouter API
var response = await _client.GetAsync<dynamic>("/v1/models");
if (response != null)
{
// Парсим ответ и извлекаем названия моделей
var models = new List<string>();
// Если ответ содержит массив моделей
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<List<string>> LoadModelsFromApiAsync()
{
var response = await _client.GetAsync<dynamic>("/v1/models");
if (response == null)
{
_logger.LogInformation(
"Using {Count} models from configuration (API unavailable)",
_openRouterSettings.AvailableModels.Count
);
return new List<string>();
}
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<string>();
}
private static List<string> ParseModelsFromResponse(dynamic response)
{
var models = new List<string>();
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;
}
/// <summary>
/// Получает настройки для текущей модели
/// </summary>
/// <returns>Настройки модели или настройки по умолчанию</returns>
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();
}
/// <summary>
/// Получает настройки по умолчанию
/// </summary>
/// <returns>Настройки по умолчанию</returns>
private ModelSettings GetDefaultModelSettings()
{
return new ModelSettings
{
Name = GetCurrentModel(),
MaxTokens = _openRouterSettings.MaxTokens,
Temperature = _openRouterSettings.Temperature,
IsEnabled = true,
};
}
public bool TrySwitchToNextModel()
{
if (_availableModels.Count <= 1)

View File

@@ -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,