audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration
This commit is contained in:
@@ -34,6 +34,7 @@ public sealed class AIPlugin : IDoctorPlugin
|
||||
new LlmProviderConfigurationCheck(),
|
||||
new ClaudeProviderCheck(),
|
||||
new OpenAiProviderCheck(),
|
||||
new GeminiProviderCheck(),
|
||||
new OllamaProviderCheck(),
|
||||
new LocalInferenceCheck()
|
||||
];
|
||||
|
||||
@@ -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] + "...";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user