From 7da2fcb5e5fe94ab7a1376614afb1c44a2e03dca Mon Sep 17 00:00:00 2001 From: javis-bot Date: Wed, 24 Jun 2026 17:55:20 +0900 Subject: [PATCH] feat(stt): beam-search decoding + no prev-text conditioning for accuracy Whisper was decoding with beam_size=1 (greedy), the least accurate setting, which hurt recognition on short/accented/noisy Discord-mic speech. Switch the default to beam search (5, Whisper's own default) and stop conditioning on the previous clip's transcript (which causes repetition/drift on isolated short utterances rather than helping). Both are env-tunable (STT_BEAM_SIZE, STT_CONDITION_ON_PREV) so accuracy/latency can be traded without a code change; wired into docker-compose and documented in .env.example. --- .env.example | 1 + bridge/server.py | 18 +++++++++++++++++- docker-compose.yml | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 332dbb2..05a275a 100644 --- a/.env.example +++ b/.env.example @@ -227,6 +227,7 @@ COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml # OLLAMA_CHAT_MODEL=qwen2.5:7b # quality (needs ~5GB VRAM + whisper small) # OLLAMA_CHAT_MODEL=qwen2.5:3b # speed (fits easily, faster on 8GB GPUs) # WHISPER_MODEL=small # small frees VRAM for a bigger LLM; medium=more accurate +# STT_BEAM_SIZE=5 # beam search (5) > greedy (1) for accuracy; lower for speed # MELO_DEVICE=cuda # cpu if no GPU on the bot host # --- Settings web UI (http://localhost:8765/settings on the bot host) --- diff --git a/bridge/server.py b/bridge/server.py index 721984e..bf475ac 100644 --- a/bridge/server.py +++ b/bridge/server.py @@ -87,6 +87,17 @@ VAD_MIN_SPEECH_MS = int(os.environ.get("VAD_MIN_SPEECH_MS", "200")) # Korean phrase decoded as Chinese) and shaves a little latency. Empty = auto. STT_LANGUAGE = os.environ.get("STT_LANGUAGE", "ko").strip() or None +# Whisper decoding accuracy knobs. beam_size=1 is greedy decoding — fast but the +# least accurate; beam search (5 is the Whisper default) explores alternatives +# and noticeably improves recognition on short, accented, or noisy Discord-mic +# speech. condition_on_previous_text=False stops Whisper from feeding a previous +# clip's transcript back in as a prompt, which on isolated short utterances +# causes repetition loops and drift rather than helping. Both are env-tunable so +# accuracy/latency can be traded without a code change (lower STT_BEAM_SIZE for +# speed, raise it for accuracy). +STT_BEAM_SIZE = max(1, int(os.environ.get("STT_BEAM_SIZE", "5"))) +STT_CONDITION_ON_PREV = os.environ.get("STT_CONDITION_ON_PREV", "0") in ("1", "true", "True", "yes", "on") + # TTS engine: "edge" (Microsoft Edge TTS, natural Korean neural voice) is the # primary voice. "melo" (a warm MeloTTS worker) and "piper" remain selectable. def _tts_engine_setting() -> str: @@ -243,7 +254,12 @@ def transcribe(wav_bytes: bytes) -> dict: print("[bridge] no speech detected (VAD) — skipping STT", flush=True) return {"text": "", "language": None, "note": "음성 아님(VAD 차단)"} - segments, info = _whisper.transcribe(audio, beam_size=1, language=STT_LANGUAGE) + segments, info = _whisper.transcribe( + audio, + beam_size=STT_BEAM_SIZE, + language=STT_LANGUAGE, + condition_on_previous_text=STT_CONDITION_ON_PREV, + ) # Second line of defence: drop non-speech / hallucinated segments by # Whisper's own no_speech_prob. The no_speech_prob hard cutoff (plus the VAD # pre-gate above) is what rejects noise/hallucinations. The avg_logprob diff --git a/docker-compose.yml b/docker-compose.yml index 395683f..9111365 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,6 +97,9 @@ services: PLANNER_ENABLED: ${PLANNER_ENABLED:-0} # Lock STT to Korean (skip Whisper auto-detect). STT_LANGUAGE: ${STT_LANGUAGE:-ko} + # Whisper decode accuracy: beam search (5) over greedy (1) lifts recognition + # on short/noisy Discord speech. Lower to 1 for minimum latency. + STT_BEAM_SIZE: ${STT_BEAM_SIZE:-5} VOICE_SILENCE_MS: ${VOICE_SILENCE_MS:-600} BRIDGE_URL: http://127.0.0.1:8765 # Split-deployment role: full (default, all-in-one), browser (only the