Files
gott/Plans/[DONE] OPENAI_PROVIDER.md
T

9.3 KiB
Raw Blame History

Plan: OpenAI als nativer Provider

Übersicht

OpenAI (GPT-4o, GPT-5, o3, etc.) als vollwertigen Provider integrieren – gleichwertig mit Anthropic. Der Key kommt aus der api_keys-Tabelle (Provider: openai), nicht aus appsettings.json.

Ist-Zustand

Architektur

  • AnthropicService – Haupt-Provider, voller Feature-Support (Tools, Review, Preparer, Finalizer, Web-Search, Memory, ask_user_choice, Plan-Mode)
  • OpenAiService – "Neben-Provider" für Nvidia, Pollinations, Gemini. Limitiert:
    • Nur 6 Tools (list_files, read_file, write_file, edit_file, delete_file, execute)
    • Kein Review/Preparer/Finalizer
    • Kein Memory, Web-Search, ask_user_choice
    • API-Key hardcoded im Providers-Dictionary (nicht aus DB)
    • maxIterations fest auf 10

API-Key-System

  • User wählt einen Key aus der api_keys-Tabelle (selected_api_key_id)
  • Aktuell nur für Anthropic genutzt (userApiKey.ApiKey → anthropic.StreamChat())
  • OpenAi-Provider nutzen eigene Keys aus Providers-Dict (Gemini aus appsettings.json, Nvidia/Pollinations keyless)

Modell-Auswahl

  • Agent hat Model-Feld, z.B. sonnet-4-6 oder nvidia:model-id
  • ChatService.ResolveModelId() mapped Anthropic-Kurzformen, OpenAI-Provider werden durchgereicht
  • Frontend: <select> mit Anthropic-Optionen + Optgroups für Nvidia/Pollinations/Gemini

Soll-Zustand

Ziel

Ein Agent mit Model openai:gpt-4o nutzt den aktiven OpenAI-Key des Users aus der api_keys-Tabelle. Voller Tool-Support, alle 17 Tools.

Architektur-Entscheidung

Option A: OpenAI als neuen Provider in OpenAiService einbauen → Einfach, aber OpenAiService hat nur 6 Tools und kein Review/Preparer/Finalizer.

Option B: OpenAI direkt über OpenAiService mit vollem Tool-Support → Alle 17 Tools + Memory + Review etc. erfordert massiven Umbau des OpenAiService.

Option C: OpenAI über AnthropicService (Anthropic-API-kompatibel) → Nicht möglich, OpenAI hat ein anderes API-Format.

→ Empfehlung: Option A (erweitert) OpenAI als Provider in OpenAiService mit ALLEN Tools (nicht nur 6). Review/Preparer/Finalizer erstmal nicht für OpenAI – das kann später kommen.

Umsetzungsplan

Phase 1: Backend – OpenAI Provider Registrierung

Datei: Services/OpenAiService.cs

  1. "openai" zu ProviderKeys hinzufügen:

    public static readonly HashSet<string> ProviderKeys = new() { "nvidia", "pollinations", "gemini", "openai" };
    
  2. OpenAI Provider-Config NICHT hardcoden, sondern dynamisch aus DB laden. Problem: Providers-Dict wird im Konstruktor befüllt, aber der API-Key kommt zur Laufzeit aus api_keys (user-spezifisch).

    Lösung: StreamChat() bekommt optionalen apiKey-Parameter. Wenn Provider = openai → BaseUrl https://api.openai.com/v1, Key aus Parameter.

    // Im Konstruktor:
    ["openai"] = ("https://api.openai.com/v1", "")  // Key kommt zur Laufzeit
    
    // In StreamChat():
    public async Task StreamChat(..., string apiKey = null, ...)
    {
        var providerConfig = Providers[provider];
        var effectiveKey = !string.IsNullOrEmpty(apiKey) ? apiKey : providerConfig.ApiKey;
        var client = CreateClient(effectiveKey);
    }
    
  3. Tools erweitern – ALLE 17 Tools statt nur 6. Problem: Aktuell sind die Tools als statische anonyme Objekte definiert (OpenAiTools). Besser: Tools dynamisch aus ToolService.GetToolDefinitions() konvertieren.

    // Neue Methode: Anthropic-Tool-Defs → OpenAI-Format konvertieren
    private static List<object> ConvertToolsToOpenAiFormat(object[] anthropicTools)
    {
        // Anthropic: { name, description, input_schema: { type, properties, required } }
        // OpenAI:    { type: "function", function: { name, description, parameters: { type, properties, required } } }
    }
    
  4. maxIterations aus ChatContext übernehmen statt fest 10.

Phase 2: Backend – API-Key Routing

Datei: Endpoints/ChatEndpoint.cs

Aktuell:

if (!string.IsNullOrEmpty(ctx.Provider))
    await openAi.StreamChat(...); // kein API-Key
else
    await anthropic.StreamChat(..., apiToken: userApiKey.ApiKey);

Neu: Wenn Provider = openai → Key aus api_keys-Tabelle durchreichen:

if (!string.IsNullOrEmpty(ctx.Provider))
{
    // Bei OpenAI: User-API-Key aus DB verwenden
    var providerApiKey = ctx.Provider == "openai" ? userApiKey?.ApiKey : null;
    await openAi.StreamChat(..., apiKey: providerApiKey, maxIterations: ctx.MaxIterations, ...);
}

Problem: Der User hat aktuell EINEN selected_api_key_id. Wenn er Anthropic nutzt, ist das ein Anthropic-Key. Wenn er OpenAI nutzt, braucht er einen OpenAI-Key.

