audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration

This commit is contained in:
master
2026-01-14 10:48:00 +02:00
parent d7be6ba34b
commit 95d5898650
379 changed files with 40695 additions and 19041 deletions

View File

@@ -34,6 +34,7 @@ public sealed class AIPlugin : IDoctorPlugin
new LlmProviderConfigurationCheck(),
new ClaudeProviderCheck(),
new OpenAiProviderCheck(),
new GeminiProviderCheck(),
new OllamaProviderCheck(),
new LocalInferenceCheck()
];

View File

@@ -0,0 +1,192 @@
using System.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.AI.Checks;
/// <summary>
/// Validates Google Gemini API connectivity.
/// </summary>
public sealed class GeminiProviderCheck : IDoctorCheck
{
private const string DefaultModel = "gemini-1.5-flash";
private const string DefaultEndpoint = "https://generativelanguage.googleapis.com/v1beta";
/// <inheritdoc />
public string CheckId => "check.ai.provider.gemini";
/// <inheritdoc />
public string Name => "Gemini Provider";
/// <inheritdoc />
public string Description => "Validates Google Gemini API connectivity and authentication";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["ai", "llm", "gemini", "google", "advisoryai"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(10);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
var apiKey = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Gemini:ApiKey")
?? Environment.GetEnvironmentVariable("GEMINI_API_KEY")
?? Environment.GetEnvironmentVariable("GOOGLE_API_KEY");
return !string.IsNullOrWhiteSpace(apiKey);
}
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.ai", DoctorCategory.AI.ToString());
var apiKey = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Gemini:ApiKey")
?? Environment.GetEnvironmentVariable("GEMINI_API_KEY")
?? Environment.GetEnvironmentVariable("GOOGLE_API_KEY");
var endpoint = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Gemini:Endpoint")
?? DefaultEndpoint;
var model = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Gemini:Model")
?? DefaultModel;
if (string.IsNullOrWhiteSpace(apiKey))
{
return result
.Skip("Gemini API key not configured")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("ApiKeyConfigured", "false");
})
.Build();
}
var httpClientFactory = context.Services.GetService<IHttpClientFactory>();
if (httpClientFactory == null)
{
return result
.Skip("HttpClientFactory not available")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("Error", "IHttpClientFactory not registered");
})
.Build();
}
try
{
using var client = httpClientFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(10);
// List models to validate API key (lightweight call)
using var response = await client.GetAsync($"{endpoint}/models?key={apiKey}", ct);
if (response.IsSuccessStatusCode)
{
return result
.Pass("Gemini API is accessible")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("Model", model);
e.Add("ApiKeyConfigured", "true (masked)");
e.Add("StatusCode", ((int)response.StatusCode).ToString());
})
.Build();
}
var errorBody = await response.Content.ReadAsStringAsync(ct);
var statusCode = (int)response.StatusCode;
var issues = new List<string>();
if (statusCode == 400)
{
issues.Add("Invalid request - check API key format");
}
else if (statusCode == 401)
{
issues.Add("Invalid API key");
}
else if (statusCode == 403)
{
issues.Add("Access forbidden - check API key permissions or enable Generative Language API");
}
else if (statusCode == 429)
{
issues.Add("Rate limited - too many requests");
}
else
{
issues.Add($"API returned status {statusCode}");
}
return result
.Warn($"Gemini API issue: {response.StatusCode}")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("Model", model);
e.Add("StatusCode", statusCode.ToString());
e.Add("Error", TruncateError(errorBody));
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Verify API key", "Check GEMINI_API_KEY or GOOGLE_API_KEY is valid")
.AddManualStep(2, "Enable API", "Ensure Generative Language API is enabled in Google Cloud Console")
.AddManualStep(3, "Check quotas", "Verify API usage limits in Google Cloud Console"))
.WithVerification("stella doctor --check check.ai.provider.gemini")
.Build();
}
catch (HttpRequestException ex)
{
return result
.Fail($"Cannot connect to Gemini API: {ex.Message}")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("Error", ex.Message);
})
.WithCauses("Network connectivity issue or invalid endpoint")
.WithRemediation(r => r
.AddManualStep(1, "Check network", "Verify network connectivity to generativelanguage.googleapis.com")
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required"))
.WithVerification("stella doctor --check check.ai.provider.gemini")
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Fail($"Gemini API error: {ex.Message}")
.WithEvidence("Gemini provider", e =>
{
e.Add("Endpoint", endpoint);
e.Add("Error", ex.GetType().Name);
})
.Build();
}
}
private static string TruncateError(string error, int maxLength = 200)
{
if (string.IsNullOrWhiteSpace(error))
{
return "(empty)";
}
if (error.Length <= maxLength)
{
return error;
}
return error[..maxLength] + "...";
}
}

View File

@@ -73,6 +73,15 @@ public sealed class LlmProviderConfigurationCheck : IDoctorCheck
configuredProviders.Add("OpenAI");
}
// Check Gemini configuration
var geminiApiKey = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Gemini:ApiKey")
?? Environment.GetEnvironmentVariable("GEMINI_API_KEY")
?? Environment.GetEnvironmentVariable("GOOGLE_API_KEY");
if (!string.IsNullOrWhiteSpace(geminiApiKey))
{
configuredProviders.Add("Gemini");
}
// Check Ollama configuration
var ollamaEndpoint = context.Configuration.GetValue<string>("AdvisoryAI:LlmProviders:Ollama:Endpoint")
?? "http://localhost:11434";
@@ -96,6 +105,7 @@ public sealed class LlmProviderConfigurationCheck : IDoctorCheck
{
"claude" => configuredProviders.Contains("Claude"),
"openai" => configuredProviders.Contains("OpenAI"),
"gemini" => configuredProviders.Contains("Gemini"),
"ollama" => configuredProviders.Contains("Ollama"),
"llamacpp" or "llama" => configuredProviders.Contains("Llama.cpp"),
_ => false