This commit is contained in:
Leonid Pershin
2025-10-16 08:08:17 +03:00
parent bf1d1c0770
commit e46013b70b
7 changed files with 113 additions and 56 deletions

View File

@@ -7,6 +7,9 @@ namespace ChatBot.Models
/// </summary> /// </summary>
public class ChatSession public class ChatSession
{ {
private readonly object _lock = new object();
private readonly List<ChatMessage> _messageHistory = new List<ChatMessage>();
/// <summary> /// <summary>
/// Unique identifier for the chat session /// Unique identifier for the chat session
/// </summary> /// </summary>
@@ -27,11 +30,6 @@ namespace ChatBot.Models
/// </summary> /// </summary>
public string ChatTitle { get; set; } = string.Empty; public string ChatTitle { get; set; } = string.Empty;
/// <summary>
/// History of messages in this chat session
/// </summary>
public List<ChatMessage> MessageHistory { get; set; } = new List<ChatMessage>();
/// <summary> /// <summary>
/// AI model to use for this session /// AI model to use for this session
/// </summary> /// </summary>
@@ -53,29 +51,32 @@ namespace ChatBot.Models
public int MaxHistoryLength { get; set; } = 20; public int MaxHistoryLength { get; set; } = 20;
/// <summary> /// <summary>
/// Add a message to the history and manage history length /// Add a message to the history and manage history length (thread-safe)
/// </summary> /// </summary>
public void AddMessage(ChatMessage message) public void AddMessage(ChatMessage message)
{ {
MessageHistory.Add(message); lock (_lock)
{
_messageHistory.Add(message);
LastUpdatedAt = DateTime.UtcNow; LastUpdatedAt = DateTime.UtcNow;
// Trim history if it exceeds max length // Trim history if it exceeds max length
if (MessageHistory.Count > MaxHistoryLength) if (_messageHistory.Count > MaxHistoryLength)
{ {
// Keep system message if it exists, then keep the most recent messages // Keep system message if it exists, then keep the most recent messages
var systemMessage = MessageHistory.FirstOrDefault(m => m.Role == "system"); var systemMessage = _messageHistory.FirstOrDefault(m => m.Role == "system");
var recentMessages = MessageHistory var recentMessages = _messageHistory
.Where(m => m.Role != "system") .Where(m => m.Role != "system")
.TakeLast(MaxHistoryLength - (systemMessage != null ? 1 : 0)) .TakeLast(MaxHistoryLength - (systemMessage != null ? 1 : 0))
.ToList(); .ToList();
MessageHistory.Clear(); _messageHistory.Clear();
if (systemMessage != null) if (systemMessage != null)
{ {
MessageHistory.Add(systemMessage); _messageHistory.Add(systemMessage);
}
_messageHistory.AddRange(recentMessages);
} }
MessageHistory.AddRange(recentMessages);
} }
} }
@@ -102,20 +103,37 @@ namespace ChatBot.Models
} }
/// <summary> /// <summary>
/// Get all messages /// Get all messages (thread-safe)
/// </summary> /// </summary>
public List<ChatMessage> GetAllMessages() public List<ChatMessage> GetAllMessages()
{ {
return new List<ChatMessage>(MessageHistory); lock (_lock)
{
return new List<ChatMessage>(_messageHistory);
}
} }
/// <summary> /// <summary>
/// Clear message history /// Get the count of messages in history (thread-safe)
/// </summary>
public int GetMessageCount()
{
lock (_lock)
{
return _messageHistory.Count;
}
}
/// <summary>
/// Clear message history (thread-safe)
/// </summary> /// </summary>
public void ClearHistory() public void ClearHistory()
{ {
MessageHistory.Clear(); lock (_lock)
{
_messageHistory.Clear();
LastUpdatedAt = DateTime.UtcNow; LastUpdatedAt = DateTime.UtcNow;
} }
} }
} }
}

View File