Lösung: GetUserSelectedApiKey um Provider-Filterung erweitern:

  • ODER: Automatisch den passenden Key basierend auf Agent-Model wählen.
  • ODER: Pro User mehrere Keys (einen pro Provider) erlauben.

→ Empfehlung: Automatische Key-Auswahl basierend auf Provider. Neuer DB-Call: GetActiveApiKeyForProvider(userId, provider) – gibt den ersten aktiven Key des Users für diesen Provider zurück.

SELECT k.* FROM api_keys k
WHERE k.provider = @provider
  AND k.is_active = 1
  AND (k.is_public = 1 OR k.key_id = (SELECT selected_api_key_id FROM users WHERE user_id = @userId))
ORDER BY k.is_public ASC  -- User-eigener Key bevorzugt
LIMIT 1

Noch einfacher: Der User wählt weiterhin EINEN Key, aber das System erkennt den Provider anhand des Keys und routet automatisch. → User braucht einen OpenAI-Key in der api_keys-Tabelle.

Einfachste Lösung:

// In ChatEndpoint:
ApiKeyRecord resolvedKey;
if (ctx.Provider == "openai")
    resolvedKey = await db.GetFirstActiveKeyByProvider("openai");
else
    resolvedKey = userApiKey; // Anthropic wie bisher

if (resolvedKey == null) { /* Fehler: Kein Key für Provider */ }

Phase 3: Backend – Modell-Liste

Datei: Services/OpenAiService.cs

OpenAI-Modelle dynamisch laden:

private List<ProviderModel> _openaiModels = new();

// In RefreshModelsFromApis():
var openAiKey = await _db.GetFirstActiveKeyByProvider("openai");
if (openAiKey != null)
    _openaiModels = await FetchModels("https://api.openai.com/v1/models", "OpenAI", openAiKey.ApiKey);

Problem: RefreshModelsFromApis() hat keinen Zugriff auf user-spezifische Keys. → Den ersten aktiven OpenAI-Key aus der DB nehmen (egal welcher User).

Datei: Endpoints/ModelsEndpoint.cs

Neuen Endpoint:

app.MapGet("/api/models/openai", HandleOpenAiModels);

Phase 4: Frontend – Modell-Auswahl

Datei: wwwroot/index.html

Neue Optgroup im Agent-Model-Select:

<optgroup label="OpenAI" id="openaiOptgroup">
  <option value="" disabled>Wird geladen...</option>
</optgroup>

Datei: wwwroot/js/pages/agent-page.js

OpenAI-Modelle laden:

loadProviderModels('/api/models/openai', 'openai', openaiOptgroup)

Phase 5: Frontend – Key-Switch-Modal

Datei: wwwroot/js/pages/chat-page.js

Im Key-Switch-Modal den Provider des Keys anzeigen:

  • Anthropic-Keys: 🟤 Anthropic · Pro · ✅
  • OpenAI-Keys: 🟢 OpenAI · Tier 5 · ✅

Aktuell zeigt das Modal alle Keys unabhängig vom Provider. → Keys nach Provider gruppieren oder filtern (nur passende Keys zeigen).

Phase 6: Usage-Tracking (optional)

Datei: Services/OpenAiService.cs

Nach jedem OpenAI-API-Call die Usage aus dem Response extrahieren und in api_keys updaten (analog zu AnthropicService.TryUpdateKeyMeta()).

OpenAI Response-Header enthalten keine Usage-Infos wie Anthropic. Aber der letzte SSE-Chunk enthält usage: { prompt_tokens, completion_tokens }. → Nach dem Stream die Token-Counts aus dem letzten Chunk in die DB schreiben.

Dateien & Aufwand

Datei Änderung Aufwand
Services/OpenAiService.cs openai Provider, API-Key-Parameter, alle Tools, maxIterations Mittel
Endpoints/ChatEndpoint.cs API-Key-Routing basierend auf Provider Niedrig
Endpoints/ModelsEndpoint.cs GET /api/models/openai Niedrig
Services/DatabaseService.Integrations.cs GetFirstActiveKeyByProvider(provider) Niedrig
wwwroot/index.html OpenAI Optgroup Minimal
wwwroot/js/pages/agent-page.js OpenAI-Modelle laden Minimal
wwwroot/js/pages/chat-page.js Provider-Info im Key-Switch-Modal Niedrig

Gesamt: ~200-250 Zeilen Änderungen, Aufwand mittel

Risiken

  1. Tool-Format-Differenzen – OpenAI und Anthropic haben leicht unterschiedliche Tool-Call/Result-Formate. Die Konvertierung in ConvertMessage() muss stimmen. → Besteht schon für Nvidia/Gemini, sollte auch für OpenAI funktionieren.

  2. Streaming-Format – OpenAI SSE-Format unterscheidet sich von Anthropic. → Wird bereits im OpenAiService korrekt gehandelt.

  3. Rate-Limits – OpenAI Rate-Limits sind pro Organisation, nicht pro Key. → Kein spezifisches Handling nötig, 429 wird als Fehler angezeigt.

  4. Modell-Liste zu lang – OpenAI hat ~43 Modelle (inkl. Legacy, Embedding, TTS). → Filtern auf Chat-Modelle: nur gpt-*, o1-*, o3-*, chatgpt-*.

Nicht im Scope (später)

  • Review/Preparer/Finalizer für OpenAI-Provider
  • ask_user_choice für OpenAI-Provider
  • Web-Search für OpenAI-Provider
  • Memory-Tools für OpenAI-Provider (aktuell nur 6 Tools) → Diese Features erfordern einen größeren Umbau des OpenAiService