Skip to content

Feat/voice robustness - #99

Merged
berges99 merged 15 commits into
mainfrom
feat/voice-robustness
Jul 24, 2026
Merged

Feat/voice robustness#99
berges99 merged 15 commits into
mainfrom
feat/voice-robustness

Conversation

@berges99

Copy link
Copy Markdown
Collaborator

No description provided.

berges99 added 7 commits July 23, 2026 17:10
Add a "Noise: browser / RNNoise / off" selector to voice.html. RNNoise
(@jitsi/rnnoise-wasm from jsDelivr) runs inside the capture worklet as a
mic preprocessor: 48 kHz context, 480-sample frames denoised in place,
then 3:1 decimation back to 16 kHz so the wire format is unchanged.
Transport-agnostic by design — the same preprocessing carries over to a
future WebRTC channel.

Browser noiseSuppression is disabled while RNNoise is active (never two
suppressors); echoCancellation stays on in every mode since the barge-in
echo gates rely on it. Asset fetch failure, wasm init failure, and a
browser refusing a 48 kHz context all degrade gracefully to the previous
behavior. The hello reports the worklet's output rate (ctx/3 when
denoising) so STT negotiation stays correct.

Footer layout: mode selects (Turns, Noise) move to their own row so
labels stay readable.
A wrong "incomplete" from Smart Turn (observed on short closers like
"Thank you." p=0.036 and interjections like "No, no." p=0.378) costs
the full hold before the turn runs — and no mid-hold re-score can rescue
it, since the EOU backend trims trailing silence and would reproduce the
same window and score.

3.0s matches both references: LiveKit's current endpointing max_delay
default (6.0s was the old default) and Pipecat's SmartTurnParams
stop_secs fallback. With our ~1.2s STT VAD silence before the hold arms,
worst-case total is ~4.2s.
Mix transcript confidence into endpointing: audio-incomplete + finished
text shortens the HOLD; audio-complete + hedge text (e.g. "I don't know.")
delays via a short HOLD and a VAD delay bump. Floor VAD min_delay at 0.45s
so confident scores never commit in ~0ms.

Also fix hold expiry stretching on the commit's own partials, rescue
stuck STT partials that never commit, and stop the playground from
splitting one agent reply across two bubbles on stray mic partials.
…e stretch

Wire VideoSDK Namo as the local text EOU with a terminal-punct lexical
rescue so finished questions stop eating the incomplete HOLD. Cut the
audio-incomplete/text-complete hold to 0.35s, and only extend HOLD on
post-commit Silero speech so late STT refinements cannot floor short
holds at the 2s grace window. Playground auto-starts; demo defaults to
Groq Llama 3.1 8B Instant.
Mirror timbal.voice module layout (session, turn_detection, namo, smart_turn,
endpointing, eou, vad, playback, realtime) out of tests/core.
…tials

Refuse CONTINUE-merge of ultra-early short trailers onto punctuated turns
so the LLM isn't cancelled/restarted; keep question VAD-splits and late
barge-ins. Preserve LocalAudio NEW_TURN for those barge-ins (don't HOLD
during TTS). Skip TranscriptPartial for VAD-vetoed barge-in hallucinations.
…ply barge-in

Add Deepgram Flux/Nova realtime STT alongside ElevenLabs Scribe, wire
provider resolution + Flux→provider turn-detector pairing, and expand the
/voice playground (STT picker, LLM catalog, Foundry-style layout, tool
status, cold-start warmup). Drop queued AudioOutput on interrupt so long
TTS replies cut immediately instead of draining the WS backlog. Also
coerce nullish tool args in serialization and add a slow get_datetime
demo tool for tool-call dead-air testing.
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches the full realtime voice path (STT provider selection, turn detection, barge-in, and session lifecycle) with new third-party STT credentials and subtle timing/state-machine behavior that is hard to validate outside extensive tests.

Overview
Adds Deepgram Flux/Nova realtime STT (resolve_stt, TIMBAL_STT_PROVIDER, DEEPGRAM_API_KEY) with Flux wired to provider turn detection and VAD endpointing off; ElevenLabs remains the fallback when config is invalid.

Introduces Namo ONNX text end-of-turn scoring (transformers in timbal[voice]) for local mode, layered with hedge/ellipsis lexical rules and tiered HOLD timeouts (audio vs text complete/incomplete). VAD endpointing gains a LiveKit-style min delay and optional text_score bumps for mid-thought partials.

VoiceSession fixes include stale-partial force-commit, HOLD that defers during TTS instead of interrupting, safer barge-in / queued-audio drop, TranscriptCommitted.replace, per-session LLM warmup for playground model overrides, and agent_status UI events. coerce_to_dict treats null/empty tool args as {} for Groq-style providers.

The voice playground is reworked (STT/turn/LLM/noise-suppression controls, RNNoise worklet, explicit Start/Stop sessions). Tests and docs cover Deepgram, Namo, endpointing, and session edge cases; the turn-modes demo adds a slow get_datetime tool.

Reviewed by Cursor Bugbot for commit 2b672ce. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread python/timbal/server/voice.py Outdated
Comment thread python/timbal/voice/session.py
Comment thread python/timbal/voice/session.py
Warm the session model override (not only agent.model), and on unknown
stt_provider clear Flux/Nova model ids from the ElevenLabs fallback.
Skip late_duplicate while a user turn or HOLD is active so prefix
continuations reach CONTINUE_TURN instead of being ignored.
Comment thread python/timbal/server/voice.py
…LD defer test

session_started was advertising the STT class name (DeepgramFluxSTT) instead
of elevenlabs/deepgram-flux/deepgram-nova. Also wait for a post-commit partial
before racing the hold timer so Windows CI doesn't expire mid-utterance.
Comment thread python/timbal/voice/deepgram.py
berges99 added 2 commits July 24, 2026 18:11
push_audio and the flush loop both drained PCM then awaited _ws.send
without a shared lock, so frames could overlap or reorder on one socket.
…all-clock

The Windows flake compared turn start to the driver inject loop; inject only
enqueues, while grace keys off _last_partial_at after drain. Wait for each
partial to process and assert the hold stays armed past its timeout.
Comment thread python/timbal/voice/eou.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ffe35f4. Configure here.

Comment thread python/timbal/voice/session.py
@berges99
berges99 merged commit b8e539a into main Jul 24, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants