254 lines
9.3 KiB
Markdown
254 lines
9.3 KiB
Markdown
# 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
|