diff --git a/xtts/voxtral/bridge.py b/xtts/voxtral/bridge.py index 47d67f9..7f7e165 100644 --- a/xtts/voxtral/bridge.py +++ b/xtts/voxtral/bridge.py @@ -94,6 +94,24 @@ _HALLUCINATION_RE = re.compile( re.IGNORECASE, ) +# Kollabiert unmittelbar wiederholte Phrasen (Voxtral-Repetition-Loop) auf EINE +# Kopie. Zweites Netz hinter no_repeat_ngram in der Generation. Phrase 5-80 Zeichen, +# 3+ mal hintereinander → eine. Kurze legitime Doppelungen ('ja ja', 'sehr sehr') +# bleiben (Unit < 5 Zeichen bzw. < 3 Wiederholungen). +_REPEAT_RE = re.compile(r"(.{5,80}?)(?:\s*\1){2,}", re.IGNORECASE | re.DOTALL) + + +def _collapse_repetitions(text: str) -> str: + if not text: + return text + out = text + for _ in range(3): # mehrfach fuer verschachtelte/ungleiche Loops + new = _REPEAT_RE.sub(r"\1", out) + if new == out: + break + out = new + return out.strip() + # Speaker-ID Gating global an/aus. DEFAULT AUS (fail-open) — die "nur meine Stimme"- # Pruefung ist ein BEWUSSTER Schalter, kein Automatismus: ein einziger schlechter # Enroll darf nie die ganze STT lahmlegen (genau das ist passiert). Wird per config- @@ -162,7 +180,20 @@ class VoxtralRunner: with torch.no_grad(): # hoch genug fuer lange Diktate (stoppt eh am EOS); 512 hat # mehrminutige Aufnahmen abgeschnitten. - outputs = model.generate(**inputs, max_new_tokens=4096) + # Repetition-Bremse: Voxtral kippt bei Stille/Rauschen am Ende + # gern in eine Schleife und wiederholt einen Satz zig-mal + # ("Vergiss das, das ist nur... Vergiss das, das ist nur..." + # x15). no_repeat_ngram_size=4 laesst die ERSTE echte Nennung + # durch, verbietet aber die exakte 4-Gramm-Wiederholung → Loop + # bricht ab; repetition_penalty daempft zusaetzlich. Beides mild, + # damit normale Sprache (auch mal ein doppeltes Wort) unberuehrt + # bleibt. + outputs = model.generate( + **inputs, + max_new_tokens=4096, + no_repeat_ngram_size=4, + repetition_penalty=1.15, + ) trimmed = outputs[:, inputs.input_ids.shape[1]:] text = proc.batch_decode(trimmed, skip_special_tokens=True) return (text[0] if text else "").strip() @@ -485,6 +516,12 @@ class SessionManager: final_text = sess.last_partial stt_ms = int((time.time() - t0) * 1000) duration_s = audio.size / 16000.0 + # Repetition-Loop einkassieren, falls trotz no_repeat_ngram was durchkam. + _collapsed = _collapse_repetitions(final_text) + if _collapsed != final_text: + logger.info("Stream %s: Repetition-Loop kollabiert (%d→%d Zeichen)", + sess.request_id[:8], len(final_text), len(_collapsed)) + final_text = _collapsed logger.info("Stream %s: FINAL (reason=%s, %.1fs, %dms): %r", sess.request_id[:8], reason, duration_s, stt_ms, final_text[:120])