fix(voxtral): Halluzinations-Filter gegen Phantom-Text aus Stille
Punkt 3: 2. Netz nach der Transkription. Im borderline-Band (wenig echte
Stimme) werden leere/Artefakt-Transkripte verworfen statt als Phantom ans
Brain zu gehen ('Die Stadt hat eine Fläche von 1,5 km²' aus Fast-Stille).
Bekannte Voxtral-Silence-Artefakte (Untertitel-Credits, Geo-/Städte-Fakten)
per Regex, gegated auf voiced_frames — echte Geo-FRAGEN (normale Energie)
gehen durch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -74,6 +75,25 @@ STREAM_VOICE_RMS_MAX = 0.020
|
|||||||
# (Voxtral halluziniert aus Fast-Nichts sonst einen Fuellsatz). 2 ≈ 400ms.
|
# (Voxtral halluziniert aus Fast-Nichts sonst einen Fuellsatz). 2 ≈ 400ms.
|
||||||
STREAM_MIN_VOICED_FRAMES = int(os.getenv("STREAM_MIN_VOICED_FRAMES", "2"))
|
STREAM_MIN_VOICED_FRAMES = int(os.getenv("STREAM_MIN_VOICED_FRAMES", "2"))
|
||||||
|
|
||||||
|
# Halluzinations-Filter (2. Netz NACH der Transkription). Der voiced_frames-Guard
|
||||||
|
# oben faengt die reine Stille; hier kommt das "borderline"-Band dazu: wenn wenig
|
||||||
|
# echte Stimme da war UND das Transkript ein bekanntes Voxtral-Silence-Artefakt
|
||||||
|
# ist (Untertitel-Credits, Staedte-/Geo-Fakten "Flaeche von X km2"), ist es fast
|
||||||
|
# sicher ein Phantom aus Fast-Nichts → verwerfen. Gegated auf wenig voiced_frames,
|
||||||
|
# damit eine ECHTE Geografie-Frage (die hat normale Stimm-Energie) durchgeht.
|
||||||
|
STREAM_HALLUC_GUARD_FRAMES = int(os.getenv("STREAM_HALLUC_GUARD_FRAMES",
|
||||||
|
str(STREAM_MIN_VOICED_FRAMES * 4))) # ~1.6s
|
||||||
|
_HALLUCINATION_RE = re.compile(
|
||||||
|
r"untertitel"
|
||||||
|
r"|amara\.org"
|
||||||
|
r"|vielen\s+dank\s+f[uü]r'?s?\s+(zuschauen|zusehen|zuh[oö]ren)"
|
||||||
|
r"|bis\s+zum\s+n[aä]chsten\s+mal"
|
||||||
|
r"|abonnier"
|
||||||
|
r"|fl[aä]che\s+von\s+[\d.,]+\s*(km|quadratkilometer)"
|
||||||
|
r"|[\d.,]+\s*(km²|quadratkilometern?|einwohnern?)\b",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
# Speaker-ID Gating global an/aus. DEFAULT AUS (fail-open) — die "nur meine Stimme"-
|
# Speaker-ID Gating global an/aus. DEFAULT AUS (fail-open) — die "nur meine Stimme"-
|
||||||
# Pruefung ist ein BEWUSSTER Schalter, kein Automatismus: ein einziger schlechter
|
# Pruefung ist ein BEWUSSTER Schalter, kein Automatismus: ein einziger schlechter
|
||||||
# Enroll darf nie die ganze STT lahmlegen (genau das ist passiert). Wird per config-
|
# Enroll darf nie die ganze STT lahmlegen (genau das ist passiert). Wird per config-
|
||||||
@@ -449,6 +469,31 @@ class SessionManager:
|
|||||||
duration_s = audio.size / 16000.0
|
duration_s = audio.size / 16000.0
|
||||||
logger.info("Stream %s: FINAL (reason=%s, %.1fs, %dms): %r",
|
logger.info("Stream %s: FINAL (reason=%s, %.1fs, %dms): %r",
|
||||||
sess.request_id[:8], reason, duration_s, stt_ms, final_text[:120])
|
sess.request_id[:8], reason, duration_s, stt_ms, final_text[:120])
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
_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:
|
||||||
|
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])
|
||||||
|
if self._ws is not None:
|
||||||
|
nospeech = {"requestId": sess.request_id,
|
||||||
|
"audioRequestId": sess.audio_request_id,
|
||||||
|
"text": "", "reason": f"hallucination:{reason}",
|
||||||
|
"durationS": 0.0, "sttMs": stt_ms}
|
||||||
|
await _send(self._ws, "stt_endpoint", nospeech)
|
||||||
|
await _send(self._ws, "stt_stream_done", {
|
||||||
|
"requestId": sess.request_id,
|
||||||
|
"audioRequestId": sess.audio_request_id,
|
||||||
|
"text": "", "reason": f"hallucination:{reason}"})
|
||||||
|
self.drop(sess.request_id)
|
||||||
|
return
|
||||||
|
|
||||||
if self._ws is not None:
|
if self._ws is not None:
|
||||||
payload = {
|
payload = {
|
||||||
"requestId": sess.request_id,
|
"requestId": sess.request_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user