fix request
This commit is contained in:
86
ChatBot/HISTORY_COMPRESSION.md
Normal file
86
ChatBot/HISTORY_COMPRESSION.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Система постепенного сжатия истории сообщений
|
||||
|
||||
## Обзор
|
||||
|
||||
Реализована система постепенного сжатия истории сообщений для оптимизации использования памяти и улучшения производительности чат-бота. Система автоматически сжимает старые сообщения, сохраняя при этом важную информацию.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
### 1. Автоматическое сжатие
|
||||
- Сжатие активируется при превышении порогового количества сообщений
|
||||
- Старые сообщения группируются и сжимаются в краткие резюме
|
||||
- Системные сообщения всегда сохраняются
|
||||
- Последние сообщения остаются без изменений
|
||||
|
||||
### 2. Умная суммаризация
|
||||
- Использование ИИ для создания кратких резюме старых сообщений
|
||||
- Раздельная обработка сообщений пользователя и ассистента
|
||||
- Сохранение ключевой информации при сжатии
|
||||
|
||||
### 3. Настраиваемые параметры
|
||||
- `EnableHistoryCompression` - включение/отключение сжатия
|
||||
- `CompressionThreshold` - порог активации сжатия (по умолчанию 20 сообщений)
|
||||
- `CompressionTarget` - целевое количество сообщений после сжатия (по умолчанию 10)
|
||||
- `MinMessageLengthForSummarization` - минимальная длина сообщения для суммаризации (50 символов)
|
||||
- `MaxSummarizedMessageLength` - максимальная длина сжатого сообщения (200 символов)
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Новые компоненты
|
||||
|
||||
1. **IHistoryCompressionService** - интерфейс для сжатия истории
|
||||
2. **HistoryCompressionService** - реализация сервиса сжатия
|
||||
3. **Обновленный ChatSession** - поддержка асинхронного сжатия
|
||||
4. **Обновленный AIService** - интеграция сжатия в генерацию ответов
|
||||
5. **Обновленный ChatService** - использование сжатия при обработке сообщений
|
||||
|
||||
### Алгоритм сжатия
|
||||
|
||||
1. **Проверка необходимости сжатия** - сравнение количества сообщений с порогом
|
||||
2. **Разделение сообщений** - отделение системных, старых и новых сообщений
|
||||
3. **Группировка по ролям** - отдельная обработка сообщений пользователя и ассистента
|
||||
4. **Суммаризация** - создание кратких резюме с помощью ИИ
|
||||
5. **Объединение** - формирование финального списка сообщений
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### appsettings.json
|
||||
```json
|
||||
{
|
||||
"AI": {
|
||||
"EnableHistoryCompression": true,
|
||||
"CompressionThreshold": 20,
|
||||
"CompressionTarget": 10,
|
||||
"MinMessageLengthForSummarization": 50,
|
||||
"MaxSummarizedMessageLength": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### Автоматическое сжатие
|
||||
Сжатие происходит автоматически при добавлении новых сообщений, если включено в настройках.
|
||||
|
||||
### Мониторинг
|
||||
Команда `/settings` показывает текущее состояние сжатия и параметры.
|
||||
|
||||
## Преимущества
|
||||
|
||||
1. **Экономия памяти** - значительное сокращение использования RAM
|
||||
2. **Улучшенная производительность** - быстрее обработка длинных диалогов
|
||||
3. **Сохранение контекста** - важная информация не теряется
|
||||
4. **Гибкость настройки** - возможность адаптации под различные сценарии
|
||||
5. **Обратная совместимость** - можно отключить без изменения кода
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
- При ошибках сжатия система автоматически переключается на простое обрезание истории
|
||||
- Логирование всех операций сжатия для мониторинга
|
||||
- Graceful degradation - бот продолжает работать даже при проблемах со сжатием
|
||||
|
||||
## Производительность
|
||||
|
||||
- Сжатие выполняется асинхронно, не блокируя основной поток
|
||||
- Использование кэширования для оптимизации повторных операций
|
||||
- Минимальное влияние на время отклика бота
|
||||
@@ -1,4 +1,5 @@
|
||||
using ChatBot.Models.Dto;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using OllamaSharp.Models.Chat;
|
||||
|
||||
namespace ChatBot.Models
|
||||
@@ -10,6 +11,7 @@ namespace ChatBot.Models
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly List<ChatMessage> _messageHistory = new List<ChatMessage>();
|
||||
private IHistoryCompressionService? _compressionService;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the chat session
|
||||
@@ -51,6 +53,14 @@ namespace ChatBot.Models
|
||||
/// </summary>
|
||||
public int MaxHistoryLength { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Enable compression service for this session
|
||||
/// </summary>
|
||||
public void SetCompressionService(IHistoryCompressionService compressionService)
|
||||
{
|
||||
_compressionService = compressionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a message to the history and manage history length (thread-safe)
|
||||
/// </summary>
|
||||
@@ -83,6 +93,100 @@ namespace ChatBot.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a message to the history with compression support (thread-safe)
|
||||
/// </summary>
|
||||
public async Task AddMessageWithCompressionAsync(
|
||||
ChatMessage message,
|
||||
int compressionThreshold,
|
||||
int compressionTarget
|
||||
)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_messageHistory.Add(message);
|
||||
LastUpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// Check if compression is needed and perform it asynchronously
|
||||
if (
|
||||
_compressionService != null
|
||||
&& _compressionService.ShouldCompress(_messageHistory.Count, compressionThreshold)
|
||||
)
|
||||
{
|
||||
await CompressHistoryAsync(compressionTarget);
|
||||
}
|
||||
else if (_messageHistory.Count > MaxHistoryLength)
|
||||
{
|
||||
// Fallback to simple trimming if compression is not available
|
||||
await TrimHistoryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress message history using the compression service
|
||||
/// </summary>
|
||||
private async Task CompressHistoryAsync(int targetCount)
|
||||
{
|
||||
if (_compressionService == null)
|
||||
{
|
||||
await TrimHistoryAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var compressedMessages = await _compressionService.CompressHistoryAsync(
|
||||
_messageHistory,
|
||||
targetCount
|
||||
);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_messageHistory.Clear();
|
||||
_messageHistory.AddRange(compressedMessages);
|
||||
LastUpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Log error and fallback to simple trimming
|
||||
// Note: We can't inject ILogger here, so we'll just fallback
|
||||
await TrimHistoryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple history trimming without compression
|
||||
/// </summary>
|
||||
private async Task TrimHistoryAsync()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_messageHistory.Count > MaxHistoryLength)
|
||||
{
|
||||
var systemMessage = _messageHistory.FirstOrDefault(m =>
|
||||
m.Role == ChatRole.System
|
||||
);
|
||||
var recentMessages = _messageHistory
|
||||
.Where(m => m.Role != ChatRole.System)
|
||||
.TakeLast(MaxHistoryLength - (systemMessage != null ? 1 : 0))
|
||||
.ToList();
|
||||
|
||||
_messageHistory.Clear();
|
||||
if (systemMessage != null)
|
||||
{
|
||||
_messageHistory.Add(systemMessage);
|
||||
}
|
||||
_messageHistory.AddRange(recentMessages);
|
||||
LastUpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a user message with username information
|
||||
/// </summary>
|
||||
@@ -96,6 +200,24 @@ namespace ChatBot.Models
|
||||
AddMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a user message with username information and compression support
|
||||
/// </summary>
|
||||
public async Task AddUserMessageWithCompressionAsync(
|
||||
string content,
|
||||
string username,
|
||||
int compressionThreshold,
|
||||
int compressionTarget
|
||||
)
|
||||
{
|
||||
var message = new ChatMessage
|
||||
{
|
||||
Role = ChatRole.User,
|
||||
Content = ChatType == "private" ? content : $"{username}: {content}",
|
||||
};
|
||||
await AddMessageWithCompressionAsync(message, compressionThreshold, compressionTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an assistant message
|
||||
/// </summary>
|
||||
@@ -105,6 +227,19 @@ namespace ChatBot.Models
|
||||
AddMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an assistant message with compression support
|
||||
/// </summary>
|
||||
public async Task AddAssistantMessageWithCompressionAsync(
|
||||
string content,
|
||||
int compressionThreshold,
|
||||
int compressionTarget
|
||||
)
|
||||
{
|
||||
var message = new ChatMessage { Role = ChatRole.Assistant, Content = content };
|
||||
await AddMessageWithCompressionAsync(message, compressionThreshold, compressionTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all messages (thread-safe)
|
||||
/// </summary>
|
||||
|
||||
@@ -36,5 +36,50 @@ namespace ChatBot.Models.Configuration
|
||||
/// Request timeout in seconds
|
||||
/// </summary>
|
||||
public int RequestTimeoutSeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Enable gradual message history compression
|
||||
/// </summary>
|
||||
public bool EnableHistoryCompression { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of messages before compression starts
|
||||
/// </summary>
|
||||
public int CompressionThreshold { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Target number of messages after compression
|
||||
/// </summary>
|
||||
public int CompressionTarget { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum message length to be considered for summarization (in characters)
|
||||
/// </summary>
|
||||
public int MinMessageLengthForSummarization { get; set; } = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum length of summarized message (in characters)
|
||||
/// </summary>
|
||||
public int MaxSummarizedMessageLength { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Enable exponential backoff for retry attempts
|
||||
/// </summary>
|
||||
public bool EnableExponentialBackoff { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum retry delay in milliseconds
|
||||
/// </summary>
|
||||
public int MaxRetryDelayMs { get; set; } = 30000;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for compression operations in seconds
|
||||
/// </summary>
|
||||
public int CompressionTimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for status check operations in seconds
|
||||
/// </summary>
|
||||
public int StatusCheckTimeoutSeconds { get; set; } = 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace ChatBot.Models.Configuration.Validators
|
||||
ValidateSystemPromptPath(options, errors);
|
||||
ValidateRetrySettings(options, errors);
|
||||
ValidateTimeoutSettings(options, errors);
|
||||
ValidateCompressionSettings(options, errors);
|
||||
|
||||
return errors.Count > 0
|
||||
? ValidateOptionsResult.Fail(errors)
|
||||
@@ -97,5 +98,80 @@ namespace ChatBot.Models.Configuration.Validators
|
||||
errors.Add("Request timeout cannot exceed 300 seconds (5 minutes)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateCompressionSettings(AISettings options, List<string> errors)
|
||||
{
|
||||
if (options.CompressionThreshold < 5)
|
||||
{
|
||||
errors.Add("Compression threshold must be at least 5 messages");
|
||||
}
|
||||
else if (options.CompressionThreshold > 100)
|
||||
{
|
||||
errors.Add("Compression threshold cannot exceed 100 messages");
|
||||
}
|
||||
|
||||
if (options.CompressionTarget < 3)
|
||||
{
|
||||
errors.Add("Compression target must be at least 3 messages");
|
||||
}
|
||||
else if (options.CompressionTarget >= options.CompressionThreshold)
|
||||
{
|
||||
errors.Add("Compression target must be less than compression threshold");
|
||||
}
|
||||
|
||||
if (options.MinMessageLengthForSummarization < 10)
|
||||
{
|
||||
errors.Add(
|
||||
"Minimum message length for summarization must be at least 10 characters"
|
||||
);
|
||||
}
|
||||
else if (options.MinMessageLengthForSummarization > 500)
|
||||
{
|
||||
errors.Add("Minimum message length for summarization cannot exceed 500 characters");
|
||||
}
|
||||
|
||||
if (options.MaxSummarizedMessageLength < 20)
|
||||
{
|
||||
errors.Add("Maximum summarized message length must be at least 20 characters");
|
||||
}
|
||||
else if (options.MaxSummarizedMessageLength > 1000)
|
||||
{
|
||||
errors.Add("Maximum summarized message length cannot exceed 1000 characters");
|
||||
}
|
||||
|
||||
if (options.MaxSummarizedMessageLength <= options.MinMessageLengthForSummarization)
|
||||
{
|
||||
errors.Add(
|
||||
"Maximum summarized message length must be greater than minimum message length for summarization"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.MaxRetryDelayMs < 1000)
|
||||
{
|
||||
errors.Add("Maximum retry delay must be at least 1000ms");
|
||||
}
|
||||
else if (options.MaxRetryDelayMs > 300000)
|
||||
{
|
||||
errors.Add("Maximum retry delay cannot exceed 300000ms (5 minutes)");
|
||||
}
|
||||
|
||||
if (options.CompressionTimeoutSeconds < 5)
|
||||
{
|
||||
errors.Add("Compression timeout must be at least 5 seconds");
|
||||
}
|
||||
else if (options.CompressionTimeoutSeconds > 300)
|
||||
{
|
||||
errors.Add("Compression timeout cannot exceed 300 seconds (5 minutes)");
|
||||
}
|
||||
|
||||
if (options.StatusCheckTimeoutSeconds < 2)
|
||||
{
|
||||
errors.Add("Status check timeout must be at least 2 seconds");
|
||||
}
|
||||
else if (options.StatusCheckTimeoutSeconds > 60)
|
||||
{
|
||||
errors.Add("Status check timeout cannot exceed 60 seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ try
|
||||
// Регистрируем основные сервисы
|
||||
builder.Services.AddSingleton<ModelService>();
|
||||
builder.Services.AddSingleton<SystemPromptService>();
|
||||
builder.Services.AddSingleton<IHistoryCompressionService, HistoryCompressionService>();
|
||||
builder.Services.AddSingleton<IAIService, AIService>();
|
||||
builder.Services.AddSingleton<ChatService>();
|
||||
|
||||
@@ -61,6 +62,7 @@ try
|
||||
builder.Services.AddSingleton<ITelegramCommand, HelpCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, ClearCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, SettingsCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, StatusCommand>();
|
||||
|
||||
// Регистрируем Telegram сервисы
|
||||
builder.Services.AddSingleton<ITelegramBotClient>(provider =>
|
||||
|
||||
@@ -19,13 +19,15 @@ namespace ChatBot.Services
|
||||
private readonly IOllamaClient _client;
|
||||
private readonly AISettings _aiSettings;
|
||||
private readonly SystemPromptService _systemPromptService;
|
||||
private readonly IHistoryCompressionService _compressionService;
|
||||
|
||||
public AIService(
|
||||
ILogger<AIService> logger,
|
||||
ModelService modelService,
|
||||
IOllamaClient client,
|
||||
IOptions<AISettings> aiSettings,
|
||||
SystemPromptService systemPromptService
|
||||
SystemPromptService systemPromptService,
|
||||
IHistoryCompressionService compressionService
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -33,6 +35,7 @@ namespace ChatBot.Services
|
||||
_client = client;
|
||||
_aiSettings = aiSettings.Value;
|
||||
_systemPromptService = systemPromptService;
|
||||
_compressionService = compressionService;
|
||||
|
||||
_logger.LogInformation(
|
||||
"AIService initialized with Temperature: {Temperature}",
|
||||
@@ -72,16 +75,20 @@ namespace ChatBot.Services
|
||||
}
|
||||
catch (HttpRequestException ex) when (attempt < _aiSettings.MaxRetryAttempts)
|
||||
{
|
||||
var statusCode = GetHttpStatusCode(ex);
|
||||
var retryDelay = CalculateRetryDelay(attempt, statusCode);
|
||||
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"HTTP request failed on attempt {Attempt}/{MaxAttempts} for model {Model}. Retrying in {DelayMs}ms...",
|
||||
"HTTP request failed on attempt {Attempt}/{MaxAttempts} for model {Model} (Status: {StatusCode}). Retrying in {DelayMs}ms...",
|
||||
attempt,
|
||||
_aiSettings.MaxRetryAttempts,
|
||||
model,
|
||||
_aiSettings.RetryDelayMs
|
||||
statusCode,
|
||||
retryDelay
|
||||
);
|
||||
|
||||
await Task.Delay(_aiSettings.RetryDelayMs, cancellationToken);
|
||||
await Task.Delay(retryDelay, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -102,6 +109,40 @@ namespace ChatBot.Services
|
||||
return AIResponseConstants.DefaultErrorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate chat completion with history compression support
|
||||
/// </summary>
|
||||
public async Task<string> GenerateChatCompletionWithCompressionAsync(
|
||||
List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Apply compression if enabled and needed
|
||||
var processedMessages = messages;
|
||||
if (
|
||||
_aiSettings.EnableHistoryCompression
|
||||
&& _compressionService.ShouldCompress(
|
||||
messages.Count,
|
||||
_aiSettings.CompressionThreshold
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Compressing message history from {OriginalCount} to {TargetCount} messages",
|
||||
messages.Count,
|
||||
_aiSettings.CompressionTarget
|
||||
);
|
||||
|
||||
processedMessages = await _compressionService.CompressHistoryAsync(
|
||||
messages,
|
||||
_aiSettings.CompressionTarget,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
return await GenerateChatCompletionAsync(processedMessages, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a single generation attempt
|
||||
/// </summary>
|
||||
@@ -188,5 +229,58 @@ namespace ChatBot.Services
|
||||
|
||||
return chatMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract HTTP status code from HttpRequestException
|
||||
/// </summary>
|
||||
private static int? GetHttpStatusCode(HttpRequestException ex)
|
||||
{
|
||||
if (ex.Data.Contains("StatusCode") && ex.Data["StatusCode"] is int statusCode)
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
// Try to extract from message
|
||||
var message = ex.Message;
|
||||
if (message.Contains("502"))
|
||||
return 502;
|
||||
if (message.Contains("503"))
|
||||
return 503;
|
||||
if (message.Contains("504"))
|
||||
return 504;
|
||||
if (message.Contains("500"))
|
||||
return 500;
|
||||
if (message.Contains("429"))
|
||||
return 429;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate retry delay based on attempt number and status code
|
||||
/// </summary>
|
||||
private int CalculateRetryDelay(int attempt, int? statusCode)
|
||||
{
|
||||
var baseDelay = _aiSettings.RetryDelayMs;
|
||||
|
||||
// Calculate delay based on backoff strategy
|
||||
var calculatedDelay = _aiSettings.EnableExponentialBackoff
|
||||
? baseDelay * (int)Math.Pow(2, attempt - 1) // Exponential backoff
|
||||
: baseDelay * attempt; // Linear backoff
|
||||
|
||||
// Additional delay for specific status codes
|
||||
var additionalDelay = statusCode switch
|
||||
{
|
||||
502 => 2000, // Bad Gateway - server overloaded
|
||||
503 => 3000, // Service Unavailable
|
||||
504 => 5000, // Gateway Timeout
|
||||
429 => 5000, // Too Many Requests
|
||||
500 => 1000, // Internal Server Error
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// Cap the maximum delay
|
||||
return Math.Min(calculatedDelay + additionalDelay, _aiSettings.MaxRetryDelayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using ChatBot.Common.Constants;
|
||||
using ChatBot.Models;
|
||||
using ChatBot.Models.Configuration;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ChatBot.Services
|
||||
{
|
||||
@@ -12,16 +14,22 @@ namespace ChatBot.Services
|
||||
private readonly ILogger<ChatService> _logger;
|
||||
private readonly IAIService _aiService;
|
||||
private readonly ISessionStorage _sessionStorage;
|
||||
private readonly AISettings _aiSettings;
|
||||
private readonly IHistoryCompressionService _compressionService;
|
||||
|
||||
public ChatService(
|
||||
ILogger<ChatService> logger,
|
||||
IAIService aiService,
|
||||
ISessionStorage sessionStorage
|
||||
ISessionStorage sessionStorage,
|
||||
IOptions<AISettings> aiSettings,
|
||||
IHistoryCompressionService compressionService
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_aiService = aiService;
|
||||
_sessionStorage = sessionStorage;
|
||||
_aiSettings = aiSettings.Value;
|
||||
_compressionService = compressionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +41,15 @@ namespace ChatBot.Services
|
||||
string chatTitle = ""
|
||||
)
|
||||
{
|
||||
return _sessionStorage.GetOrCreate(chatId, chatType, chatTitle);
|
||||
var session = _sessionStorage.GetOrCreate(chatId, chatType, chatTitle);
|
||||
|
||||
// Set compression service if compression is enabled
|
||||
if (_aiSettings.EnableHistoryCompression)
|
||||
{
|
||||
session.SetCompressionService(_compressionService);
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +69,19 @@ namespace ChatBot.Services
|
||||
var session = GetOrCreateSession(chatId, chatType, chatTitle);
|
||||
|
||||
// Add user message to history with username
|
||||
if (_aiSettings.EnableHistoryCompression)
|
||||
{
|
||||
await session.AddUserMessageWithCompressionAsync(
|
||||
message,
|
||||
username,
|
||||
_aiSettings.CompressionThreshold,
|
||||
_aiSettings.CompressionTarget
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
session.AddUserMessage(message, username);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Processing message from user {Username} in chat {ChatId} ({ChatType}): {Message}",
|
||||
@@ -63,8 +91,8 @@ namespace ChatBot.Services
|
||||
message
|
||||
);
|
||||
|
||||
// Get AI response
|
||||
var response = await _aiService.GenerateChatCompletionAsync(
|
||||
// Get AI response with compression support
|
||||
var response = await _aiService.GenerateChatCompletionWithCompressionAsync(
|
||||
session.GetAllMessages(),
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
@@ -89,7 +117,18 @@ namespace ChatBot.Services
|
||||
}
|
||||
|
||||
// Add AI response to history
|
||||
if (_aiSettings.EnableHistoryCompression)
|
||||
{
|
||||
await session.AddAssistantMessageWithCompressionAsync(
|
||||
response,
|
||||
_aiSettings.CompressionThreshold,
|
||||
_aiSettings.CompressionTarget
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
session.AddAssistantMessage(response);
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"AI response generated for chat {ChatId} (length: {Length})",
|
||||
|
||||
405
ChatBot/Services/HistoryCompressionService.cs
Normal file
405
ChatBot/Services/HistoryCompressionService.cs
Normal file
@@ -0,0 +1,405 @@
|
||||
using System.Text;
|
||||
using ChatBot.Models.Configuration;
|
||||
using ChatBot.Models.Dto;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OllamaSharp.Models.Chat;
|
||||
|
||||
namespace ChatBot.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for compressing message history to reduce memory usage
|
||||
/// </summary>
|
||||
public class HistoryCompressionService : IHistoryCompressionService
|
||||
{
|
||||
private readonly ILogger<HistoryCompressionService> _logger;
|
||||
private readonly AISettings _aiSettings;
|
||||
private readonly IOllamaClient _ollamaClient;
|
||||
|
||||
public HistoryCompressionService(
|
||||
ILogger<HistoryCompressionService> logger,
|
||||
IOptions<AISettings> aiSettings,
|
||||
IOllamaClient ollamaClient
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_aiSettings = aiSettings.Value;
|
||||
_ollamaClient = ollamaClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress message history by summarizing old messages and removing less important ones
|
||||
/// </summary>
|
||||
public async Task<List<ChatMessage>> CompressHistoryAsync(
|
||||
List<ChatMessage> messages,
|
||||
int targetCount,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (messages.Count <= targetCount)
|
||||
{
|
||||
return messages;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Compressing message history from {OriginalCount} to {TargetCount} messages",
|
||||
messages.Count,
|
||||
targetCount
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
// Separate system messages, recent messages, and old messages
|
||||
var systemMessages = messages.Where(m => m.Role == ChatRole.System).ToList();
|
||||
var recentMessages = messages
|
||||
.Where(m => m.Role != ChatRole.System)
|
||||
.TakeLast(targetCount - systemMessages.Count)
|
||||
.ToList();
|
||||
var oldMessages = messages
|
||||
.Where(m => m.Role != ChatRole.System)
|
||||
.SkipLast(targetCount - systemMessages.Count)
|
||||
.ToList();
|
||||
|
||||
if (oldMessages.Count == 0)
|
||||
{
|
||||
return messages;
|
||||
}
|
||||
|
||||
// Compress old messages
|
||||
var compressedOldMessages = await CompressOldMessagesAsync(
|
||||
oldMessages,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
// Combine system messages, compressed old messages, and recent messages
|
||||
var result = new List<ChatMessage>();
|
||||
result.AddRange(systemMessages);
|
||||
result.AddRange(compressedOldMessages);
|
||||
result.AddRange(recentMessages);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Successfully compressed history: {OriginalCount} -> {CompressedCount} messages",
|
||||
messages.Count,
|
||||
result.Count
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"HTTP error during message compression, falling back to simple trimming. Original count: {OriginalCount}",
|
||||
messages.Count
|
||||
);
|
||||
|
||||
// Fallback to simple trimming
|
||||
return TrimMessagesSimple(messages, targetCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Failed to compress message history, falling back to simple trimming. Original count: {OriginalCount}",
|
||||
messages.Count
|
||||
);
|
||||
|
||||
// Fallback to simple trimming
|
||||
return TrimMessagesSimple(messages, targetCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if compression is needed based on message count and settings
|
||||
/// </summary>
|
||||
public bool ShouldCompress(int messageCount, int threshold)
|
||||
{
|
||||
return messageCount > threshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple message trimming without AI summarization
|
||||
/// </summary>
|
||||
private List<ChatMessage> TrimMessagesSimple(List<ChatMessage> messages, int targetCount)
|
||||
{
|
||||
if (messages.Count <= targetCount)
|
||||
{
|
||||
return messages;
|
||||
}
|
||||
|
||||
var systemMessages = messages.Where(m => m.Role == ChatRole.System).ToList();
|
||||
var otherMessages = messages.Where(m => m.Role != ChatRole.System).ToList();
|
||||
|
||||
var remainingSlots = targetCount - systemMessages.Count;
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
return systemMessages;
|
||||
}
|
||||
|
||||
var result = new List<ChatMessage>();
|
||||
result.AddRange(systemMessages);
|
||||
result.AddRange(otherMessages.TakeLast(remainingSlots));
|
||||
|
||||
_logger.LogInformation(
|
||||
"Simple trimming applied: {OriginalCount} -> {TrimmedCount} messages",
|
||||
messages.Count,
|
||||
result.Count
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress old messages by summarizing them
|
||||
/// </summary>
|
||||
private async Task<List<ChatMessage>> CompressOldMessagesAsync(
|
||||
List<ChatMessage> oldMessages,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (oldMessages.Count == 0)
|
||||
{
|
||||
return new List<ChatMessage>();
|
||||
}
|
||||
|
||||
// Group messages by role for better summarization
|
||||
var userMessages = oldMessages.Where(m => m.Role == ChatRole.User).ToList();
|
||||
var assistantMessages = oldMessages.Where(m => m.Role == ChatRole.Assistant).ToList();
|
||||
|
||||
var compressedMessages = new List<ChatMessage>();
|
||||
|
||||
// Summarize user messages if there are enough of them
|
||||
if (userMessages.Count > 1)
|
||||
{
|
||||
var userSummary = await SummarizeMessagesAsync(
|
||||
userMessages,
|
||||
"пользователь",
|
||||
cancellationToken
|
||||
);
|
||||
if (!string.IsNullOrEmpty(userSummary))
|
||||
{
|
||||
compressedMessages.Add(
|
||||
new ChatMessage
|
||||
{
|
||||
Role = ChatRole.User,
|
||||
Content = $"[Сжато: {userSummary}]",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (userMessages.Count == 1)
|
||||
{
|
||||
// Keep single user message if it's important enough
|
||||
var message = userMessages[0];
|
||||
if (ShouldKeepMessage(message))
|
||||
{
|
||||
compressedMessages.Add(CompressSingleMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
// Summarize assistant messages if there are enough of them
|
||||
if (assistantMessages.Count > 1)
|
||||
{
|
||||
var assistantSummary = await SummarizeMessagesAsync(
|
||||
assistantMessages,
|
||||
"ассистент",
|
||||
cancellationToken
|
||||
);
|
||||
if (!string.IsNullOrEmpty(assistantSummary))
|
||||
{
|
||||
compressedMessages.Add(
|
||||
new ChatMessage
|
||||
{
|
||||
Role = ChatRole.Assistant,
|
||||
Content = $"[Сжато: {assistantSummary}]",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (assistantMessages.Count == 1)
|
||||
{
|
||||
// Keep single assistant message if it's important enough
|
||||
var message = assistantMessages[0];
|
||||
if (ShouldKeepMessage(message))
|
||||
{
|
||||
compressedMessages.Add(CompressSingleMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
return compressedMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summarize a list of messages using AI
|
||||
/// </summary>
|
||||
private async Task<string> SummarizeMessagesAsync(
|
||||
List<ChatMessage> messages,
|
||||
string role,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = string.Join("\n", messages.Select(m => m.Content));
|
||||
|
||||
// If content is too short, don't summarize
|
||||
if (content.Length < _aiSettings.MinMessageLengthForSummarization)
|
||||
{
|
||||
return content.Length > _aiSettings.MaxSummarizedMessageLength
|
||||
? content.Substring(0, _aiSettings.MaxSummarizedMessageLength) + "..."
|
||||
: content;
|
||||
}
|
||||
|
||||
var summaryPrompt =
|
||||
$@"Создай краткое резюме следующих сообщений от {role} в чате.
|
||||
Сохрани ключевые моменты и важную информацию. Максимум {_aiSettings.MaxSummarizedMessageLength} символов.
|
||||
|
||||
Сообщения:
|
||||
{content}
|
||||
|
||||
Краткое резюме:";
|
||||
|
||||
var summaryMessages = new List<ChatMessage>
|
||||
{
|
||||
new ChatMessage { Role = ChatRole.User, Content = summaryPrompt },
|
||||
};
|
||||
|
||||
var summary = await GenerateSummaryAsync(summaryMessages, cancellationToken);
|
||||
|
||||
return string.IsNullOrEmpty(summary)
|
||||
? content.Substring(
|
||||
0,
|
||||
Math.Min(content.Length, _aiSettings.MaxSummarizedMessageLength)
|
||||
) + "..."
|
||||
: summary;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to summarize messages for role {Role}", role);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate summary using AI with retry logic
|
||||
/// </summary>
|
||||
private async Task<string> GenerateSummaryAsync(
|
||||
List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
const int maxRetries = 2; // Fewer retries for summarization to avoid delays
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var chatRequest = new OllamaSharp.Models.Chat.ChatRequest
|
||||
{
|
||||
Messages = messages
|
||||
.Select(m => new OllamaSharp.Models.Chat.Message(m.Role, m.Content))
|
||||
.ToList(),
|
||||
Stream = false,
|
||||
Options = new OllamaSharp.Models.RequestOptions
|
||||
{
|
||||
Temperature = 0.3f, // Lower temperature for more focused summaries
|
||||
},
|
||||
};
|
||||
|
||||
// Create timeout cancellation token for compression operations
|
||||
using var timeoutCts = new CancellationTokenSource(
|
||||
TimeSpan.FromSeconds(_aiSettings.CompressionTimeoutSeconds)
|
||||
);
|
||||
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken,
|
||||
timeoutCts.Token
|
||||
);
|
||||
|
||||
var response = new StringBuilder();
|
||||
await foreach (
|
||||
var chatResponse in _ollamaClient
|
||||
.ChatAsync(chatRequest)
|
||||
.WithCancellation(combinedCts.Token)
|
||||
)
|
||||
{
|
||||
if (chatResponse?.Message?.Content != null)
|
||||
{
|
||||
response.Append(chatResponse.Message.Content);
|
||||
}
|
||||
}
|
||||
|
||||
var result = response.ToString().Trim();
|
||||
return result.Length > _aiSettings.MaxSummarizedMessageLength
|
||||
? result.Substring(0, _aiSettings.MaxSummarizedMessageLength) + "..."
|
||||
: result;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
when (ex.InnerException is TaskCanceledException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Compression operation timed out after {TimeoutSeconds} seconds on attempt {Attempt}",
|
||||
_aiSettings.CompressionTimeoutSeconds,
|
||||
attempt
|
||||
);
|
||||
if (attempt == maxRetries)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex) when (attempt < maxRetries)
|
||||
{
|
||||
var delay = 1000 * attempt; // Simple linear backoff for summarization
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to generate AI summary on attempt {Attempt}/{MaxAttempts}. Retrying in {DelayMs}ms...",
|
||||
attempt,
|
||||
maxRetries,
|
||||
delay
|
||||
);
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to generate AI summary on attempt {Attempt}",
|
||||
attempt
|
||||
);
|
||||
if (attempt == maxRetries)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a single message should be kept (not too short, not too long)
|
||||
/// </summary>
|
||||
private bool ShouldKeepMessage(ChatMessage message)
|
||||
{
|
||||
return message.Content.Length >= _aiSettings.MinMessageLengthForSummarization / 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress a single message if it's too long
|
||||
/// </summary>
|
||||
private ChatMessage CompressSingleMessage(ChatMessage message)
|
||||
{
|
||||
if (message.Content.Length <= _aiSettings.MaxSummarizedMessageLength)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
return new ChatMessage
|
||||
{
|
||||
Role = message.Role,
|
||||
Content =
|
||||
message.Content.Substring(0, _aiSettings.MaxSummarizedMessageLength) + "...",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,13 @@ namespace ChatBot.Services.Interfaces
|
||||
List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Generate chat completion with history compression support
|
||||
/// </summary>
|
||||
Task<string> GenerateChatCompletionWithCompressionAsync(
|
||||
List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
31
ChatBot/Services/Interfaces/IHistoryCompressionService.cs
Normal file
31
ChatBot/Services/Interfaces/IHistoryCompressionService.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using ChatBot.Models.Dto;
|
||||
|
||||
namespace ChatBot.Services.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for compressing message history to reduce memory usage
|
||||
/// </summary>
|
||||
public interface IHistoryCompressionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Compress message history by summarizing old messages and removing less important ones
|
||||
/// </summary>
|
||||
/// <param name="messages">List of messages to compress</param>
|
||||
/// <param name="targetCount">Target number of messages after compression</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Compressed list of messages</returns>
|
||||
Task<List<ChatMessage>> CompressHistoryAsync(
|
||||
List<ChatMessage> messages,
|
||||
int targetCount,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Check if compression is needed based on message count and settings
|
||||
/// </summary>
|
||||
/// <param name="messageCount">Current number of messages</param>
|
||||
/// <param name="threshold">Compression threshold</param>
|
||||
/// <returns>True if compression is needed</returns>
|
||||
bool ShouldCompress(int messageCount, int threshold);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using ChatBot.Models.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ChatBot.Services.Telegram.Commands
|
||||
{
|
||||
/// <summary>
|
||||
@@ -6,8 +9,17 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
[Command("/settings", "Показать настройки чата")]
|
||||
public class SettingsCommand : TelegramCommandBase
|
||||
{
|
||||
public SettingsCommand(ChatService chatService, ModelService modelService)
|
||||
: base(chatService, modelService) { }
|
||||
private readonly AISettings _aiSettings;
|
||||
|
||||
public SettingsCommand(
|
||||
ChatService chatService,
|
||||
ModelService modelService,
|
||||
IOptions<AISettings> aiSettings
|
||||
)
|
||||
: base(chatService, modelService)
|
||||
{
|
||||
_aiSettings = aiSettings.Value;
|
||||
}
|
||||
|
||||
public override string CommandName => "/settings";
|
||||
public override string Description => "Показать настройки чата";
|
||||
@@ -25,6 +37,11 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
);
|
||||
}
|
||||
|
||||
var compressionStatus = _aiSettings.EnableHistoryCompression ? "Включено" : "Отключено";
|
||||
var compressionInfo = _aiSettings.EnableHistoryCompression
|
||||
? $"\nСжатие истории: {compressionStatus}\nПорог сжатия: {_aiSettings.CompressionThreshold} сообщений\nЦелевое количество: {_aiSettings.CompressionTarget} сообщений"
|
||||
: $"\nСжатие истории: {compressionStatus}";
|
||||
|
||||
return Task.FromResult(
|
||||
$"Настройки чата:\n"
|
||||
+ $"Тип чата: {session.ChatType}\n"
|
||||
@@ -32,6 +49,7 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
+ $"Модель: {session.Model}\n"
|
||||
+ $"Сообщений в истории: {session.GetMessageCount()}\n"
|
||||
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}"
|
||||
+ compressionInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
183
ChatBot/Services/Telegram/Commands/StatusCommand.cs
Normal file
183
ChatBot/Services/Telegram/Commands/StatusCommand.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Linq;
|
||||
using ChatBot.Models.Configuration;
|
||||
using ChatBot.Services;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ChatBot.Services.Telegram.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Команда /status - показывает статус системы и API
|
||||
/// </summary>
|
||||
[Command("/status", "Показать статус системы и API")]
|
||||
public class StatusCommand : TelegramCommandBase
|
||||
{
|
||||
private readonly AISettings _aiSettings;
|
||||
private readonly IOllamaClient _ollamaClient;
|
||||
|
||||
public StatusCommand(
|
||||
ChatService chatService,
|
||||
ModelService modelService,
|
||||
IOptions<AISettings> aiSettings,
|
||||
IOllamaClient ollamaClient
|
||||
)
|
||||
: base(chatService, modelService)
|
||||
{
|
||||
_aiSettings = aiSettings.Value;
|
||||
_ollamaClient = ollamaClient;
|
||||
}
|
||||
|
||||
public override string CommandName => "/status";
|
||||
public override string Description => "Показать статус системы и API";
|
||||
|
||||
public override async Task<string> ExecuteAsync(
|
||||
TelegramCommandContext context,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusBuilder = new System.Text.StringBuilder();
|
||||
statusBuilder.AppendLine("🔍 **Статус системы:**");
|
||||
statusBuilder.AppendLine();
|
||||
|
||||
// Информация о сессии
|
||||
var session = _chatService.GetSession(context.ChatId);
|
||||
if (session != null)
|
||||
{
|
||||
statusBuilder.AppendLine($"📊 **Сессия:**");
|
||||
statusBuilder.AppendLine($"• Сообщений в истории: {session.GetMessageCount()}");
|
||||
statusBuilder.AppendLine($"• Модель: {session.Model}");
|
||||
statusBuilder.AppendLine($"• Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}");
|
||||
statusBuilder.AppendLine();
|
||||
}
|
||||
|
||||
// Настройки сжатия
|
||||
statusBuilder.AppendLine($"🗜️ **Сжатие истории:**");
|
||||
statusBuilder.AppendLine(
|
||||
$"• Статус: {(session?.GetMessageCount() > _aiSettings.CompressionThreshold ? "Активно" : "Не требуется")}"
|
||||
);
|
||||
statusBuilder.AppendLine($"• Порог: {_aiSettings.CompressionThreshold} сообщений");
|
||||
statusBuilder.AppendLine(
|
||||
$"• Целевое количество: {_aiSettings.CompressionTarget} сообщений"
|
||||
);
|
||||
statusBuilder.AppendLine();
|
||||
|
||||
// Настройки retry
|
||||
statusBuilder.AppendLine($"🔄 **Повторные попытки:**");
|
||||
statusBuilder.AppendLine($"• Максимум попыток: {_aiSettings.MaxRetryAttempts}");
|
||||
statusBuilder.AppendLine($"• Базовая задержка: {_aiSettings.RetryDelayMs}мс");
|
||||
statusBuilder.AppendLine(
|
||||
$"• Экспоненциальный backoff: {(_aiSettings.EnableExponentialBackoff ? "Включен" : "Отключен")}"
|
||||
);
|
||||
statusBuilder.AppendLine(
|
||||
$"• Максимальная задержка: {_aiSettings.MaxRetryDelayMs}мс"
|
||||
);
|
||||
statusBuilder.AppendLine();
|
||||
|
||||
// Проверка API
|
||||
statusBuilder.AppendLine($"🌐 **API статус:**");
|
||||
try
|
||||
{
|
||||
var currentModel = _modelService.GetCurrentModel();
|
||||
statusBuilder.AppendLine($"• Модель: {currentModel}");
|
||||
|
||||
// Простая проверка доступности API
|
||||
var testRequest = new OllamaSharp.Models.Chat.ChatRequest
|
||||
{
|
||||
Messages = new List<OllamaSharp.Models.Chat.Message>
|
||||
{
|
||||
new OllamaSharp.Models.Chat.Message(
|
||||
OllamaSharp.Models.Chat.ChatRole.User,
|
||||
"test"
|
||||
),
|
||||
},
|
||||
Stream = false,
|
||||
Options = new OllamaSharp.Models.RequestOptions { Temperature = 0.1f },
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource(
|
||||
TimeSpan.FromSeconds(_aiSettings.StatusCheckTimeoutSeconds)
|
||||
);
|
||||
var hasResponse = false;
|
||||
|
||||
await foreach (
|
||||
var chatResponse in _ollamaClient
|
||||
.ChatAsync(testRequest)
|
||||
.WithCancellation(cts.Token)
|
||||
)
|
||||
{
|
||||
if (chatResponse?.Message?.Content != null)
|
||||
{
|
||||
hasResponse = true;
|
||||
break; // Получили ответ, выходим из цикла
|
||||
}
|
||||
}
|
||||
|
||||
if (hasResponse)
|
||||
{
|
||||
statusBuilder.AppendLine("• Статус: ✅ Доступен");
|
||||
}
|
||||
else
|
||||
{
|
||||
statusBuilder.AppendLine("• Статус: ⚠️ Нет ответа");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
var statusCode = GetHttpStatusCode(ex);
|
||||
statusBuilder.AppendLine($"• Статус: ❌ Ошибка HTTP {statusCode}");
|
||||
statusBuilder.AppendLine($"• Описание: {GetStatusDescription(statusCode)}");
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
statusBuilder.AppendLine("• Статус: ⏰ Таймаут");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
statusBuilder.AppendLine($"• Статус: ❌ Ошибка: {ex.Message}");
|
||||
}
|
||||
|
||||
return statusBuilder.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"❌ Ошибка при получении статуса: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private static int? GetHttpStatusCode(HttpRequestException ex)
|
||||
{
|
||||
if (ex.Data.Contains("StatusCode") && ex.Data["StatusCode"] is int statusCode)
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
var message = ex.Message;
|
||||
if (message.Contains("502"))
|
||||
return 502;
|
||||
if (message.Contains("503"))
|
||||
return 503;
|
||||
if (message.Contains("504"))
|
||||
return 504;
|
||||
if (message.Contains("500"))
|
||||
return 500;
|
||||
if (message.Contains("429"))
|
||||
return 429;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetStatusDescription(int? statusCode)
|
||||
{
|
||||
return statusCode switch
|
||||
{
|
||||
502 => "Bad Gateway - сервер перегружен",
|
||||
503 => "Service Unavailable - сервис недоступен",
|
||||
504 => "Gateway Timeout - превышено время ожидания",
|
||||
429 => "Too Many Requests - слишком много запросов",
|
||||
500 => "Internal Server Error - внутренняя ошибка сервера",
|
||||
_ => "Неизвестная ошибка",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,15 @@
|
||||
"SystemPromptPath": "Prompts/system-prompt.txt",
|
||||
"MaxRetryAttempts": 3,
|
||||
"RetryDelayMs": 1000,
|
||||
"RequestTimeoutSeconds": 60
|
||||
"RequestTimeoutSeconds": 180,
|
||||
"EnableHistoryCompression": true,
|
||||
"CompressionThreshold": 20,
|
||||
"CompressionTarget": 10,
|
||||
"MinMessageLengthForSummarization": 50,
|
||||
"MaxSummarizedMessageLength": 200,
|
||||
"EnableExponentialBackoff": true,
|
||||
"MaxRetryDelayMs": 30000,
|
||||
"CompressionTimeoutSeconds": 30,
|
||||
"StatusCheckTimeoutSeconds": 10
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user