fix(voxtral): Phantom-Filter greift auch bei stream_end
Aus dem ai-box-Log gelernt: die realen Silence-Phantome ('Die Stadt hat eine
Fläche von 1,5 km²') kommen ALLE 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 mit bewusster Sprache'. Meine vorige Fassung nahm
stream_end aus → Phantome liefen durch.
Jetzt: (1) Pre-Guard greift auch bei stream_end, aber mit Schwelle voiced==0
(kurze bewusste Wörter am Button gehen durch, echte Stille nicht). (2) Phrase-
Filter gilt für ALLE reasons; das borderline-Band (wenig voiced_frames) schützt
echte, klar gesprochene Geo-Fragen. Gegen alle 3 Log-Fälle + reale Eingaben
verifiziert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-10
@@ -438,14 +438,21 @@ class SessionManager:
|
|||||||
sess.endpoint_sent = True
|
sess.endpoint_sent = True
|
||||||
# Halluzinations-Guard: zu wenig echte Stimme (Stille / kurzer Blip im
|
# Halluzinations-Guard: zu wenig echte Stimme (Stille / kurzer Blip im
|
||||||
# Passiv-/Wake-Fenster) → NICHT transkribieren. Voxtral (wie Whisper) baut
|
# Passiv-/Wake-Fenster) → NICHT transkribieren. Voxtral (wie Whisper) baut
|
||||||
# aus Fast-Nichts gern einen Fuellsatz ("keine Ahnung" o.ae.), der dann als
|
# aus Fast-Nichts gern einen Fuellsatz ("Die Stadt hat eine Flaeche von
|
||||||
# PHANTOM-Nachricht ans Brain geht und das Gespraech entgleisen laesst
|
# 1,5 km2"), der dann als PHANTOM-Nachricht ans Brain geht und das Gespraech
|
||||||
# (Stefans Repro: "kam Nachricht von mir, obwohl ich nichts sagte"). Leeres
|
# entgleisen laesst (Stefans Repro: "kam Nachricht von mir, obwohl ich
|
||||||
# Endpoint = no-speech → App re-armt still. Der manuelle Stop (stream_end)
|
# nichts sagte"). Leeres Endpoint = no-speech → App re-armt still.
|
||||||
# ist ausgenommen: dort hat der User bewusst gesprochen (kurze Woerter ok).
|
#
|
||||||
if reason != "stream_end" and sess.voiced_frames < STREAM_MIN_VOICED_FRAMES:
|
# 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",
|
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:
|
if self._ws is not None:
|
||||||
nospeech = {"requestId": sess.request_id,
|
nospeech = {"requestId": sess.request_id,
|
||||||
"audioRequestId": sess.audio_request_id,
|
"audioRequestId": sess.audio_request_id,
|
||||||
@@ -472,12 +479,16 @@ class SessionManager:
|
|||||||
|
|
||||||
# Halluzinations-Filter (2. Netz): leeres/Artefakt-Transkript im borderline-
|
# Halluzinations-Filter (2. Netz): leeres/Artefakt-Transkript im borderline-
|
||||||
# Band → als no-speech verwerfen statt ein Phantom ("Die Stadt hat eine
|
# 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)
|
# Flaeche von 1,5 km2") ans Brain zu schicken. Gilt fuer ALLE reasons inkl.
|
||||||
# ist ausgenommen (bewusst gesprochen, auch kurze Woerter zaehlen).
|
# 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")
|
_clean = final_text.strip(" .,!?…-\t\n\r")
|
||||||
_borderline = sess.voiced_frames < STREAM_HALLUC_GUARD_FRAMES
|
_borderline = sess.voiced_frames < STREAM_HALLUC_GUARD_FRAMES
|
||||||
_is_phantom = (not _clean) or (_borderline and bool(_HALLUCINATION_RE.search(final_text)))
|
_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)",
|
logger.info("Stream %s: Halluzination verworfen (voiced_frames=%d<%d, %.1fs, text=%r)",
|
||||||
sess.request_id[:8], sess.voiced_frames, STREAM_HALLUC_GUARD_FRAMES,
|
sess.request_id[:8], sess.voiced_frames, STREAM_HALLUC_GUARD_FRAMES,
|
||||||
duration_s, final_text[:80])
|
duration_s, final_text[:80])
|
||||||
|
|||||||
Reference in New Issue
Block a user