From 9e78d75149fa2c278e5976327072daa0f5a3c6cd Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 15 Aug 2026 14:15:21 +0200 Subject: [PATCH] =?UTF-8?q?fix(voxtral):=20"Sprache=20erkannt"-Ping=20?= =?UTF-8?q?=E2=80=94=20No-Speech-Watchdog=20schneidet=20nicht=20mehr=20ab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Im Ohr-Modus (Wake/Passiv/Barge) beendete die App die Aufnahme am Konversations- fenster (~conv window), obwohl Stefan noch redete — nur dort, nicht beim normalen Button. Ursache: der App-No-Speech-Watchdog cancelt, solange KEIN stt_partial kam (streamGotPartial=false). Voxtral schickt aber seit dem Cutoff-Fix keine Live- Partials mehr → streamGotPartial blieb immer false → Timer feuerte mitten im Satz. Der normale Button nutzt noSpeechTimeoutMs=0 (Watchdog aus) → dort nie aufgefallen. Fix: Voxtral-Bridge feuert EIN leeres stt_partial beim ersten Voice-Frame (nach der Speaker-ID-Pruefung, fremde Stimmen signalisieren also nicht). Die App setzt darauf streamGotPartial=true und loescht den Watchdog — bestehender Handler, keine App-Aenderung. Der akustische Endpoint (Stille >= endpointMs) bleibt der einzige Turn-Ende-Ausloeser. Deploy: docker compose up -d --build voxtral-bridge (keine neue APK noetig). Co-Authored-By: Claude Opus 4.8 --- xtts/voxtral/bridge.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/xtts/voxtral/bridge.py b/xtts/voxtral/bridge.py index b051148..ef75f1e 100644 --- a/xtts/voxtral/bridge.py +++ b/xtts/voxtral/bridge.py @@ -168,6 +168,11 @@ class StreamSession: noise_floor: float = 0.0 closed: bool = False endpoint_sent: bool = False + # Einmaliges "Sprache erkannt"-Signal an die App gesendet? Voxtral schickt + # keine Live-Partials, aber der App-No-Speech-Watchdog wartet auf ein + # stt_partial, um "der User redet" zu erkennen — sonst cancelt er mitten im + # Satz. Wir feuern EIN leeres stt_partial beim ersten Voice-Frame. + speech_signaled: bool = False # Speaker-ID Gating (einmalig auf die ersten ~1.5s der Aufnahme) speaker_checked: bool = False speaker_match: Optional[bool] = None @@ -361,6 +366,20 @@ class SessionManager: rms = self._tail_rms(sess) if rms >= self._voice_threshold(sess): sess.last_voice_at = now + # Einmalig der App melden, dass Sprache begonnen hat. Ohne Live-Partials + # wuerde ihr No-Speech-Watchdog die Aufnahme sonst am Konversations- + # fenster canceln, obwohl der User noch redet (Stefans Reproduktion: + # "beendet nach ~4s, obwohl ich weiterrede — nur im Ohr-Modus"). Ein + # leeres stt_partial reicht: die App setzt streamGotPartial=true und + # loescht den Watchdog. Nach der Speaker-ID-Pruefung (oben) → fremde + # Stimmen signalisieren NICHT. + if not sess.speech_signaled and self._ws is not None: + sess.speech_signaled = True + await _send(self._ws, "stt_partial", { + "requestId": sess.request_id, + "audioRequestId": sess.audio_request_id, + "text": "", + }) else: self._update_noise_floor(sess, rms) # Endpoint: hat der User schon gesprochen UND ist es seit endpoint_ms still?