Phase 1 Tasks Completed: - Add AI configuration sections to appsettings.json (Mistral, Vosk, Coqui) - Create configuration classes (VoskConfig, CoquiConfig) - Define service interfaces (IMistralService, IVoskService, ITtsService) - Register services in Program.cs - Set up configuration validation (ValidateAiConfigurations method) - Add health checks for AI services (AiServicesHealthCheck class) Files changed: - Added AiServicesHealthCheck.cs (Infrastructure layer) - Updated Program.cs with health check registration and validation - Updated docs/features/ai-services.md with Phase 1 completion All 296 tests passing (148 unit + 148 integration) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
149 lines
5.3 KiB
C#
149 lines
5.3 KiB
C#
using GermanApp.Domain.Interfaces;
|
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace GermanApp.Infrastructure.Services;
|
|
|
|
/// <summary>
|
|
/// Health check for AI services (Mistral, Vosk, Coqui TTS).
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class AiServicesHealthCheck : IHealthCheck
|
|
{
|
|
private readonly IMistralService? _mistralService;
|
|
private readonly IVoskService? _voskService;
|
|
private readonly ITtsService? _ttsService;
|
|
private readonly ILogger<AiServicesHealthCheck> _logger;
|
|
|
|
/// <summary>
|
|
/// Creates a new AI services health check.
|
|
/// </summary>
|
|
/// <param name="mistralService">The Mistral text generation service (optional, may be null in some environments)</param>
|
|
/// <param name="voskService">The Vosk speech recognition service (optional, may be null in some environments)</param>
|
|
/// <param name="ttsService">The Coqui TTS service (optional, may be null in some environments)</param>
|
|
/// <param name="logger">Logger for health check operations</param>
|
|
public AiServicesHealthCheck(
|
|
IMistralService? mistralService,
|
|
IVoskService? voskService,
|
|
ITtsService? ttsService,
|
|
ILogger<AiServicesHealthCheck> logger)
|
|
{
|
|
_mistralService = mistralService;
|
|
_voskService = voskService;
|
|
_ttsService = ttsService;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the health check for all AI services.
|
|
/// </summary>
|
|
public async Task<HealthCheckResult> CheckHealthAsync(
|
|
HealthCheckContext context,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var checks = new Dictionary<string, HealthStatus>();
|
|
var exceptions = new Dictionary<string, Exception>();
|
|
|
|
// 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<string, object>();
|
|
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);
|
|
}
|
|
}
|