172 lines
6.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|