Add promt fix tests
All checks were successful
SonarQube / Build and analyze (push) Successful in 2m54s
All checks were successful
SonarQube / Build and analyze (push) Successful in 2m54s
This commit is contained in:
@@ -10,6 +10,7 @@ namespace ChatBot.Models
|
||||
public class ChatSession
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly List<ChatMessage> _messageHistory = new List<ChatMessage>();
|
||||
private IHistoryCompressionService? _compressionService;
|
||||
|
||||
@@ -110,35 +111,41 @@ namespace ChatBot.Models
|
||||
int compressionTarget
|
||||
)
|
||||
{
|
||||
lock (_lock)
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
_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);
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
else if (_messageHistory.Count > MaxHistoryLength)
|
||||
finally
|
||||
{
|
||||
// Fallback to simple trimming if compression is not available
|
||||
await TrimHistoryAsync();
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress message history using the compression service
|
||||
/// Note: This method should be called within a semaphore lock
|
||||
/// </summary>
|
||||
private async Task CompressHistoryAsync(int targetCount)
|
||||
{
|
||||
if (_compressionService == null)
|
||||
{
|
||||
await TrimHistoryAsync();
|
||||
TrimHistoryInternal();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,50 +156,52 @@ namespace ChatBot.Models
|
||||
targetCount
|
||||
);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_messageHistory.Clear();
|
||||
_messageHistory.AddRange(compressedMessages);
|
||||
LastUpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
_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();
|
||||
TrimHistoryInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple history trimming without compression
|
||||
/// Simple history trimming without compression (async wrapper)
|
||||
/// Note: This method should be called within a semaphore lock
|
||||
/// </summary>
|
||||
private async Task TrimHistoryAsync()
|
||||
private 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();
|
||||
TrimHistoryInternal();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_messageHistory.Clear();
|
||||
if (systemMessage != null)
|
||||
{
|
||||
_messageHistory.Add(systemMessage);
|
||||
}
|
||||
_messageHistory.AddRange(recentMessages);
|
||||
LastUpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
/// <summary>
|
||||
/// Internal method to trim history without async overhead
|
||||
/// Note: This method should be called within a semaphore lock
|
||||
/// </summary>
|
||||
private void TrimHistoryInternal()
|
||||
{
|
||||
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>
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace ChatBot.Services
|
||||
/// <summary>
|
||||
/// Get or create a chat session for the given chat ID
|
||||
/// </summary>
|
||||
public ChatSession GetOrCreateSession(
|
||||
public async Task<ChatSession> GetOrCreateSessionAsync(
|
||||
long chatId,
|
||||
string chatType = ChatTypes.Private,
|
||||
string chatTitle = ""
|
||||
)
|
||||
{
|
||||
var session = _sessionStorage.GetOrCreate(chatId, chatType, chatTitle);
|
||||
var session = await _sessionStorage.GetOrCreateAsync(chatId, chatType, chatTitle);
|
||||
|
||||
// Set compression service if compression is enabled
|
||||
if (_aiSettings.EnableHistoryCompression)
|
||||
@@ -66,7 +66,7 @@ namespace ChatBot.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = GetOrCreateSession(chatId, chatType, chatTitle);
|
||||
var session = await GetOrCreateSessionAsync(chatId, chatType, chatTitle);
|
||||
|
||||
// Add user message to history with username
|
||||
if (_aiSettings.EnableHistoryCompression)
|
||||
@@ -157,7 +157,7 @@ namespace ChatBot.Services
|
||||
/// </summary>
|
||||
public async Task UpdateSessionParametersAsync(long chatId, string? model = null)
|
||||
{
|
||||
var session = _sessionStorage.Get(chatId);
|
||||
var session = await _sessionStorage.GetAsync(chatId);
|
||||
if (session != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(model))
|
||||
@@ -177,7 +177,7 @@ namespace ChatBot.Services
|
||||
/// </summary>
|
||||
public virtual async Task ClearHistoryAsync(long chatId)
|
||||
{
|
||||
var session = _sessionStorage.Get(chatId);
|
||||
var session = await _sessionStorage.GetAsync(chatId);
|
||||
if (session != null)
|
||||
{
|
||||
session.ClearHistory();
|
||||
@@ -192,33 +192,33 @@ namespace ChatBot.Services
|
||||
/// <summary>
|
||||
/// Get session information
|
||||
/// </summary>
|
||||
public virtual ChatSession? GetSession(long chatId)
|
||||
public virtual async Task<ChatSession?> GetSessionAsync(long chatId)
|
||||
{
|
||||
return _sessionStorage.Get(chatId);
|
||||
return await _sessionStorage.GetAsync(chatId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a session
|
||||
/// </summary>
|
||||
public bool RemoveSession(long chatId)
|
||||
public async Task<bool> RemoveSessionAsync(long chatId)
|
||||
{
|
||||
return _sessionStorage.Remove(chatId);
|
||||
return await _sessionStorage.RemoveAsync(chatId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all active sessions count
|
||||
/// </summary>
|
||||
public int GetActiveSessionsCount()
|
||||
public async Task<int> GetActiveSessionsCountAsync()
|
||||
{
|
||||
return _sessionStorage.GetActiveSessionsCount();
|
||||
return await _sessionStorage.GetActiveSessionsCountAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up old sessions (older than specified hours)
|
||||
/// </summary>
|
||||
public int CleanupOldSessions(int hoursOld = 24)
|
||||
public async Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
|
||||
{
|
||||
return _sessionStorage.CleanupOldSessions(hoursOld);
|
||||
return await _sessionStorage.CleanupOldSessionsAsync(hoursOld);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ChatBot.Data;
|
||||
using ChatBot.Data.Interfaces;
|
||||
using ChatBot.Models;
|
||||
using ChatBot.Models.Dto;
|
||||
@@ -15,19 +16,22 @@ namespace ChatBot.Services
|
||||
private readonly IChatSessionRepository _repository;
|
||||
private readonly ILogger<DatabaseSessionStorage> _logger;
|
||||
private readonly IHistoryCompressionService? _compressionService;
|
||||
private readonly ChatBotDbContext _context;
|
||||
|
||||
public DatabaseSessionStorage(
|
||||
IChatSessionRepository repository,
|
||||
ILogger<DatabaseSessionStorage> logger,
|
||||
ChatBotDbContext context,
|
||||
IHistoryCompressionService? compressionService = null
|
||||
)
|
||||
{
|
||||
_repository = repository;
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
_compressionService = compressionService;
|
||||
}
|
||||
|
||||
public ChatSession GetOrCreate(
|
||||
public async Task<ChatSession> GetOrCreateAsync(
|
||||
long chatId,
|
||||
string chatType = "private",
|
||||
string chatTitle = ""
|
||||
@@ -35,10 +39,7 @@ namespace ChatBot.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var sessionEntity = _repository
|
||||
.GetOrCreateAsync(chatId, chatType, chatTitle)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
var sessionEntity = await _repository.GetOrCreateAsync(chatId, chatType, chatTitle);
|
||||
return ConvertToChatSession(sessionEntity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -51,11 +52,11 @@ namespace ChatBot.Services
|
||||
}
|
||||
}
|
||||
|
||||
public ChatSession? Get(long chatId)
|
||||
public async Task<ChatSession?> GetAsync(long chatId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sessionEntity = _repository.GetByChatIdAsync(chatId).GetAwaiter().GetResult();
|
||||
var sessionEntity = await _repository.GetByChatIdAsync(chatId);
|
||||
return sessionEntity != null ? ConvertToChatSession(sessionEntity) : null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -65,11 +66,11 @@ namespace ChatBot.Services
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(long chatId)
|
||||
public async Task<bool> RemoveAsync(long chatId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _repository.DeleteAsync(chatId).GetAwaiter().GetResult();
|
||||
return await _repository.DeleteAsync(chatId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -78,11 +79,11 @@ namespace ChatBot.Services
|
||||
}
|
||||
}
|
||||
|
||||
public int GetActiveSessionsCount()
|
||||
public async Task<int> GetActiveSessionsCountAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _repository.GetActiveSessionsCountAsync().GetAwaiter().GetResult();
|
||||
return await _repository.GetActiveSessionsCountAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -91,11 +92,11 @@ namespace ChatBot.Services
|
||||
}
|
||||
}
|
||||
|
||||
public int CleanupOldSessions(int hoursOld = 24)
|
||||
public async Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _repository.CleanupOldSessionsAsync(hoursOld).GetAwaiter().GetResult();
|
||||
return await _repository.CleanupOldSessionsAsync(hoursOld);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -105,10 +106,11 @@ namespace ChatBot.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save session changes to database
|
||||
/// Save session changes to database with transaction support
|
||||
/// </summary>
|
||||
public async Task SaveSessionAsync(ChatSession session)
|
||||
{
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var sessionEntity = await _repository.GetByChatIdAsync(session.ChatId);
|
||||
@@ -126,7 +128,7 @@ namespace ChatBot.Services
|
||||
sessionEntity.MaxHistoryLength = session.MaxHistoryLength;
|
||||
sessionEntity.LastUpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Clear existing messages and add new ones
|
||||
// Clear existing messages and add new ones in a transaction
|
||||
await _repository.ClearMessagesAsync(sessionEntity.Id);
|
||||
|
||||
var messages = session.GetAllMessages();
|
||||
@@ -141,9 +143,14 @@ namespace ChatBot.Services
|
||||
}
|
||||
|
||||
await _repository.UpdateAsync(sessionEntity);
|
||||
|
||||
// Commit transaction if all operations succeeded
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Transaction will be automatically rolled back on exception
|
||||
await transaction.RollbackAsync();
|
||||
_logger.LogError(ex, "Failed to save session for chat {ChatId}", session.ChatId);
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to save session for chat {session.ChatId}",
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace ChatBot.Services
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ChatSession GetOrCreate(
|
||||
public Task<ChatSession> GetOrCreateAsync(
|
||||
long chatId,
|
||||
string chatType = "private",
|
||||
string chatTitle = ""
|
||||
@@ -54,31 +54,31 @@ namespace ChatBot.Services
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
return Task.FromResult(session);
|
||||
}
|
||||
|
||||
public ChatSession? Get(long chatId)
|
||||
public Task<ChatSession?> GetAsync(long chatId)
|
||||
{
|
||||
_sessions.TryGetValue(chatId, out var session);
|
||||
return session;
|
||||
return Task.FromResult(session);
|
||||
}
|
||||
|
||||
public bool Remove(long chatId)
|
||||
public Task<bool> RemoveAsync(long chatId)
|
||||
{
|
||||
var removed = _sessions.TryRemove(chatId, out _);
|
||||
if (removed)
|
||||
{
|
||||
_logger.LogInformation("Removed session for chat {ChatId}", chatId);
|
||||
}
|
||||
return removed;
|
||||
return Task.FromResult(removed);
|
||||
}
|
||||
|
||||
public int GetActiveSessionsCount()
|
||||
public Task<int> GetActiveSessionsCountAsync()
|
||||
{
|
||||
return _sessions.Count;
|
||||
return Task.FromResult(_sessions.Count);
|
||||
}
|
||||
|
||||
public int CleanupOldSessions(int hoursOld = 24)
|
||||
public Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
|
||||
{
|
||||
var cutoffTime = DateTime.UtcNow.AddHours(-hoursOld);
|
||||
var sessionsToRemove = _sessions
|
||||
@@ -97,7 +97,7 @@ namespace ChatBot.Services
|
||||
}
|
||||
|
||||
_logger.LogInformation("Cleaned up {DeletedCount} old sessions", deletedCount);
|
||||
return deletedCount;
|
||||
return Task.FromResult(deletedCount);
|
||||
}
|
||||
|
||||
public Task SaveSessionAsync(ChatSession session)
|
||||
|
||||
@@ -10,27 +10,27 @@ namespace ChatBot.Services.Interfaces
|
||||
/// <summary>
|
||||
/// Get or create a chat session
|
||||
/// </summary>
|
||||
ChatSession GetOrCreate(long chatId, string chatType = "private", string chatTitle = "");
|
||||
Task<ChatSession> GetOrCreateAsync(long chatId, string chatType = "private", string chatTitle = "");
|
||||
|
||||
/// <summary>
|
||||
/// Get a session by chat ID
|
||||
/// </summary>
|
||||
ChatSession? Get(long chatId);
|
||||
Task<ChatSession?> GetAsync(long chatId);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a session
|
||||
/// </summary>
|
||||
bool Remove(long chatId);
|
||||
Task<bool> RemoveAsync(long chatId);
|
||||
|
||||
/// <summary>
|
||||
/// Get count of active sessions
|
||||
/// </summary>
|
||||
int GetActiveSessionsCount();
|
||||
Task<int> GetActiveSessionsCountAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Clean up old sessions
|
||||
/// </summary>
|
||||
int CleanupOldSessions(int hoursOld = 24);
|
||||
Task<int> CleanupOldSessionsAsync(int hoursOld = 24);
|
||||
|
||||
/// <summary>
|
||||
/// Save session changes to storage (for database implementations)
|
||||
|
||||
@@ -24,17 +24,15 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
public override string CommandName => "/settings";
|
||||
public override string Description => "Показать настройки чата";
|
||||
|
||||
public override Task<string> ExecuteAsync(
|
||||
public override async Task<string> ExecuteAsync(
|
||||
TelegramCommandContext context,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var session = _chatService.GetSession(context.ChatId);
|
||||
var session = await _chatService.GetSessionAsync(context.ChatId);
|
||||
if (session == null)
|
||||
{
|
||||
return Task.FromResult(
|
||||
"Сессия не найдена. Отправьте любое сообщение для создания новой сессии."
|
||||
);
|
||||
return "Сессия не найдена. Отправьте любое сообщение для создания новой сессии.";
|
||||
}
|
||||
|
||||
var compressionStatus = _aiSettings.EnableHistoryCompression ? "Включено" : "Отключено";
|
||||
@@ -42,15 +40,13 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
? $"\nСжатие истории: {compressionStatus}\nПорог сжатия: {_aiSettings.CompressionThreshold} сообщений\nЦелевое количество: {_aiSettings.CompressionTarget} сообщений"
|
||||
: $"\nСжатие истории: {compressionStatus}";
|
||||
|
||||
return Task.FromResult(
|
||||
$"Настройки чата:\n"
|
||||
+ $"Тип чата: {session.ChatType}\n"
|
||||
+ $"Название: {session.ChatTitle}\n"
|
||||
+ $"Модель: {session.Model}\n"
|
||||
+ $"Сообщений в истории: {session.GetMessageCount()}\n"
|
||||
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}"
|
||||
+ compressionInfo
|
||||
);
|
||||
return $"Настройки чата:\n"
|
||||
+ $"Тип чата: {session.ChatType}\n"
|
||||
+ $"Название: {session.ChatTitle}\n"
|
||||
+ $"Модель: {session.Model}\n"
|
||||
+ $"Сообщений в истории: {session.GetMessageCount()}\n"
|
||||
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}"
|
||||
+ compressionInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
statusBuilder.AppendLine();
|
||||
|
||||
// Информация о сессии
|
||||
var session = _chatService.GetSession(context.ChatId);
|
||||
var session = await _chatService.GetSessionAsync(context.ChatId);
|
||||
if (session != null)
|
||||
{
|
||||
statusBuilder.AppendLine($"📊 **Сессия:**");
|
||||
|
||||
@@ -39,6 +39,23 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Input validation
|
||||
if (string.IsNullOrWhiteSpace(messageText))
|
||||
{
|
||||
_logger.LogWarning("Empty message received for chat {ChatId}", chatId);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (chatId == 0)
|
||||
{
|
||||
_logger.LogError("Invalid chatId (0) received");
|
||||
throw new ArgumentException("ChatId cannot be 0", nameof(chatId));
|
||||
}
|
||||
|
||||
username = username ?? "Unknown";
|
||||
chatType = chatType ?? "private";
|
||||
chatTitle = chatTitle ?? string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// Получаем информацию о боте
|
||||
@@ -121,10 +138,25 @@ namespace ChatBot.Services.Telegram.Commands
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Network error processing message for chat {ChatId}", chatId);
|
||||
return "Ошибка сети при обработке сообщения. Проверьте подключение к AI сервису.";
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Request timeout for chat {ChatId}", chatId);
|
||||
return "Превышено время ожидания ответа. Попробуйте еще раз.";
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Invalid operation for chat {ChatId}", chatId);
|
||||
return "Ошибка в работе системы. Попробуйте позже.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing message for chat {ChatId}", chatId);
|
||||
return "Произошла ошибка при обработке сообщения. Попробуйте еще раз.";
|
||||
_logger.LogError(ex, "Unexpected error processing message for chat {ChatId}", chatId);
|
||||
return "Произошла непредвиденная ошибка. Попробуйте еще раз.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user