@@ -14,10 +14,5 @@ namespace ChatBot.Models.Configuration
/// Название модели по умолчанию /// Название модели по умолчанию
/// </summary> /// </summary>
public string DefaultModel { get; set; } = "llama3"; public string DefaultModel { get; set; } = "llama3";
/// <summary>
/// Максимальное количество повторных попыток при ошибках
/// </summary>
public int MaxRetries { get; set; } = 3;
} }
} }

View File

@@ -12,7 +12,6 @@ namespace ChatBot.Models.Configuration.Validators
var errors = new List<string>(); var errors = new List<string>();
ValidateUrl(options, errors); ValidateUrl(options, errors);
ValidateRetryAndTokenSettings(options, errors);
ValidateDefaultModel(options, errors); ValidateDefaultModel(options, errors);
return errors.Count > 0 return errors.Count > 0
@@ -28,18 +27,6 @@ namespace ChatBot.Models.Configuration.Validators
errors.Add($"Invalid Ollama URL format: {options.Url}"); errors.Add($"Invalid Ollama URL format: {options.Url}");
} }
private static void ValidateRetryAndTokenSettings(
OllamaSettings options,
List<string> errors
)
{
if (options.MaxRetries < 1)
errors.Add($"MaxRetries must be at least 1, got: {options.MaxRetries}");
if (options.MaxRetries > 10)
errors.Add($"MaxRetries should not exceed 10, got: {options.MaxRetries}");
}
private static void ValidateDefaultModel(OllamaSettings options, List<string> errors) private static void ValidateDefaultModel(OllamaSettings options, List<string> errors)
{ {
if (string.IsNullOrWhiteSpace(options.DefaultModel)) if (string.IsNullOrWhiteSpace(options.DefaultModel))

View File

@@ -68,8 +68,13 @@ try
builder.Services.AddSingleton<BotInfoService>(); builder.Services.AddSingleton<BotInfoService>();
builder.Services.AddSingleton<ITelegramCommandProcessor, TelegramCommandProcessor>(); builder.Services.AddSingleton<ITelegramCommandProcessor, TelegramCommandProcessor>();
builder.Services.AddSingleton<ITelegramMessageHandler, TelegramMessageHandler>(); builder.Services.AddSingleton<ITelegramMessageHandler, TelegramMessageHandler>();
builder.Services.AddSingleton<ITelegramBotService, TelegramBotService>();
builder.Services.AddHostedService<TelegramBotService>(); // Регистрируем TelegramBotService как singleton и используем один экземпляр для интерфейса и HostedService
builder.Services.AddSingleton<TelegramBotService>();
builder.Services.AddSingleton<ITelegramBotService>(sp =>
sp.GetRequiredService<TelegramBotService>()
);
builder.Services.AddHostedService(sp => sp.GetRequiredService<TelegramBotService>());
// Регистрируем Health Checks // Регистрируем Health Checks
builder builder

View File

@@ -30,7 +30,7 @@ namespace ChatBot.Services.Telegram.Commands
+ $"Тип чата: {session.ChatType}\n" + $"Тип чата: {session.ChatType}\n"
+ $"Название: {session.ChatTitle}\n" + $"Название: {session.ChatTitle}\n"
+ $"Модель: {session.Model}\n" + $"Модель: {session.Model}\n"
+ $"Сообщений в истории: {session.MessageHistory.Count}\n" + $"Сообщений в истории: {session.GetMessageCount()}\n"
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}" + $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}"
); );
} }

View File

