diff --git a/ChatBot/ChatBot.csproj b/ChatBot/ChatBot.csproj index bdf7edf..7846212 100644 --- a/ChatBot/ChatBot.csproj +++ b/ChatBot/ChatBot.csproj @@ -17,8 +17,6 @@ - - diff --git a/ChatBot/Common/Constants/RetryConstants.cs b/ChatBot/Common/Constants/RetryConstants.cs deleted file mode 100644 index bb8272d..0000000 --- a/ChatBot/Common/Constants/RetryConstants.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ChatBot.Common.Constants -{ - /// - /// Constants for retry logic - /// - public static class RetryConstants - { - public const int DefaultMaxRetries = 3; - public const int DefaultBaseDelaySeconds = 1; - public const int DefaultMaxJitterMs = 2000; - } -} diff --git a/ChatBot/Common/Results/Result.cs b/ChatBot/Common/Results/Result.cs deleted file mode 100644 index 4f498f8..0000000 --- a/ChatBot/Common/Results/Result.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace ChatBot.Common.Results -{ - /// - /// Represents the result of an operation that can succeed or fail - /// - public class Result - { - public bool IsSuccess { get; } - public string Error { get; } - - protected Result(bool isSuccess, string error) - { - IsSuccess = isSuccess; - Error = error; - } - - public static Result Success() => new(true, string.Empty); - - public static Result Failure(string error) => new(false, error); - } - - /// - /// Represents the result of an operation that returns a value - /// - public class Result : Result - { - public T? Value { get; } - - private Result(T? value, bool isSuccess, string error) - : base(isSuccess, error) - { - Value = value; - } - - public static Result Success(T value) => new(value, true, string.Empty); - - public static new Result Failure(string error) => new(default, false, error); - } -} diff --git a/ChatBot/Models/Configuration/AppSettings.cs b/ChatBot/Models/Configuration/AppSettings.cs deleted file mode 100644 index da1af0a..0000000 --- a/ChatBot/Models/Configuration/AppSettings.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace ChatBot.Models.Configuration -{ - /// - /// Основные настройки приложения - /// - public class AppSettings - { - /// - /// Настройки Telegram бота - /// - public TelegramBotSettings TelegramBot { get; set; } = new(); - - /// - /// Настройки Ollama API - /// - public OllamaSettings Ollama { get; set; } = new(); - - /// - /// Настройки логирования Serilog - /// - public SerilogSettings Serilog { get; set; } = new(); - } -} diff --git a/ChatBot/Models/Configuration/SerilogSettings.cs b/ChatBot/Models/Configuration/SerilogSettings.cs deleted file mode 100644 index a57b062..0000000 --- a/ChatBot/Models/Configuration/SerilogSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace ChatBot.Models.Configuration -{ - /// - /// Настройки логирования Serilog - /// - public class SerilogSettings - { - /// - /// Список используемых sink'ов для логирования - /// - public List Using { get; set; } = new(); - - /// - /// Настройки минимального уровня логирования - /// - public MinimumLevelSettings MinimumLevel { get; set; } = new(); - - /// - /// Настройки получателей логов (куда писать логи) - /// - public List WriteTo { get; set; } = new(); - - /// - /// Список обогатителей логов (дополнительная информация) - /// - public List Enrich { get; set; } = new(); - } - - /// - /// Настройки минимального уровня логирования - /// - public class MinimumLevelSettings - { - /// - /// Уровень логирования по умолчанию - /// - public string Default { get; set; } = "Information"; - - /// - /// Переопределения уровня логирования для конкретных пространств имен - /// - public Dictionary Override { get; set; } = new(); - } - - /// - /// Настройки получателя логов - /// - public class WriteToSettings - { - /// - /// Название sink'а для записи логов - /// - public string Name { get; set; } = string.Empty; - - /// - /// Аргументы для настройки sink'а - /// - public Dictionary Args { get; set; } = new(); - } -} diff --git a/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs b/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs deleted file mode 100644 index ad48ee0..0000000 --- a/ChatBot/Models/Configuration/Validators/ConfigurationValidator.cs +++ /dev/null @@ -1,129 +0,0 @@ -namespace ChatBot.Models.Configuration.Validators -{ - /// - /// Валидатор конфигурации приложения - /// - public static class ConfigurationValidator - { - /// - /// Валидирует все настройки приложения - /// - /// Настройки приложения - /// Результат валидации - public static ValidationResult ValidateAppSettings(AppSettings settings) - { - var errors = new List(); - - // Валидация настроек Telegram бота - var telegramResult = ValidateTelegramBotSettings(settings.TelegramBot); - errors.AddRange(telegramResult.Errors); - - // Валидация настроек Ollama - var ollamaResult = ValidateOllamaSettings(settings.Ollama); - errors.AddRange(ollamaResult.Errors); - - return new ValidationResult { IsValid = !errors.Any(), Errors = errors }; - } - - /// - /// Валидирует настройки Telegram бота - /// - /// Настройки Telegram бота - /// Результат валидации - public static ValidationResult ValidateTelegramBotSettings(TelegramBotSettings settings) - { - var errors = new List(); - - // Проверка наличия токена бота - if (string.IsNullOrWhiteSpace(settings.BotToken)) - { - errors.Add("TelegramBot:BotToken is required"); - } - // Проверка формата токена (должен содержать ':' или начинаться с 'bot') - else if ( - !settings.BotToken.StartsWith("bot", StringComparison.OrdinalIgnoreCase) - && !settings.BotToken.Contains(":") - ) - { - errors.Add( - "TelegramBot:BotToken appears to be invalid (should contain ':' or start with 'bot')" - ); - } - - return new ValidationResult { IsValid = !errors.Any(), Errors = errors }; - } - - /// - /// Валидирует настройки Ollama - /// - /// Настройки Ollama - /// Результат валидации - public static ValidationResult ValidateOllamaSettings(OllamaSettings settings) - { - var errors = new List(); - - // Валидация основных компонентов настроек Ollama - ValidateUrl(settings.Url, errors); - ValidateNumericSettings(settings, errors); - - if (string.IsNullOrWhiteSpace(settings.DefaultModel)) - { - errors.Add("Ollama:DefaultModel is required"); - } - - return new ValidationResult { IsValid = !errors.Any(), Errors = errors }; - } - - /// - /// Валидирует URL Ollama - /// - /// URL для проверки - /// Список ошибок валидации - private static void ValidateUrl(string url, List errors) - { - // Проверка наличия URL - if (string.IsNullOrWhiteSpace(url)) - { - errors.Add("Ollama:Url is required"); - } - // Проверка корректности URL (должен быть валидным HTTP/HTTPS URL) - else if ( - !Uri.TryCreate(url, UriKind.Absolute, out var uri) - || (uri.Scheme != "http" && uri.Scheme != "https") - ) - { - errors.Add("Ollama:Url must be a valid HTTP/HTTPS URL"); - } - } - - /// - /// Валидирует числовые параметры настроек Ollama - /// - /// Настройки Ollama - /// Список ошибок валидации - private static void ValidateNumericSettings(OllamaSettings settings, List errors) - { - // Проверка количества повторных попыток (1-10) - if (settings.MaxRetries < 1 || settings.MaxRetries > 10) - { - errors.Add("Ollama:MaxRetries must be between 1 and 10"); - } - } - } - - /// - /// Результат валидации конфигурации - /// - public class ValidationResult - { - /// - /// Указывает, прошла ли валидация успешно - /// - public bool IsValid { get; set; } - - /// - /// Список ошибок валидации - /// - public List Errors { get; set; } = new(); - } -} diff --git a/ChatBot/Models/Validation/ChatMessageValidator.cs b/ChatBot/Models/Validation/ChatMessageValidator.cs deleted file mode 100644 index 3d41768..0000000 --- a/ChatBot/Models/Validation/ChatMessageValidator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using ChatBot.Common.Constants; -using ChatBot.Models.Dto; -using FluentValidation; - -namespace ChatBot.Models.Validation -{ - /// - /// Validator for ChatMessage - /// - public class ChatMessageValidator : AbstractValidator - { - public ChatMessageValidator() - { - RuleFor(x => x.Content) - .NotEmpty() - .WithMessage("Message content cannot be empty") - .MaximumLength(10000) - .WithMessage("Message content is too long (max 10000 characters)"); - - RuleFor(x => x.Role) - .NotEmpty() - .WithMessage("Message role cannot be empty") - .Must(role => - role == ChatRoles.System - || role == ChatRoles.User - || role == ChatRoles.Assistant - ) - .WithMessage( - $"Invalid message role. Must be one of: {ChatRoles.System}, {ChatRoles.User}, {ChatRoles.Assistant}" - ); - } - } -} diff --git a/ChatBot/Program.cs b/ChatBot/Program.cs index 53fd17d..adec8ff 100644 --- a/ChatBot/Program.cs +++ b/ChatBot/Program.cs @@ -1,14 +1,11 @@ using ChatBot.Models.Configuration; using ChatBot.Models.Configuration.Validators; -using ChatBot.Models.Validation; using ChatBot.Services; -using ChatBot.Services.ErrorHandlers; using ChatBot.Services.HealthChecks; using ChatBot.Services.Interfaces; using ChatBot.Services.Telegram.Commands; using ChatBot.Services.Telegram.Interfaces; using ChatBot.Services.Telegram.Services; -using FluentValidation; using Microsoft.Extensions.Options; using Serilog; using Telegram.Bot; @@ -26,8 +23,6 @@ try builder.Services.AddSerilog(); // Конфигурируем настройки с валидацией - builder.Services.Configure(builder.Configuration); - builder .Services.Configure(builder.Configuration.GetSection("TelegramBot")) .AddSingleton, TelegramBotSettingsValidator>(); @@ -36,36 +31,10 @@ try .Services.Configure(builder.Configuration.GetSection("Ollama")) .AddSingleton, OllamaSettingsValidator>(); - builder.Services.Configure(builder.Configuration.GetSection("Serilog")); - // Валидируем конфигурацию при старте builder.Services.AddOptions().ValidateOnStart(); builder.Services.AddOptions().ValidateOnStart(); - // Валидируем конфигурацию (старый способ для совместимости) - var appSettings = builder.Configuration.Get(); - if (appSettings == null) - { - Log.ForContext().Fatal("Failed to load configuration"); - return; - } - - var validationResult = ConfigurationValidator.ValidateAppSettings(appSettings); - if (!validationResult.IsValid) - { - Log.ForContext().Fatal("Configuration validation failed:"); - foreach (var error in validationResult.Errors) - { - Log.ForContext().Fatal(" - {Error}", error); - } - return; - } - - Log.ForContext().Debug("Configuration validation passed"); - - // Регистрируем FluentValidation валидаторы - builder.Services.AddValidatorsFromAssemblyContaining(); - // Регистрируем IOllamaClient builder.Services.AddSingleton(sp => { @@ -76,13 +45,6 @@ try // Регистрируем интерфейсы и сервисы builder.Services.AddSingleton(); - // Регистрируем error handlers - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - // Регистрируем retry policy (использует error handlers) - builder.Services.AddSingleton(); - // Регистрируем основные сервисы builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/ChatBot/Services/ErrorHandlers/NetworkErrorHandler.cs b/ChatBot/Services/ErrorHandlers/NetworkErrorHandler.cs deleted file mode 100644 index 011e7c6..0000000 --- a/ChatBot/Services/ErrorHandlers/NetworkErrorHandler.cs +++ /dev/null @@ -1,49 +0,0 @@ -using ChatBot.Services.Interfaces; - -namespace ChatBot.Services.ErrorHandlers -{ - /// - /// Error handler for network-related errors - /// - public class NetworkErrorHandler : IErrorHandler - { - private readonly ILogger _logger; - - public NetworkErrorHandler(ILogger logger) - { - _logger = logger; - } - - public bool CanHandle(Exception exception) - { - return exception is HttpRequestException - || exception is TaskCanceledException - || exception.Message.Contains("timeout", StringComparison.OrdinalIgnoreCase) - || exception.Message.Contains("connection", StringComparison.OrdinalIgnoreCase); - } - - public async Task HandleAsync( - Exception exception, - int attempt, - string currentModel, - CancellationToken cancellationToken = default - ) - { - _logger.LogWarning( - exception, - "Network error on attempt {Attempt} for model {Model}", - attempt, - currentModel - ); - - // Apply exponential backoff for network errors - var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)); - - _logger.LogInformation("Waiting {Delay} before retry due to network error", delay); - - await Task.Delay(delay, cancellationToken); - - return ErrorHandlingResult.Retry(); - } - } -} diff --git a/ChatBot/Services/ErrorHandlers/RateLimitErrorHandler.cs b/ChatBot/Services/ErrorHandlers/RateLimitErrorHandler.cs deleted file mode 100644 index 4aed920..0000000 --- a/ChatBot/Services/ErrorHandlers/RateLimitErrorHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -using ChatBot.Services.Interfaces; - -namespace ChatBot.Services.ErrorHandlers -{ - /// - /// Error handler for rate limit errors (HTTP 429) - /// - public class RateLimitErrorHandler : IErrorHandler - { - private readonly ILogger _logger; - - public RateLimitErrorHandler(ILogger logger) - { - _logger = logger; - } - - public bool CanHandle(Exception exception) - { - return exception.Message.Contains("429") - || exception.Message.Contains("Too Many Requests") - || exception.Message.Contains("rate limit", StringComparison.OrdinalIgnoreCase); - } - - public async Task HandleAsync( - Exception exception, - int attempt, - string currentModel, - CancellationToken cancellationToken = default - ) - { - _logger.LogWarning( - exception, - "Rate limit exceeded on attempt {Attempt} for model {Model}", - attempt, - currentModel - ); - - // Apply exponential backoff for rate limiting - var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)); - var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 2000)); - - _logger.LogInformation( - "Rate limit hit, waiting {Delay} before retry", - delay.Add(jitter) - ); - - await Task.Delay(delay.Add(jitter), cancellationToken); - - return ErrorHandlingResult.Retry(); - } - } -} diff --git a/ChatBot/Services/ExponentialBackoffRetryPolicy.cs b/ChatBot/Services/ExponentialBackoffRetryPolicy.cs deleted file mode 100644 index 7936a47..0000000 --- a/ChatBot/Services/ExponentialBackoffRetryPolicy.cs +++ /dev/null @@ -1,111 +0,0 @@ -using ChatBot.Common.Constants; -using ChatBot.Models.Configuration; -using ChatBot.Services.Interfaces; -using Microsoft.Extensions.Options; - -namespace ChatBot.Services -{ - /// - /// Retry policy with exponential backoff and jitter - /// - public class ExponentialBackoffRetryPolicy : IRetryPolicy - { - private readonly int _maxRetries; - private readonly ILogger _logger; - private readonly IEnumerable _errorHandlers; - - public ExponentialBackoffRetryPolicy( - IOptions settings, - ILogger logger, - IEnumerable errorHandlers - ) - { - _maxRetries = settings.Value.MaxRetries; - _logger = logger; - _errorHandlers = errorHandlers; - } - - public async Task ExecuteAsync( - Func> action, - CancellationToken cancellationToken = default - ) - { - Exception? lastException = null; - - for (int attempt = 1; attempt <= _maxRetries; attempt++) - { - try - { - return await action(); - } - catch (Exception ex) when (attempt < _maxRetries) - { - lastException = ex; - LogAttemptFailure(ex, attempt); - - if (!await HandleErrorAndDecideRetry(ex, attempt, cancellationToken)) - break; - } - catch (Exception ex) - { - lastException = ex; - _logger.LogError(ex, "All {MaxRetries} attempts failed", _maxRetries); - } - } - - throw new InvalidOperationException( - $"Failed after {_maxRetries} attempts", - lastException - ); - } - - private void LogAttemptFailure(Exception ex, int attempt) - { - _logger.LogWarning(ex, "Attempt {Attempt}/{MaxRetries} failed", attempt, _maxRetries); - } - - private async Task HandleErrorAndDecideRetry( - Exception ex, - int attempt, - CancellationToken cancellationToken - ) - { - var handler = _errorHandlers.FirstOrDefault(h => h.CanHandle(ex)); - if (handler == null) - { - await DelayWithBackoff(attempt, cancellationToken); - return true; - } - - var result = await handler.HandleAsync(ex, attempt, string.Empty, cancellationToken); - - if (result.IsFatal) - { - _logger.LogError("Fatal error occurred: {ErrorMessage}", result.ErrorMessage); - return false; - } - - return result.ShouldRetry; - } - - private async Task DelayWithBackoff(int attempt, CancellationToken cancellationToken) - { - var baseDelay = TimeSpan.FromSeconds( - Math.Pow(2, attempt - 1) * RetryConstants.DefaultBaseDelaySeconds - ); - var jitter = TimeSpan.FromMilliseconds( - Random.Shared.Next(0, RetryConstants.DefaultMaxJitterMs) - ); - var delay = baseDelay.Add(jitter); - - _logger.LogInformation( - "Waiting {Delay} before retry {NextAttempt}/{MaxRetries}", - delay, - attempt + 1, - _maxRetries - ); - - await Task.Delay(delay, cancellationToken); - } - } -} diff --git a/ChatBot/Services/InMemorySessionStorage.cs b/ChatBot/Services/InMemorySessionStorage.cs index f5799a8..77a1ee4 100644 --- a/ChatBot/Services/InMemorySessionStorage.cs +++ b/ChatBot/Services/InMemorySessionStorage.cs @@ -1,6 +1,6 @@ +using System.Collections.Concurrent; using ChatBot.Models; using ChatBot.Services.Interfaces; -using System.Collections.Concurrent; namespace ChatBot.Services { diff --git a/ChatBot/Services/Interfaces/IErrorHandler.cs b/ChatBot/Services/Interfaces/IErrorHandler.cs deleted file mode 100644 index 910c5c5..0000000 --- a/ChatBot/Services/Interfaces/IErrorHandler.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace ChatBot.Services.Interfaces -{ - /// - /// Interface for error handling strategy - /// - public interface IErrorHandler - { - /// - /// Check if this handler can handle the exception - /// - bool CanHandle(Exception exception); - - /// - /// Handle the exception and return result - /// - Task HandleAsync( - Exception exception, - int attempt, - string currentModel, - CancellationToken cancellationToken = default - ); - } - - /// - /// Result of error handling - /// - public class ErrorHandlingResult - { - public bool ShouldRetry { get; set; } - public string? NewModel { get; set; } - public bool IsFatal { get; set; } - public string? ErrorMessage { get; set; } - - public static ErrorHandlingResult Retry(string? newModel = null) => - new() { ShouldRetry = true, NewModel = newModel }; - - public static ErrorHandlingResult Fatal(string errorMessage) => - new() { IsFatal = true, ErrorMessage = errorMessage }; - - public static ErrorHandlingResult NoRetry() => new() { ShouldRetry = false }; - } -} diff --git a/ChatBot/Services/Interfaces/IRetryPolicy.cs b/ChatBot/Services/Interfaces/IRetryPolicy.cs deleted file mode 100644 index abb557a..0000000 --- a/ChatBot/Services/Interfaces/IRetryPolicy.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace ChatBot.Services.Interfaces -{ - /// - /// Interface for retry policy - /// - public interface IRetryPolicy - { - /// - /// Execute an action with retry logic - /// - Task ExecuteAsync( - Func> action, - CancellationToken cancellationToken = default - ); - } -} diff --git a/ChatBot/Services/Telegram/Services/TelegramBotService.cs b/ChatBot/Services/Telegram/Services/TelegramBotService.cs index fcda393..e522d35 100644 --- a/ChatBot/Services/Telegram/Services/TelegramBotService.cs +++ b/ChatBot/Services/Telegram/Services/TelegramBotService.cs @@ -15,24 +15,20 @@ namespace ChatBot.Services.Telegram.Services { private readonly ILogger _logger; private readonly ITelegramBotClient _botClient; - private readonly TelegramBotSettings _telegramBotSettings; private readonly ITelegramMessageHandler _messageHandler; private readonly ITelegramErrorHandler _errorHandler; public TelegramBotService( ILogger logger, - IOptions telegramBotSettings, + ITelegramBotClient botClient, ITelegramMessageHandler messageHandler, ITelegramErrorHandler errorHandler ) { _logger = logger; - _telegramBotSettings = telegramBotSettings.Value; + _botClient = botClient; _messageHandler = messageHandler; _errorHandler = errorHandler; - - ValidateConfiguration(); - _botClient = new TelegramBotClient(_telegramBotSettings.BotToken); } /// @@ -98,15 +94,5 @@ namespace ChatBot.Services.Telegram.Services await Task.Delay(1000, stoppingToken); } } - - private void ValidateConfiguration() - { - if (string.IsNullOrEmpty(_telegramBotSettings.BotToken)) - { - throw new InvalidOperationException( - "Bot token is not configured. Please set TelegramBot:BotToken in appsettings.json" - ); - } - } } } diff --git a/README.md b/README.md index fb01b6d..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,248 +0,0 @@ -# Telegram ChatBot with Ollama AI - -A high-quality, production-ready Telegram chatbot powered by Ollama AI models. This bot provides natural conversation experiences using local AI models. - -## 🎯 Features - -- **Ollama Integration**: Uses OllamaSharp library for efficient AI model communication -- **Multiple Model Support**: Automatically manages and switches between multiple AI models -- **Session Management**: Maintains conversation history for each chat -- **Command System**: Extensible command architecture for bot commands -- **Smart Retry Logic**: Exponential backoff with jitter for failed requests -- **Rate Limit Handling**: Automatic model switching on rate limits -- **Natural Conversation**: Configurable response delays for human-like interactions -- **Group Chat Support**: Works in both private and group conversations -- **Robust Logging**: Comprehensive logging with Serilog - -## 📋 Prerequisites - -- .NET 9.0 or later -- Ollama server running locally or remotely -- Telegram Bot Token (from [@BotFather](https://t.me/botfather)) - -## 🚀 Getting Started - -### 1. Install Ollama - -Download and install Ollama from [ollama.ai](https://ollama.ai) - -### 2. Pull an AI Model - -```bash -ollama pull llama3 -``` - -### 3. Configure the Bot - -Edit `appsettings.json`: - -```json -{ - "TelegramBot": { - "BotToken": "YOUR_BOT_TOKEN_HERE" - }, - "Ollama": { - "Url": "http://localhost:11434", - "MaxRetries": 3, - "MaxTokens": 1000, - "Temperature": 0.7, - "ResponseDelay": { - "IsEnabled": true, - "MinDelayMs": 1000, - "MaxDelayMs": 3000 - }, - "SystemPromptFilePath": "system-prompt.txt" - } -} -``` - -Edit `appsettings.Models.json` to configure your models: - -```json -{ - "ModelConfigurations": [ - { - "Name": "llama3", - "MaxTokens": 2000, - "Temperature": 0.8, - "Description": "Llama 3 Model", - "IsEnabled": true - } - ] -} -``` - -### 4. Customize System Prompt - -Edit `system-prompt.txt` to define your bot's personality and behavior. - -### 5. Run the Bot - -```bash -cd ChatBot -dotnet run -``` - -## 🏗️ Architecture - -### Core Services - -- **AIService**: Handles AI model communication and text generation -- **ChatService**: Manages chat sessions and message history -- **ModelService**: Handles model selection and switching -- **TelegramBotService**: Main Telegram bot service - -### Command System - -Commands are automatically registered using attributes: - -```csharp -[Command("start", "Start conversation with the bot")] -public class StartCommand : TelegramCommandBase -{ - // Implementation -} -``` - -Available commands: -- `/start` - Start conversation -- `/help` - Show help information -- `/clear` - Clear conversation history -- `/settings` - View current settings - -## ⚙️ Configuration - -### Ollama Settings - -- **Url**: Ollama server URL -- **MaxRetries**: Maximum retry attempts for failed requests -- **MaxTokens**: Default maximum tokens for responses -- **Temperature**: AI creativity level (0.0 - 2.0) -- **ResponseDelay**: Add human-like delays before responses -- **SystemPromptFilePath**: Path to system prompt file - -### Model Configuration - -Each model can have custom settings: - -- **Name**: Model name (must match Ollama model name) -- **MaxTokens**: Maximum tokens for this model -- **Temperature**: Temperature for this model -- **Description**: Human-readable description -- **IsEnabled**: Whether the model is available for use - -## 🔧 Advanced Features - -### Automatic Model Switching - -The bot automatically switches to alternative models when: -- Rate limits are encountered -- Current model becomes unavailable - -### Session Management - -- Automatic session creation per chat -- Configurable message history length -- Old session cleanup (default: 24 hours) - -### Error Handling - -- Exponential backoff with jitter for retries -- Graceful degradation on failures -- Comprehensive error logging - -## 📝 Development - -### Project Structure - -``` -ChatBot/ -├── Models/ -│ ├── Configuration/ # Configuration models -│ │ └── Validators/ # Configuration validation -│ └── Dto/ # Data transfer objects -├── Services/ -│ ├── Telegram/ # Telegram-specific services -│ │ ├── Commands/ # Bot commands -│ │ ├── Interfaces/ # Service interfaces -│ │ └── Services/ # Service implementations -│ ├── AIService.cs # AI model communication -│ ├── ChatService.cs # Chat session management -│ └── ModelService.cs # Model management -└── Program.cs # Application entry point -``` - -### Adding New Commands - -1. Create a new class in `Services/Telegram/Commands/` -2. Inherit from `TelegramCommandBase` -3. Add `[Command]` attribute -4. Implement `ExecuteAsync` method - -Example: - -```csharp -[Command("mycommand", "Description of my command")] -public class MyCommand : TelegramCommandBase -{ - public override async Task ExecuteAsync(TelegramCommandContext context) - { - await context.MessageSender.SendTextMessageAsync( - context.Message.Chat.Id, - "Command executed!" - ); - } -} -``` - -## 🐛 Troubleshooting - -### Bot doesn't respond - -1. Check if Ollama server is running: `ollama list` -2. Verify bot token in `appsettings.json` -3. Check logs in `logs/` directory - -### Model not found - -1. Pull the model: `ollama pull model-name` -2. Verify model name matches in `appsettings.Models.json` -3. Check model availability: `ollama list` - -### Connection errors - -1. Verify Ollama URL in configuration -2. Check firewall settings -3. Ensure Ollama server is accessible - -## 📦 Dependencies - -- **OllamaSharp** (v5.4.7): Ollama API client -- **Telegram.Bot** (v22.7.2): Telegram Bot API -- **Serilog** (v4.3.0): Structured logging -- **Microsoft.Extensions.Hosting** (v9.0.10): Host infrastructure - -## 📄 License - -This project is licensed under the terms specified in [LICENSE.txt](LICENSE.txt). - -## 🤝 Contributing - -Contributions are welcome! Please ensure: -- Code follows existing patterns -- All tests pass -- Documentation is updated -- Commits are descriptive - -## 🔮 Future Enhancements - -- [ ] Multi-language support -- [ ] Voice message handling -- [ ] Image generation support -- [ ] User preferences persistence -- [ ] Advanced conversation analytics -- [ ] Custom model fine-tuning support - ---- - -Built with ❤️ using .NET 9.0 and Ollama diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md deleted file mode 100644 index 2c0b3b4..0000000 --- a/REFACTORING_SUMMARY.md +++ /dev/null @@ -1,449 +0,0 @@ -# Рефакторинг проекта ChatBot - Итоги - -## 📋 Выполненные улучшения - -Все рекомендации по улучшению проекта были реализованы, за исключением unit-тестов (как было запрошено). - ---- - -## ✅ Реализованные изменения - -### 1. **Константы для магических строк и значений** - -Созданы классы констант для улучшения читаемости и поддерживаемости: - -- `ChatBot/Common/Constants/AIResponseConstants.cs` - константы для AI ответов -- `ChatBot/Common/Constants/ChatRoles.cs` - роли сообщений (system, user, assistant) -- `ChatBot/Common/Constants/ChatTypes.cs` - типы чатов -- `ChatBot/Common/Constants/RetryConstants.cs` - константы для retry логики - -**Преимущества:** -- Нет магических строк в коде -- Легко изменить значения в одном месте -- IntelliSense помогает при разработке - ---- - -### 2. **Result Pattern** - -Создан класс `Result` для явного представления успеха/неудачи операций: - -**Файл:** `ChatBot/Common/Results/Result.cs` - -```csharp -var result = Result.Success("данные"); -var failure = Result.Failure("ошибка"); -``` - -**Преимущества:** -- Явная обработка ошибок без exceptions -- Более функциональный подход -- Лучшая читаемость кода - ---- - -### 3. **SOLID Principles - Интерфейсы для всех сервисов** - -#### **Dependency Inversion Principle (DIP)** - -Созданы интерфейсы для всех основных сервисов: - -- `IAIService` - интерфейс для AI сервиса -- `ISessionStorage` - интерфейс для хранения сессий -- `IOllamaClient` - интерфейс для Ollama клиента -- `ISystemPromptProvider` - интерфейс для загрузки системного промпта -- `IRetryPolicy` - интерфейс для retry логики -- `IResponseDelayService` - интерфейс для задержек -- `IErrorHandler` - интерфейс для обработки ошибок - -**Преимущества:** -- Слабая связанность компонентов -- Легко тестировать с моками -- Можно менять реализацию без изменения зависимых классов - ---- - -### 4. **Single Responsibility Principle (SRP)** - -#### **Разделение ответственностей в AIService** - -**До:** AIService делал все - генерацию, retry, задержки, переключение моделей - -**После:** Каждый класс отвечает за одну вещь: - -- `AIService` - только генерация текста -- `ExponentialBackoffRetryPolicy` - retry логика -- `RandomResponseDelayService` - задержки ответов -- `RateLimitErrorHandler` / `NetworkErrorHandler` - обработка ошибок -- `ModelService` - управление моделями - -#### **Удаление статического метода из ChatSession** - -**До:** `ChatSession.LoadSystemPrompt()` - нарушал SRP - -**После:** Создан `FileSystemPromptProvider` - отдельный сервис для загрузки промптов - -#### **Новая структура:** - -``` -ChatBot/Services/ -├── AIService.cs (упрощен) -├── FileSystemPromptProvider.cs -├── InMemorySessionStorage.cs -├── ExponentialBackoffRetryPolicy.cs -├── RandomResponseDelayService.cs -└── ErrorHandlers/ - ├── RateLimitErrorHandler.cs - └── NetworkErrorHandler.cs -``` - ---- - -### 5. **Open/Closed Principle (OCP)** - -#### **Strategy Pattern для обработки ошибок** - -**До:** Жестко закодированная проверка `if (ex.Message.Contains("429"))` - -**После:** Расширяемая система с интерфейсом `IErrorHandler` - -```csharp -public interface IErrorHandler -{ - bool CanHandle(Exception exception); - Task HandleAsync(...); -} -``` - -**Реализации:** -- `RateLimitErrorHandler` - обработка HTTP 429 -- `NetworkErrorHandler` - сетевые ошибки - -**Преимущества:** -- Легко добавить новый обработчик без изменения существующего кода -- Каждый обработчик независим -- Цепочка ответственности (Chain of Responsibility) - ---- - -### 6. **Устранение анти-паттернов** - -#### **6.1. Service Locator в CommandRegistry (КРИТИЧНО)** - -**До:** -```csharp -// Service Locator - анти-паттерн -var service = serviceProvider.GetService(parameterType); -var command = Activator.CreateInstance(commandType, args); -``` - -**После:** -```csharp -// Proper Dependency Injection -public CommandRegistry(IEnumerable commands) -{ - foreach (var command in commands) - RegisterCommand(command); -} -``` - -В `Program.cs`: -```csharp -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -``` - -#### **6.2. Threading Issue в BotInfoService (КРИТИЧНО)** - -**До:** -```csharp -lock (_lock) // lock с async - deadlock! -{ - var task = _botClient.GetMe(); - task.Wait(); // блокировка потока -} -``` - -**После:** -```csharp -private readonly SemaphoreSlim _semaphore = new(1, 1); - -await _semaphore.WaitAsync(cancellationToken); -try -{ - _cachedBotInfo = await _botClient.GetMe(...); -} -finally -{ - _semaphore.Release(); -} -``` - -**Преимущества:** -- Нет риска deadlock -- Асинхронный код работает правильно -- Поддержка CancellationToken - ---- - -### 7. **FluentValidation** - -Добавлены валидаторы для моделей данных: - -**Файлы:** -- `ChatBot/Models/Validation/ChatMessageValidator.cs` -- `ChatBot/Models/Configuration/Validators/OllamaSettingsValidator.cs` -- `ChatBot/Models/Configuration/Validators/TelegramBotSettingsValidator.cs` - -**Пример:** -```csharp -public class ChatMessageValidator : AbstractValidator -{ - public ChatMessageValidator() - { - RuleFor(x => x.Content) - .NotEmpty() - .MaximumLength(10000); - - RuleFor(x => x.Role) - .Must(role => new[] { "system", "user", "assistant" }.Contains(role)); - } -} -``` - ---- - -### 8. **Options Pattern Validation** - -Валидация конфигурации при старте приложения: - -```csharp -builder.Services - .Configure(...) - .AddSingleton, OllamaSettingsValidator>() - .ValidateOnStart(); -``` - -**Преимущества:** -- Приложение не стартует с невалидной конфигурацией -- Ошибки конфигурации обнаруживаются сразу -- Детальные сообщения об ошибках - ---- - -### 9. **Health Checks** - -Добавлены проверки работоспособности внешних зависимостей: - -**Файлы:** -- `ChatBot/Services/HealthChecks/OllamaHealthCheck.cs` - проверка Ollama API -- `ChatBot/Services/HealthChecks/TelegramBotHealthCheck.cs` - проверка Telegram Bot API - -**Регистрация:** -```csharp -builder.Services - .AddHealthChecks() - .AddCheck("ollama", tags: new[] { "api", "ollama" }) - .AddCheck("telegram", tags: new[] { "api", "telegram" }); -``` - -**Преимущества:** -- Мониторинг состояния сервисов -- Быстрое обнаружение проблем -- Интеграция с системами мониторинга - ---- - -### 10. **CancellationToken Support** - -Добавлена поддержка отмены операций во всех асинхронных методах: - -```csharp -public async Task GenerateChatCompletionAsync( - List messages, - int? maxTokens = null, - double? temperature = null, - CancellationToken cancellationToken = default) // ✓ -``` - -**Преимущества:** -- Graceful shutdown приложения -- Отмена долгих операций -- Экономия ресурсов - ---- - -### 11. **Новые пакеты** - -Добавлены в `ChatBot.csproj`: - -```xml - - - -``` - ---- - -## 📊 Сравнение "До" и "После" - -### **AIService** - -**До:** 237 строк, 8 ответственностей -**После:** 104 строки, 1 ответственность (генерация текста) - -### **ChatService** - -**До:** Зависит от конкретных реализаций -**После:** Зависит только от интерфейсов - -### **Program.cs** - -**До:** 101 строка, Service Locator -**После:** 149 строк, Proper DI с валидацией и Health Checks - ---- - -## 🎯 Соблюдение SOLID Principles - -### ✅ **S - Single Responsibility Principle** -- Каждый класс имеет одну ответственность -- AIService упрощен с 237 до 104 строк -- Логика вынесена в специализированные сервисы - -### ✅ **O - Open/Closed Principle** -- Strategy Pattern для обработки ошибок -- Легко добавить новый ErrorHandler без изменения существующего кода - -### ✅ **L - Liskov Substitution Principle** -- Все реализации интерфейсов взаимозаменяемы -- Mock-объекты для тестирования - -### ✅ **I - Interface Segregation Principle** -- Интерфейсы специфичны и минимальны -- Никто не зависит от методов, которые не использует - -### ✅ **D - Dependency Inversion Principle** -- Все зависимости через интерфейсы -- Высокоуровневые модули не зависят от низкоуровневых - ---- - -## 🏗️ Паттерны проектирования - -1. **Dependency Injection** - через Microsoft.Extensions.DependencyInjection -2. **Strategy Pattern** - IErrorHandler для разных типов ошибок -3. **Adapter Pattern** - OllamaClientAdapter оборачивает OllamaApiClient -4. **Provider Pattern** - ISystemPromptProvider для загрузки промптов -5. **Repository Pattern** - ISessionStorage для хранения сессий -6. **Command Pattern** - ITelegramCommand для команд бота -7. **Chain of Responsibility** - ErrorHandlingChain для обработки ошибок - ---- - -## 📝 Структура проекта после рефакторинга - -``` -ChatBot/ -├── Common/ -│ ├── Constants/ -│ │ ├── AIResponseConstants.cs -│ │ ├── ChatRoles.cs -│ │ ├── ChatTypes.cs -│ │ └── RetryConstants.cs -│ └── Results/ -│ └── Result.cs -├── Models/ -│ ├── Configuration/ -│ │ └── Validators/ -│ │ ├── OllamaSettingsValidator.cs -│ │ └── TelegramBotSettingsValidator.cs -│ └── Validation/ -│ └── ChatMessageValidator.cs -├── Services/ -│ ├── Interfaces/ -│ │ ├── IAIService.cs -│ │ ├── IErrorHandler.cs -│ │ ├── IOllamaClient.cs -│ │ ├── IResponseDelayService.cs -│ │ ├── IRetryPolicy.cs -│ │ ├── ISessionStorage.cs -│ │ └── ISystemPromptProvider.cs -│ ├── ErrorHandlers/ -│ │ ├── RateLimitErrorHandler.cs -│ │ └── NetworkErrorHandler.cs -│ ├── HealthChecks/ -│ │ ├── OllamaHealthCheck.cs -│ │ └── TelegramBotHealthCheck.cs -│ ├── AIService.cs (refactored) -│ ├── ChatService.cs (refactored) -│ ├── ExponentialBackoffRetryPolicy.cs -│ ├── FileSystemPromptProvider.cs -│ ├── InMemorySessionStorage.cs -│ ├── OllamaClientAdapter.cs -│ └── RandomResponseDelayService.cs -└── Program.cs (updated) -``` - ---- - -## 🚀 Преимущества после рефакторинга - -### Для разработки: -- ✅ Код легче читать и понимать -- ✅ Легко добавлять новые функции -- ✅ Проще писать unit-тесты -- ✅ Меньше дублирования кода - -### Для поддержки: -- ✅ Проще находить и исправлять баги -- ✅ Изменения не влияют на другие части системы -- ✅ Логи более структурированы - -### Для производительности: -- ✅ Нет риска deadlock'ов -- ✅ Правильная работа с async/await -- ✅ Поддержка отмены операций - -### Для надежности: -- ✅ Валидация конфигурации при старте -- ✅ Health checks для мониторинга -- ✅ Правильная обработка ошибок - ---- - -## 🔧 Что дальше? - -### Рекомендации для дальнейшего развития: - -1. **Unit-тесты** - покрыть тестами новые сервисы -2. **Integration тесты** - тестирование с реальными зависимостями -3. **Метрики** - добавить Prometheus metrics -4. **Distributed Tracing** - добавить OpenTelemetry -5. **Circuit Breaker** - для защиты от каскадных ошибок -6. **Rate Limiting** - ограничение запросов к AI -7. **Caching** - кэширование ответов AI -8. **Background Jobs** - для cleanup старых сессий - ---- - -## ✨ Итоги - -Проект был полностью отрефакторен согласно принципам SOLID и best practices .NET: - -- ✅ 14 задач выполнено -- ✅ 0 критичных проблем -- ✅ Код компилируется без ошибок -- ✅ Следует принципам SOLID -- ✅ Использует современные паттерны -- ✅ Готов к масштабированию и тестированию - -**Время выполнения:** ~40 минут -**Файлов создано:** 23 -**Файлов изменено:** 8 -**Строк кода:** +1500 / -300 - -🎉 **Проект готов к production использованию!** -