clear
This commit is contained in:
@@ -17,8 +17,6 @@
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace ChatBot.Common.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for retry logic
|
||||
/// </summary>
|
||||
public static class RetryConstants
|
||||
{
|
||||
public const int DefaultMaxRetries = 3;
|
||||
public const int DefaultBaseDelaySeconds = 1;
|
||||
public const int DefaultMaxJitterMs = 2000;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
namespace ChatBot.Common.Results
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the result of an operation that can succeed or fail
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of an operation that returns a value
|
||||
/// </summary>
|
||||
public class Result<T> : Result
|
||||
{
|
||||
public T? Value { get; }
|
||||
|
||||
private Result(T? value, bool isSuccess, string error)
|
||||
: base(isSuccess, error)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Result<T> Success(T value) => new(value, true, string.Empty);
|
||||
|
||||
public static new Result<T> Failure(string error) => new(default, false, error);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
namespace ChatBot.Models.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Основные настройки приложения
|
||||
/// </summary>
|
||||
public class AppSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Настройки Telegram бота
|
||||
/// </summary>
|
||||
public TelegramBotSettings TelegramBot { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Настройки Ollama API
|
||||
/// </summary>
|
||||
public OllamaSettings Ollama { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Настройки логирования Serilog
|
||||
/// </summary>
|
||||
public SerilogSettings Serilog { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
namespace ChatBot.Models.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Настройки логирования Serilog
|
||||
/// </summary>
|
||||
public class SerilogSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Список используемых sink'ов для логирования
|
||||
/// </summary>
|
||||
public List<string> Using { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Настройки минимального уровня логирования
|
||||
/// </summary>
|
||||
public MinimumLevelSettings MinimumLevel { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Настройки получателей логов (куда писать логи)
|
||||
/// </summary>
|
||||
public List<WriteToSettings> WriteTo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Список обогатителей логов (дополнительная информация)
|
||||
/// </summary>
|
||||
public List<string> Enrich { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройки минимального уровня логирования
|
||||
/// </summary>
|
||||
public class MinimumLevelSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Уровень логирования по умолчанию
|
||||
/// </summary>
|
||||
public string Default { get; set; } = "Information";
|
||||
|
||||
/// <summary>
|
||||
/// Переопределения уровня логирования для конкретных пространств имен
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Override { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройки получателя логов
|
||||
/// </summary>
|
||||
public class WriteToSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Название sink'а для записи логов
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Аргументы для настройки sink'а
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Args { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
namespace ChatBot.Models.Configuration.Validators
|
||||
{
|
||||
/// <summary>
|
||||
/// Валидатор конфигурации приложения
|
||||
/// </summary>
|
||||
public static class ConfigurationValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Валидирует все настройки приложения
|
||||
/// </summary>
|
||||
/// <param name="settings">Настройки приложения</param>
|
||||
/// <returns>Результат валидации</returns>
|
||||
public static ValidationResult ValidateAppSettings(AppSettings settings)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
// Валидация настроек 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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Валидирует настройки Telegram бота
|
||||
/// </summary>
|
||||
/// <param name="settings">Настройки Telegram бота</param>
|
||||
/// <returns>Результат валидации</returns>
|
||||
public static ValidationResult ValidateTelegramBotSettings(TelegramBotSettings settings)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
// Проверка наличия токена бота
|
||||
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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Валидирует настройки Ollama
|
||||
/// </summary>
|
||||
/// <param name="settings">Настройки Ollama</param>
|
||||
/// <returns>Результат валидации</returns>
|
||||
public static ValidationResult ValidateOllamaSettings(OllamaSettings settings)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
// Валидация основных компонентов настроек 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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Валидирует URL Ollama
|
||||
/// </summary>
|
||||
/// <param name="url">URL для проверки</param>
|
||||
/// <param name="errors">Список ошибок валидации</param>
|
||||
private static void ValidateUrl(string url, List<string> 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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Валидирует числовые параметры настроек Ollama
|
||||
/// </summary>
|
||||
/// <param name="settings">Настройки Ollama</param>
|
||||
/// <param name="errors">Список ошибок валидации</param>
|
||||
private static void ValidateNumericSettings(OllamaSettings settings, List<string> errors)
|
||||
{
|
||||
// Проверка количества повторных попыток (1-10)
|
||||
if (settings.MaxRetries < 1 || settings.MaxRetries > 10)
|
||||
{
|
||||
errors.Add("Ollama:MaxRetries must be between 1 and 10");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Результат валидации конфигурации
|
||||
/// </summary>
|
||||
public class ValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Указывает, прошла ли валидация успешно
|
||||
/// </summary>
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Список ошибок валидации
|
||||
/// </summary>
|
||||
public List<string> Errors { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using ChatBot.Common.Constants;
|
||||
using ChatBot.Models.Dto;
|
||||
using FluentValidation;
|
||||
|
||||
namespace ChatBot.Models.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Validator for ChatMessage
|
||||
/// </summary>
|
||||
public class ChatMessageValidator : AbstractValidator<ChatMessage>
|
||||
{
|
||||
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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<AppSettings>(builder.Configuration);
|
||||
|
||||
builder
|
||||
.Services.Configure<TelegramBotSettings>(builder.Configuration.GetSection("TelegramBot"))
|
||||
.AddSingleton<IValidateOptions<TelegramBotSettings>, TelegramBotSettingsValidator>();
|
||||
@@ -36,36 +31,10 @@ try
|
||||
.Services.Configure<OllamaSettings>(builder.Configuration.GetSection("Ollama"))
|
||||
.AddSingleton<IValidateOptions<OllamaSettings>, OllamaSettingsValidator>();
|
||||
|
||||
builder.Services.Configure<SerilogSettings>(builder.Configuration.GetSection("Serilog"));
|
||||
|
||||
// Валидируем конфигурацию при старте
|
||||
builder.Services.AddOptions<TelegramBotSettings>().ValidateOnStart();
|
||||
builder.Services.AddOptions<OllamaSettings>().ValidateOnStart();
|
||||
|
||||
// Валидируем конфигурацию (старый способ для совместимости)
|
||||
var appSettings = builder.Configuration.Get<AppSettings>();
|
||||
if (appSettings == null)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal("Failed to load configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = ConfigurationValidator.ValidateAppSettings(appSettings);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal("Configuration validation failed:");
|
||||
foreach (var error in validationResult.Errors)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(" - {Error}", error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.ForContext<Program>().Debug("Configuration validation passed");
|
||||
|
||||
// Регистрируем FluentValidation валидаторы
|
||||
builder.Services.AddValidatorsFromAssemblyContaining<ChatMessageValidator>();
|
||||
|
||||
// Регистрируем IOllamaClient
|
||||
builder.Services.AddSingleton<IOllamaClient>(sp =>
|
||||
{
|
||||
@@ -76,13 +45,6 @@ try
|
||||
// Регистрируем интерфейсы и сервисы
|
||||
builder.Services.AddSingleton<ISessionStorage, InMemorySessionStorage>();
|
||||
|
||||
// Регистрируем error handlers
|
||||
builder.Services.AddSingleton<IErrorHandler, RateLimitErrorHandler>();
|
||||
builder.Services.AddSingleton<IErrorHandler, NetworkErrorHandler>();
|
||||
|
||||
// Регистрируем retry policy (использует error handlers)
|
||||
builder.Services.AddSingleton<IRetryPolicy, ExponentialBackoffRetryPolicy>();
|
||||
|
||||
// Регистрируем основные сервисы
|
||||
builder.Services.AddSingleton<ModelService>();
|
||||
builder.Services.AddSingleton<IAIService, AIService>();
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using ChatBot.Services.Interfaces;
|
||||
|
||||
namespace ChatBot.Services.ErrorHandlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Error handler for network-related errors
|
||||
/// </summary>
|
||||
public class NetworkErrorHandler : IErrorHandler
|
||||
{
|
||||
private readonly ILogger<NetworkErrorHandler> _logger;
|
||||
|
||||
public NetworkErrorHandler(ILogger<NetworkErrorHandler> 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<ErrorHandlingResult> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using ChatBot.Services.Interfaces;
|
||||
|
||||
namespace ChatBot.Services.ErrorHandlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Error handler for rate limit errors (HTTP 429)
|
||||
/// </summary>
|
||||
public class RateLimitErrorHandler : IErrorHandler
|
||||
{
|
||||
private readonly ILogger<RateLimitErrorHandler> _logger;
|
||||
|
||||
public RateLimitErrorHandler(ILogger<RateLimitErrorHandler> 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<ErrorHandlingResult> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using ChatBot.Common.Constants;
|
||||
using ChatBot.Models.Configuration;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ChatBot.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Retry policy with exponential backoff and jitter
|
||||
/// </summary>
|
||||
public class ExponentialBackoffRetryPolicy : IRetryPolicy
|
||||
{
|
||||
private readonly int _maxRetries;
|
||||
private readonly ILogger<ExponentialBackoffRetryPolicy> _logger;
|
||||
private readonly IEnumerable<IErrorHandler> _errorHandlers;
|
||||
|
||||
public ExponentialBackoffRetryPolicy(
|
||||
IOptions<OllamaSettings> settings,
|
||||
ILogger<ExponentialBackoffRetryPolicy> logger,
|
||||
IEnumerable<IErrorHandler> errorHandlers
|
||||
)
|
||||
{
|
||||
_maxRetries = settings.Value.MaxRetries;
|
||||
_logger = logger;
|
||||
_errorHandlers = errorHandlers;
|
||||
}
|
||||
|
||||
public async Task<T> ExecuteAsync<T>(
|
||||
Func<Task<T>> 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<bool> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ChatBot.Models;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ChatBot.Services
|
||||
{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
namespace ChatBot.Services.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for error handling strategy
|
||||
/// </summary>
|
||||
public interface IErrorHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if this handler can handle the exception
|
||||
/// </summary>
|
||||
bool CanHandle(Exception exception);
|
||||
|
||||
/// <summary>
|
||||
/// Handle the exception and return result
|
||||
/// </summary>
|
||||
Task<ErrorHandlingResult> HandleAsync(
|
||||
Exception exception,
|
||||
int attempt,
|
||||
string currentModel,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of error handling
|
||||
/// </summary>
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace ChatBot.Services.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for retry policy
|
||||
/// </summary>
|
||||
public interface IRetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute an action with retry logic
|
||||
/// </summary>
|
||||
Task<T> ExecuteAsync<T>(
|
||||
Func<Task<T>> action,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,24 +15,20 @@ namespace ChatBot.Services.Telegram.Services
|
||||
{
|
||||
private readonly ILogger<TelegramBotService> _logger;
|
||||
private readonly ITelegramBotClient _botClient;
|
||||
private readonly TelegramBotSettings _telegramBotSettings;
|
||||
private readonly ITelegramMessageHandler _messageHandler;
|
||||
private readonly ITelegramErrorHandler _errorHandler;
|
||||
|
||||
public TelegramBotService(
|
||||
ILogger<TelegramBotService> logger,
|
||||
IOptions<TelegramBotSettings> telegramBotSettings,
|
||||
ITelegramBotClient botClient,
|
||||
ITelegramMessageHandler messageHandler,
|
||||
ITelegramErrorHandler errorHandler
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_telegramBotSettings = telegramBotSettings.Value;
|
||||
_botClient = botClient;
|
||||
_messageHandler = messageHandler;
|
||||
_errorHandler = errorHandler;
|
||||
|
||||
ValidateConfiguration();
|
||||
_botClient = new TelegramBotClient(_telegramBotSettings.BotToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
248
README.md
248
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
|
||||
|
||||
@@ -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<T>` для явного представления успеха/неудачи операций:
|
||||
|
||||
**Файл:** `ChatBot/Common/Results/Result.cs`
|
||||
|
||||
```csharp
|
||||
var result = Result<string>.Success("данные");
|
||||
var failure = Result<string>.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<ErrorHandlingResult> 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<ITelegramCommand> commands)
|
||||
{
|
||||
foreach (var command in commands)
|
||||
RegisterCommand(command);
|
||||
}
|
||||
```
|
||||
|
||||
В `Program.cs`:
|
||||
```csharp
|
||||
builder.Services.AddSingleton<ITelegramCommand, StartCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, HelpCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, ClearCommand>();
|
||||
builder.Services.AddSingleton<ITelegramCommand, SettingsCommand>();
|
||||
```
|
||||
|
||||
#### **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<ChatMessage>
|
||||
{
|
||||
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<OllamaSettings>(...)
|
||||
.AddSingleton<IValidateOptions<OllamaSettings>, 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<OllamaHealthCheck>("ollama", tags: new[] { "api", "ollama" })
|
||||
.AddCheck<TelegramBotHealthCheck>("telegram", tags: new[] { "api", "telegram" });
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Мониторинг состояния сервисов
|
||||
- Быстрое обнаружение проблем
|
||||
- Интеграция с системами мониторинга
|
||||
|
||||
---
|
||||
|
||||
### 10. **CancellationToken Support**
|
||||
|
||||
Добавлена поддержка отмены операций во всех асинхронных методах:
|
||||
|
||||
```csharp
|
||||
public async Task<string> GenerateChatCompletionAsync(
|
||||
List<ChatMessage> messages,
|
||||
int? maxTokens = null,
|
||||
double? temperature = null,
|
||||
CancellationToken cancellationToken = default) // ✓
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Graceful shutdown приложения
|
||||
- Отмена долгих операций
|
||||
- Экономия ресурсов
|
||||
|
||||
---
|
||||
|
||||
### 11. **Новые пакеты**
|
||||
|
||||
Добавлены в `ChatBot.csproj`:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.10.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение "До" и "После"
|
||||
|
||||
### **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 использованию!**
|
||||
|
||||
Reference in New Issue
Block a user