using System; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Predictalytics.Application.Interfaces; namespace Predictalytics.Infrastructure.Providers.OpenRouter; public class OpenRouterApiClient : IOpenRouterApiClient { private readonly HttpClient _httpClient; private readonly IConfiguration _config; private readonly ILogger _logger; /// /// Ohne Schluessel wird gar nicht erst gesendet. Sonst quittiert OpenRouter jeden /// Aufruf mit 401, das landet als Error im Fehler-Stream des Deployment Centers und /// der Aufrufer versucht anschliessend, die Fehlermeldung als JSON zu lesen. /// private readonly bool _configured; public OpenRouterApiClient(HttpClient httpClient, IConfiguration config, ILogger logger) { _httpClient = httpClient; _config = config; _logger = logger; var baseUrl = _config["OpenRouter:BaseUrl"] ?? "https://openrouter.ai/api/v1"; var apiKey = _config["OpenRouter:ApiKey"]; _httpClient.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"); _configured = !string.IsNullOrWhiteSpace(apiKey); if (_configured) { _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); } else { // Warning, nicht Error: eine fehlende KI-Anbindung ist ein Konfigurationszustand, // kein Stoerfall - die uebrige Analyse laeuft vollstaendig ohne sie. _logger.LogWarning("OpenRouter ist nicht konfiguriert (OpenRouter:ApiKey leer) - die KI-Strategieanalyse bleibt aus. Schluessel ueber die Umgebungsvariable OpenRouter__ApiKey setzen."); } // OpenRouter recommends adding a referer and title for ranking _httpClient.DefaultRequestHeaders.Add("HTTP-Referer", "http://localhost"); _httpClient.DefaultRequestHeaders.Add("X-Title", "Predictalytics"); } public async Task GenerateChatCompletionAsync(string prompt, bool useManualModel = false, CancellationToken ct = default) { if (!_configured) return IOpenRouterApiClient.NotConfigured; var model = useManualModel ? _config["OpenRouter:ManualAnalysisModel"] ?? "anthropic/claude-3-opus" : _config["OpenRouter:DefaultModel"] ?? "google/gemini-flash-1.5"; var requestBody = new { model = model, messages = new[] { new { role = "system", content = "You are an expert crypto and prediction market analyst. You analyze a trader's history and deduce their strategy, strengths, and weaknesses." }, new { role = "user", content = prompt } } }; try { var response = await _httpClient.PostAsJsonAsync("chat/completions", requestBody, ct); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(cancellationToken: ct); return result?.Choices?[0]?.Message?.Content ?? "No response generated."; } catch (Exception ex) { _logger.LogError(ex, "Failed to generate chat completion from OpenRouter using model {Model}", model); return $"Error: {ex.Message}"; } } private class OpenRouterResponse { [JsonPropertyName("choices")] public Choice[]? Choices { get; set; } } private class Choice { [JsonPropertyName("message")] public Message? Message { get; set; } } private class Message { [JsonPropertyName("content")] public string? Content { get; set; } } }