From 1d338810b1333eb742dc40173d7d44cb8efdffb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Schl=C3=BCter?= Date: Wed, 17 Jun 2026 09:31:34 +0200 Subject: [PATCH] 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 --- Docs/voice-assistant-architecture.md | 2 +- app/providers/stt/openrouter.py | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Docs/voice-assistant-architecture.md b/Docs/voice-assistant-architecture.md index c842fcf..ca7c72c 100644 --- a/Docs/voice-assistant-architecture.md +++ b/Docs/voice-assistant-architecture.md @@ -182,7 +182,7 @@ Endpunkt-/Provider-Auswahl ist über **Request-Body** (pro Aufruf), **Session** ## 8. Stand der Implementierung **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; Device Router (strikt, Singleton); Output-Lifecycle; **Authentifizierung (Bearer-Token) + persistenter SQLite-Store für Nutzer/Sessions + Mandanten-Trennung diff --git a/app/providers/stt/openrouter.py b/app/providers/stt/openrouter.py index 81a3f6a..45f02dc 100644 --- a/app/providers/stt/openrouter.py +++ b/app/providers/stt/openrouter.py @@ -1,3 +1,5 @@ +import base64 + import httpx from app.providers.stt.base import STTProvider @@ -16,12 +18,17 @@ class OpenRouterSTTProvider(STTProvider): if not audio_bytes: raise ValueError("STT input audio is empty") - # OpenAI-kompatibler /audio/transcriptions-Endpunkt erwartet multipart/form-data - # mit binärem file-Feld, nicht JSON mit base64. - files = {"file": (f"audio.{fmt}", audio_bytes, f"audio/{fmt}")} - data: dict[str, str] = {"model": self.model} + # OpenRouter /audio/transcriptions erwartet JSON mit base64-Audio + # (NICHT multipart/form-data). Quelle: OpenRouter-Doku (STT). + payload = { + "model": self.model, + "input_audio": { + "data": base64.b64encode(audio_bytes).decode("utf-8"), + "format": fmt, + }, + } if language: - data["language"] = language + payload["language"] = language timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0) @@ -29,9 +36,11 @@ class OpenRouterSTTProvider(STTProvider): try: response = await client.post( "https://openrouter.ai/api/v1/audio/transcriptions", - headers={"Authorization": f"Bearer {self.api_key}"}, - files=files, - data=data, + headers={ + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json", + }, + json=payload, ) response.raise_for_status() except httpx.HTTPStatusError as exc: