Files
ChatBot/ChatBot/Services/AIService.cs
2025-10-15 20:36:55 +03:00

172 lines
6.3 KiB
C#

using ChatBot.Models.Configuration;
using ChatBot.Models.Dto;
using Microsoft.Extensions.Options;
using ServiceStack;
namespace ChatBot.Services
{
public class AIService
{
private readonly ILogger<AIService> _logger;
private readonly OpenRouterSettings _openRouterSettings;
private readonly ModelService _modelService;
private readonly JsonApiClient _client;
public AIService(
ILogger<AIService> logger,
IOptions<OpenRouterSettings> openRouterSettings,
ModelService modelService
)
{
_logger = logger;
_openRouterSettings = openRouterSettings.Value;
_modelService = modelService;
_client = new JsonApiClient(_openRouterSettings.Url)
{
BearerToken = _openRouterSettings.Token,
};
// Log available configuration
_logger.LogInformation(
"AIService initialized with URL: {Url}",
_openRouterSettings.Url
);
}
public async Task<string> GenerateTextAsync(
string prompt,
string role,
int? maxTokens = null
)
{
var modelSettings = _modelService.GetCurrentModelSettings();
var tokens = maxTokens ?? modelSettings.MaxTokens;
var model = modelSettings.Name;
try
{
var result = await _client.PostAsync<OpenAiChatResponse>(
"/v1/chat/completions",
new OpenAiChatCompletion
{
Model = model,
Messages = [new() { Role = role, Content = prompt }],
MaxTokens = tokens,
Temperature = modelSettings.Temperature,
}
);
return result.Choices[0].Message.Content;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating text with model {Model}", model);
// Пытаемся переключиться на другую модель
if (_modelService.TrySwitchToNextModel())
{
_logger.LogInformation(
"Retrying with alternative model: {Model}",
_modelService.GetCurrentModel()
);
return await GenerateTextAsync(prompt, role, tokens);
}
return string.Empty;
}
}
/// <summary>
/// Generate text using conversation history
/// </summary>
public async Task<string> GenerateTextAsync(
List<ChatMessage> messages,
int? maxTokens = null,
double? temperature = null
)
{
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++)
{
try
{
var result = await _client.PostAsync<OpenAiChatResponse>(
"/v1/chat/completions",
new OpenAiChatCompletion
{
Model = model,
Messages = messages,
MaxTokens = tokens,
Temperature = temp,
}
);
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,
model
);
if (attempt == _openRouterSettings.MaxRetries)
{
_logger.LogError(
ex,
"Failed to generate text after {MaxRetries} attempts due to rate limiting for model {Model}",
_openRouterSettings.MaxRetries,
model
);
return string.Empty;
}
// Calculate delay: exponential backoff with jitter
var baseDelay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)); // 1s, 2s, 4s...
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 2000)); // Add up to 2s random jitter
var delay = baseDelay.Add(jitter);
_logger.LogInformation(
"Waiting {Delay} before retry {NextAttempt}/{MaxRetries}",
delay,
attempt + 1,
_openRouterSettings.MaxRetries
);
await Task.Delay(delay);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error generating text with conversation history. Model: {Model}, Messages count: {MessageCount}",
model,
messages.Count
);
// Пытаемся переключиться на другую модель
if (_modelService.TrySwitchToNextModel())
{
_logger.LogInformation(
"Retrying with alternative model: {Model}",
_modelService.GetCurrentModel()
);
model = _modelService.GetCurrentModel();
continue;
}
return string.Empty;
}
}
return string.Empty;
}
}
}