fix: OpenRouter-STT auf JSON/base64 statt multipart (passend zur OpenRouter-API)

Live-Test gegen OpenRouter ergab: /audio/transcriptions erwartet JSON mit
input_audio:{data(base64),format} - NICHT multipart/form-data. Die fruehere
Umstellung auf multipart (OpenAI-Standard) war fuer OpenRouter falsch.

- STT-Provider zurueck auf JSON/base64 (mit robuster Fehlerbehandlung)
- verifiziert per TTS->WAV->STT Round-Trip: Text korrekt zurueck transkribiert
- LLM, TTS und STT funktionieren jetzt alle live gegen OpenRouter
- Doku-Referenz aktualisiert (multipart -> JSON/base64)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-17 09:31:34 +02:00
commit 1d338810b1
2 changed files with 18 additions and 9 deletions

View file

@ -182,7 +182,7 @@ Endpunkt-/Provider-Auswahl ist über **Request-Body** (pro Aufruf), **Session**
## 8. Stand der Implementierung ## 8. Stand der Implementierung
**Umgesetzt:** FastAPI-Gateway, alle o. g. REST-Endpunkte; OpenRouter-Adapter für **Umgesetzt:** FastAPI-Gateway, alle o. g. REST-Endpunkte; OpenRouter-Adapter für
STT (multipart), LLM und TTS; lokaler OpenAI-kompatibler LLM-Adapter; regelbasierte STT (JSON/base64), LLM und TTS; lokaler OpenAI-kompatibler LLM-Adapter; regelbasierte
Pipeline; geschichtete Config + Profile; Registry + einheitliche Route-Auflösung; Pipeline; geschichtete Config + Profile; Registry + einheitliche Route-Auflösung;
Device Router (strikt, Singleton); Output-Lifecycle; **Authentifizierung Device Router (strikt, Singleton); Output-Lifecycle; **Authentifizierung
(Bearer-Token) + persistenter SQLite-Store für Nutzer/Sessions + Mandanten-Trennung (Bearer-Token) + persistenter SQLite-Store für Nutzer/Sessions + Mandanten-Trennung

View file

@ -1,3 +1,5 @@
import base64
import httpx import httpx
from app.providers.stt.base import STTProvider from app.providers.stt.base import STTProvider
@ -16,12 +18,17 @@ class OpenRouterSTTProvider(STTProvider):
if not audio_bytes: if not audio_bytes:
raise ValueError("STT input audio is empty") raise ValueError("STT input audio is empty")
# OpenAI-kompatibler /audio/transcriptions-Endpunkt erwartet multipart/form-data # OpenRouter /audio/transcriptions erwartet JSON mit base64-Audio
# mit binärem file-Feld, nicht JSON mit base64. # (NICHT multipart/form-data). Quelle: OpenRouter-Doku (STT).
files = {"file": (f"audio.{fmt}", audio_bytes, f"audio/{fmt}")} payload = {
data: dict[str, str] = {"model": self.model} "model": self.model,
"input_audio": {
"data": base64.b64encode(audio_bytes).decode("utf-8"),
"format": fmt,
},
}
if language: if language:
data["language"] = language payload["language"] = language
timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0) timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0)
@ -29,9 +36,11 @@ class OpenRouterSTTProvider(STTProvider):
try: try:
response = await client.post( response = await client.post(
"https://openrouter.ai/api/v1/audio/transcriptions", "https://openrouter.ai/api/v1/audio/transcriptions",
headers={"Authorization": f"Bearer {self.api_key}"}, headers={
files=files, "Authorization": f"Bearer {self.api_key}",
data=data, "Content-Type": "application/json",
},
json=payload,
) )
response.raise_for_status() response.raise_for_status()
except httpx.HTTPStatusError as exc: except httpx.HTTPStatusError as exc: