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

254 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```csharp
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.
```csharp
// 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.
```csharp
// 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:
```csharp
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:
```csharp
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.
```sql
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:**
```csharp
// 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:
```csharp
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:
```csharp
app.MapGet("/api/models/openai", HandleOpenAiModels);
```
### Phase 4: Frontend – Modell-Auswahl
**Datei: `wwwroot/index.html`**
Neue Optgroup im Agent-Model-Select:
```html
<optgroup label="OpenAI" id="openaiOptgroup">
<option value="" disabled>Wird geladen...</option>
</optgroup>
```
**Datei: `wwwroot/js/pages/agent-page.js`**
OpenAI-Modelle laden:
```javascript
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