diff --git a/GermanApp/Infrastructure/Services/AiServicesHealthCheck.cs b/GermanApp/Infrastructure/Services/AiServicesHealthCheck.cs
new file mode 100644
index 0000000..9c58b71
--- /dev/null
+++ b/GermanApp/Infrastructure/Services/AiServicesHealthCheck.cs
@@ -0,0 +1,149 @@
+using GermanApp.Domain.Interfaces;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+
+namespace GermanApp.Infrastructure.Services;
+
+///
+/// Health check for AI services (Mistral, Vosk, Coqui TTS).
+/// This is part of the Infrastructure layer.
+///
+public class AiServicesHealthCheck : IHealthCheck
+{
+ private readonly IMistralService? _mistralService;
+ private readonly IVoskService? _voskService;
+ private readonly ITtsService? _ttsService;
+ private readonly ILogger _logger;
+
+ ///
+ /// Creates a new AI services health check.
+ ///
+ /// The Mistral text generation service (optional, may be null in some environments)
+ /// The Vosk speech recognition service (optional, may be null in some environments)
+ /// The Coqui TTS service (optional, may be null in some environments)
+ /// Logger for health check operations
+ public AiServicesHealthCheck(
+ IMistralService? mistralService,
+ IVoskService? voskService,
+ ITtsService? ttsService,
+ ILogger logger)
+ {
+ _mistralService = mistralService;
+ _voskService = voskService;
+ _ttsService = ttsService;
+ _logger = logger;
+ }
+
+ ///
+ /// Executes the health check for all AI services.
+ ///
+ public async Task CheckHealthAsync(
+ HealthCheckContext context,
+ CancellationToken cancellationToken = default)
+ {
+ var checks = new Dictionary();
+ var exceptions = new Dictionary();
+
+ // Check Mistral service
+ if (_mistralService != null)
+ {
+ try
+ {
+ var isHealthy = await _mistralService.TestConnectionAsync(cancellationToken);
+ checks["Mistral"] = isHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy;
+ if (!isHealthy)
+ {
+ _logger.LogWarning("Mistral service health check failed");
+ }
+ }
+ catch (Exception ex)
+ {
+ checks["Mistral"] = HealthStatus.Unhealthy;
+ exceptions["Mistral"] = ex;
+ _logger.LogError(ex, "Mistral service health check failed with exception");
+ }
+ }
+ else
+ {
+ checks["Mistral"] = HealthStatus.Degraded;
+ _logger.LogWarning("Mistral service is not registered");
+ }
+
+ // Check Vosk service
+ if (_voskService != null)
+ {
+ try
+ {
+ var isHealthy = await _voskService.TestModelAsync(cancellationToken);
+ checks["Vosk"] = isHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy;
+ if (!isHealthy)
+ {
+ _logger.LogWarning("Vosk service health check failed");
+ }
+ }
+ catch (Exception ex)
+ {
+ checks["Vosk"] = HealthStatus.Unhealthy;
+ exceptions["Vosk"] = ex;
+ _logger.LogError(ex, "Vosk service health check failed with exception");
+ }
+ }
+ else
+ {
+ checks["Vosk"] = HealthStatus.Degraded;
+ _logger.LogWarning("Vosk service is not registered");
+ }
+
+ // Check Coqui TTS service
+ if (_ttsService != null)
+ {
+ try
+ {
+ var isHealthy = await _ttsService.TestModelAsync(cancellationToken);
+ checks["Coqui TTS"] = isHealthy ? HealthStatus.Healthy : HealthStatus.Unhealthy;
+ if (!isHealthy)
+ {
+ _logger.LogWarning("Coqui TTS service health check failed");
+ }
+ }
+ catch (Exception ex)
+ {
+ checks["Coqui TTS"] = HealthStatus.Unhealthy;
+ exceptions["Coqui TTS"] = ex;
+ _logger.LogError(ex, "Coqui TTS service health check failed with exception");
+ }
+ }
+ else
+ {
+ checks["Coqui TTS"] = HealthStatus.Degraded;
+ _logger.LogWarning("Coqui TTS service is not registered");
+ }
+
+ // Determine overall status
+ var allHealthy = checks.Values.All(s => s == HealthStatus.Healthy);
+ var anyUnhealthy = checks.Values.Any(s => s == HealthStatus.Unhealthy);
+ var anyDegraded = checks.Values.Any(s => s == HealthStatus.Degraded);
+
+ HealthStatus overallStatus = allHealthy
+ ? HealthStatus.Healthy
+ : anyUnhealthy
+ ? HealthStatus.Unhealthy
+ : HealthStatus.Degraded;
+
+ // Build data dictionary with details
+ var data = new Dictionary();
+ foreach (var check in checks)
+ {
+ data[check.Key] = new
+ {
+ Status = check.Value.ToString(),
+ Exception = exceptions.TryGetValue(check.Key, out var ex) ? ex.Message : null
+ };
+ }
+
+ return new HealthCheckResult(
+ overallStatus,
+ "AI Services health check",
+ data: data);
+ }
+}
diff --git a/GermanApp/Program.cs b/GermanApp/Program.cs
index 77928c1..886c48d 100644
--- a/GermanApp/Program.cs
+++ b/GermanApp/Program.cs
@@ -49,7 +49,8 @@ try
// Add Health Checks
builder.Services.AddHealthChecks()
- .AddDbContextCheck();
+ .AddDbContextCheck()
+ .AddCheck("ai_services");
// Add Password Hasher for custom User entity
builder.Services.AddScoped, PasswordHasher>();
@@ -168,6 +169,9 @@ try
builder.Services.Configure(builder.Configuration.GetSection("Vosk"));
builder.Services.Configure(builder.Configuration.GetSection("Coqui"));
+ // Validate AI service configurations
+ ValidateAiConfigurations(builder.Configuration);
+
// Register AI services (Infrastructure implementations of Domain interfaces)
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -269,6 +273,24 @@ try
app.Run();
+// Helper method to validate AI service configurations
+static void ValidateAiConfigurations(IConfiguration configuration)
+{
+ // Validate Mistral configuration
+ var mistralConfig = configuration.GetSection("Mistral").Get() ?? new MistralConfig();
+ mistralConfig.Validate();
+
+ // Validate Vosk configuration
+ var voskConfig = configuration.GetSection("Vosk").Get() ?? new VoskConfig();
+ voskConfig.Validate();
+
+ // Validate Coqui configuration
+ var coquiConfig = configuration.GetSection("Coqui").Get() ?? new CoquiConfig();
+ coquiConfig.Validate();
+
+ Log.Information("All AI service configurations validated successfully");
+}
+
// Helper method to apply migrations with retry logic for Docker
static async Task ApplyMigrationsWithRetry(WebApplication app, int maxRetries, int delaySeconds)
{
diff --git a/docs/features/ai-services.md b/docs/features/ai-services.md
index 7761a2a..02914da 100644
--- a/docs/features/ai-services.md
+++ b/docs/features/ai-services.md
@@ -200,12 +200,12 @@ For production deployment via Woodpecker:
- Resilient to API failures (retry, rate limiting, circuit breaker, caching)
- Testable with mocked HTTP client
-### Phase 1: Configuration ### Phase 1: Configuration & Interfaces (2 hours) Interfaces (2 hours) ✅
+### Phase 1: Configuration ### Phase 1: Configuration ### Phase 1: Configuration & Interfaces (2 hours) Interfaces (2 hours) ✅ Interfaces (2 hours) ✅
- [x] Add AI configuration section to appsettings.json
-- [ ] Create configuration classes (MistralConfig, VoskConfig, CoquiConfig)
-- [ ] Define service interfaces (IMistralService, IVoskService, ITtsService)
-- [ ] Register services in Program.cs
-- [ ] Set up configuration validation
+- [x] Create configuration classes (MistralConfig, VoskConfig, CoquiConfig)
+- [x] Define service interfaces (IMistralService, IVoskService, ITtsService)
+- [x] Register services in Program.cs
+- [x] Set up configuration validation (ValidateAiConfigurations method added to Program.cs)
### Phase 2: Mistral-Medium Integration (2-3 hours) ✅
- [ ] Create MistralService implementation
@@ -275,14 +275,14 @@ For production deployment via Woodpecker:
### Backend - Configuration
- [x] Create Configuration/MistralConfig.cs
-- [ ] Add Mistral settings to appsettings.json (MistralConfig already registered)
-- [ ] Add Vosk settings to appsettings.json (VoskConfig registered, needs model path)
-- [ ] Add Coqui settings to appsettings.json (CoquiConfig registered, needs model name)
+- [x] Add Mistral settings to appsettings.json
+- [x] Add Vosk settings to appsettings.json
+- [x] Add Coqui settings to appsettings.json
- [x] Create Configuration/VoskConfig.cs
- [x] Create Configuration/CoquiConfig.cs
- [x] Register Mistral Connector in Program.cs
- [x] Register all AI services in Program.cs
-- [ ] Add health checks for AI services
+- [x] Add health checks for AI services (AiServicesHealthCheck.cs created)
### Backend - Mistral Service
- [x] Create `Domain/Interfaces/IMistralService.cs`