diff --git a/xtts/voxtral/bridge.py b/xtts/voxtral/bridge.py index f9f6b3f..e1d2a1c 100644 --- a/xtts/voxtral/bridge.py +++ b/xtts/voxtral/bridge.py @@ -438,14 +438,21 @@ class SessionManager: sess.endpoint_sent = True # Halluzinations-Guard: zu wenig echte Stimme (Stille / kurzer Blip im # Passiv-/Wake-Fenster) → NICHT transkribieren. Voxtral (wie Whisper) baut - # aus Fast-Nichts gern einen Fuellsatz ("keine Ahnung" o.ae.), der dann als - # PHANTOM-Nachricht ans Brain geht und das Gespraech entgleisen laesst - # (Stefans Repro: "kam Nachricht von mir, obwohl ich nichts sagte"). Leeres - # Endpoint = no-speech → App re-armt still. Der manuelle Stop (stream_end) - # ist ausgenommen: dort hat der User bewusst gesprochen (kurze Woerter ok). - if reason != "stream_end" and sess.voiced_frames < STREAM_MIN_VOICED_FRAMES: + # aus Fast-Nichts gern einen Fuellsatz ("Die Stadt hat eine Flaeche von + # 1,5 km2"), der dann als PHANTOM-Nachricht ans Brain geht und das Gespraech + # entgleisen laesst (Stefans Repro: "kam Nachricht von mir, obwohl ich + # nichts sagte"). Leeres Endpoint = no-speech → App re-armt still. + # + # WICHTIG (aus dem ai-box-Log gelernt): die Phantome kommen mit + # reason=stream_end — Passiv-/Wake-Fenster enden AUCH per stream_end, wenn + # sie auf Stille zumachen. stream_end ist also NICHT gleich "manueller Stop". + # Deshalb greift der Guard jetzt auch bei stream_end, aber mit niedrigerer + # Schwelle (voiced==0 = gar keine Stimme), damit ein kurzes bewusstes Wort + # ('ja', 'stopp') am Aufnahme-Button noch durchgeht, echte Stille aber nicht. + _min_voiced = STREAM_MIN_VOICED_FRAMES if reason != "stream_end" else 1 + if sess.voiced_frames < _min_voiced: logger.info("Stream %s: no-speech (voiced_frames=%d<%d, reason=%s) — leeres Endpoint", - sess.request_id[:8], sess.voiced_frames, STREAM_MIN_VOICED_FRAMES, reason) + sess.request_id[:8], sess.voiced_frames, _min_voiced, reason) if self._ws is not None: nospeech = {"requestId": sess.request_id, "audioRequestId": sess.audio_request_id, @@ -472,12 +479,16 @@ class SessionManager: # Halluzinations-Filter (2. Netz): leeres/Artefakt-Transkript im borderline- # Band → als no-speech verwerfen statt ein Phantom ("Die Stadt hat eine - # Flaeche von 1,5 km2") ans Brain zu schicken. Manueller Stop (stream_end) - # ist ausgenommen (bewusst gesprochen, auch kurze Woerter zaehlen). + # Flaeche von 1,5 km2") ans Brain zu schicken. Gilt fuer ALLE reasons inkl. + # stream_end (dort kamen die realen Phantome!) — aber das borderline-Band + # (wenig voiced_frames) schuetzt echte, klar gesprochene Eingaben: eine echte + # Geografie-FRAGE hat normale Stimm-Energie (voiced_frames >> Schwelle) und + # geht durch; das Phantom aus Stille hat ~0 und wird verworfen. Ein leeres + # Transkript wird immer verworfen (nichts gesagt = nichts senden). _clean = final_text.strip(" .,!?…-\t\n\r") _borderline = sess.voiced_frames < STREAM_HALLUC_GUARD_FRAMES _is_phantom = (not _clean) or (_borderline and bool(_HALLUCINATION_RE.search(final_text))) - if reason != "stream_end" and _is_phantom: + if _is_phantom: logger.info("Stream %s: Halluzination verworfen (voiced_frames=%d<%d, %.1fs, text=%r)", sess.request_id[:8], sess.voiced_frames, STREAM_HALLUC_GUARD_FRAMES, duration_s, final_text[:80])