@@ -4,7 +4,7 @@ using Telegram.Bot.Types;
namespace ChatBot.Services.Telegram.Services namespace ChatBot.Services.Telegram.Services
{ {
/// <summary> /// <summary>
/// Сервис для получения информации о боте /// Сервис для получения информации о боте с улучшенным кэшированием
/// </summary> /// </summary>
public class BotInfoService public class BotInfoService
{ {
@@ -12,6 +12,8 @@ namespace ChatBot.Services.Telegram.Services
private readonly ILogger<BotInfoService> _logger; private readonly ILogger<BotInfoService> _logger;
private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly SemaphoreSlim _semaphore = new(1, 1);
private User? _cachedBotInfo; private User? _cachedBotInfo;
private DateTime? _cacheExpirationTime;
private readonly TimeSpan _cacheLifetime = TimeSpan.FromHours(1);
public BotInfoService(ITelegramBotClient botClient, ILogger<BotInfoService> logger) public BotInfoService(ITelegramBotClient botClient, ILogger<BotInfoService> logger)
{ {
@@ -20,32 +22,60 @@ namespace ChatBot.Services.Telegram.Services
} }
/// <summary> /// <summary>
/// Получает информацию о боте (с кешированием) /// Получает информацию о боте (с кэшированием и автоматической инвалидацией)
/// </summary> /// </summary>
public async Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default) public async Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default)
{ {
if (_cachedBotInfo != null) // Проверяем, есть ли валидный кэш
if (
_cachedBotInfo != null
&& _cacheExpirationTime.HasValue
&& DateTime.UtcNow < _cacheExpirationTime.Value
)
{
return _cachedBotInfo; return _cachedBotInfo;
}
await _semaphore.WaitAsync(cancellationToken); await _semaphore.WaitAsync(cancellationToken);
try try
{ {
if (_cachedBotInfo != null) // Double-check после получения блокировки
if (
_cachedBotInfo != null
&& _cacheExpirationTime.HasValue
&& DateTime.UtcNow < _cacheExpirationTime.Value
)
{
return _cachedBotInfo; return _cachedBotInfo;
}
_logger.LogDebug("Fetching bot info from Telegram API");
_cachedBotInfo = await _botClient.GetMe(cancellationToken: cancellationToken); _cachedBotInfo = await _botClient.GetMe(cancellationToken: cancellationToken);
_cacheExpirationTime = DateTime.UtcNow.Add(_cacheLifetime);
_logger.LogInformation( _logger.LogInformation(
"Bot info loaded: @{BotUsername} (ID: {BotId})", "Bot info loaded and cached: @{BotUsername} (ID: {BotId}). Cache expires at {ExpirationTime}",
_cachedBotInfo.Username, _cachedBotInfo.Username,
_cachedBotInfo.Id _cachedBotInfo.Id,
_cacheExpirationTime
); );
return _cachedBotInfo; return _cachedBotInfo;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Failed to get bot info"); _logger.LogError(ex, "Failed to get bot info from Telegram API");
// Если есть устаревший кэш, используем его
if (_cachedBotInfo != null)
{
_logger.LogWarning(
"Using stale cached bot info due to API error: @{BotUsername}",
_cachedBotInfo.Username
);
return _cachedBotInfo;
}
return null; return null;
} }
finally finally
@@ -53,5 +83,28 @@ namespace ChatBot.Services.Telegram.Services
_semaphore.Release(); _semaphore.Release();
} }
} }
/// <summary>
/// Принудительно инвалидирует кэш информации о боте
/// </summary>
public void InvalidateCache()
{
lock (_semaphore)
{
_cachedBotInfo = null;
_cacheExpirationTime = null;
_logger.LogInformation("Bot info cache invalidated");
}
}
/// <summary>
/// Проверяет, есть ли валидная информация о боте в кэше
/// </summary>
public bool IsCacheValid()
{
return _cachedBotInfo != null
&& _cacheExpirationTime.HasValue
&& DateTime.UtcNow < _cacheExpirationTime.Value;
}
} }
} }

View File

@@ -33,7 +33,6 @@
}, },
"Ollama": { "Ollama": {
"Url": "http://10.10.1.202:11434", "Url": "http://10.10.1.202:11434",
"DefaultModel": "llama3", "DefaultModel": "llama3chat"
"MaxRetries": 3
} }
} }