fix(tts): revert sampling knobs — real cause was Turbo AR-tail over-run, fixed server-side

The long-turn "swaps to German" garble was NOT a language leak (infra-ops's
initial framing) and NOT the sampling entropy my interim curb targeted. The real
cause, signal-measured by infra-ops: the Chatterbox Turbo model over-runs its
generation TAIL — a long single generation degrades into garble/dead-air in its
final ~2-3s (voiced-tail zero-crossing rate 1.58x the middle). The gateway's
unbounded chunk-size ratchet built 300-600 char mega-chunks that landed in that
zone, and streaming concatenated each bad tail.

My interim curb (top_k 1000->80, top_p 0.95->0.85, temp 0.8->0.5) made it WORSE:
tight sampling pulls the degradation onset to a shorter length (~200 chars vs
~300 at defaults), so it fights the server-side fix rather than helping.

Fixed server-side (infra-ops, chatterbox-fast image :v2): a max_chunk_chars=250
cap bounds each generation below the ~300-char onset -> clean prosodic chunks
(verified ZCR 1.58x -> 0.64x; operator ear-confirmed clean audio + clean joins).

Consumer side, this commit:
- Revert the sampling knobs: gateway_body back to {text, voice, format, stream},
  send full text with the gateway's default sampling. The server chunks at 250.
- Keep the /api/tts empty-200 -> 503 guard as hygiene (DEC-9a; the shared-3090
  OOM that produced empty 200s is also resolved — Zonos moved off the card).

Contract DEC-9 rewritten with the resolved root cause + the two wrong hypotheses;
DEC-9a marked kept-as-hygiene. 520 green.
This commit is contained in:
vh
2026-08-07 11:43:35 -07:00
parent 19b499ab50
commit 7fdaf3bd23
4 changed files with 56 additions and 75 deletions
+7 -11
View File
@@ -12,9 +12,6 @@ import pytest
import respx
from ratatoskr.tts import (
_TTS_TEMPERATURE,
_TTS_TOP_K,
_TTS_TOP_P,
CHATTERBOX_TTS_URL,
TtsUnavailable,
gateway_body,
@@ -60,15 +57,14 @@ class TestGatewayBody:
assert b["format"] == "wav" # DEC-3 — "format", not "response_format"
assert b["stream"] is True # DEC-2 — play-as-it-arrives
def test_sampling_curbs_below_gateway_defaults_hold_english(self) -> None:
# DEC-9: the model has no `language` pin and Turbo's multilingual capacity leaks under
# high-entropy sampling on long turns. gateway_body tightens temperature/top_p/top_k
# below the gateway defaults (0.8 / 0.95 / 1000) to hold English — top_k the highest-
# leverage. Pin presence + the below-default relationship (infra-ops-authoritative).
def test_default_sampling_no_client_side_curbs(self) -> None:
# DEC-9 (real cause): the long-turn garble was Turbo over-running its GENERATION TAIL,
# fixed SERVER-SIDE (:v2 max_chunk_chars=250). A client sampling curb was
# counterproductive (tight sampling pulls the garble onset earlier), so gateway_body
# sends NO temperature/top_p/top_k — the gateway's defaults govern.
b = gateway_body("a long turn", "donut")
assert b["temperature"] == _TTS_TEMPERATURE and _TTS_TEMPERATURE < 0.8
assert b["top_p"] == _TTS_TOP_P and _TTS_TOP_P < 0.95
assert b["top_k"] == _TTS_TOP_K and _TTS_TOP_K < 1000
for knob in ("temperature", "top_p", "top_k"):
assert knob not in b
def test_no_zonos_era_fields(self) -> None:
# The Zonos body fields are gone: no OpenAI `input`/`response_format`, no