feat(stt): echtes lokales STT via faster-whisper (optional .[local])

- FasterWhisperProvider implementiert (CTranslate2): Modell prozessweit gecacht
  (lru_cache), Transkription in asyncio.to_thread; robuster CPU-Fallback wenn
  GPU/compute_type nicht verfuegbar
- Config: FASTER_WHISPER_MODEL/DEVICE/COMPUTE_TYPE (Defaults base/auto/default)
- pyproject: optionales Extra [local] = faster-whisper
- Test: transcribe-Endpunkt nutzt jetzt einen Stub-STT (kein Platzhalter mehr)
- Doku: Voraussetzungen, Hybrid-Beispiel (STT+LLM lokal, TTS remote), Architektur/README

Live verifiziert: TTS->WAV->faster-whisper transkribiert korrekt (de); voller Hybrid
ueber /ws/voice (faster-whisper + Ollama llama3.2 + OpenRouter-TTS) funktioniert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-17 11:28:20 +02:00
commit c25f081f9f
8 changed files with 92 additions and 8 deletions

View file

@ -75,14 +75,19 @@ def test_chat_per_request_override_and_loopback(monkeypatch):
assert loopback_output().chunks[0].data == b"AUDIO"
def test_transcribe_local_provider():
def test_transcribe_with_stub_stt(monkeypatch):
class StubSTT:
async def transcribe(self, audio_bytes, fmt, language=None):
return "erkannter text"
monkeypatch.setitem(deps.STT_REGISTRY, "stub-stt", lambda s: StubSTT())
files = {"file": ("a.wav", b"RIFFdata", "audio/wav")}
data = {"stt_provider": "faster-whisper"}
data = {"stt_provider": "stub-stt"}
resp = client.post("/api/transcribe", data=data, files=files)
assert resp.status_code == 200
body = resp.json()
assert body["route"]["stt_provider"] == "faster-whisper"
assert body["trace"]["raw_transcript"] == "[local transcription placeholder]"
assert body["route"]["stt_provider"] == "stub-stt"
assert body["trace"]["raw_transcript"] == "erkannter text"
def test_config_endpoint_exposes_no_secrets():