Thomas Piacentini
← Workshop

Cheat sheet

Real-Time Voice Assistants

Original text · English

VAD, turn detection, barge-in, latency budgets and full-duplex speech for a voice agent that answers in time.

Cascaded voice pipeline

framesspeechpartialscommitcalltokensaudiobarge-in cancelMic16 kHz mono, 20 msclientVADSilero, speech gateprocessStreaming STTpartial transcriptsmodelTurn detectorscore above 0.6modelLLM with toolsstreams tokensmodelTool side channelfiller after 300msprocessStreaming TTSfirst chunk in 200msmodelSpeakerWebRTC jitter bufferclient
Nodes and connections
NodeRoleKind
Mic16 kHz mono, 20 msclient
VADSilero, speech gateprocess
Streaming STTpartial transcriptsmodel
Turn detectorscore above 0.6model
LLM with toolsstreams tokensmodel
Tool side channelfiller after 300msprocess
Streaming TTSfirst chunk in 200msmodel
SpeakerWebRTC jitter bufferclient
FromToConnection
MicVADframes
VADStreaming STTspeech
Streaming STTTurn detectorpartials
Turn detectorLLM with toolscommit
LLM with toolsTool side channelcall
LLM with toolsStreaming TTStokens
Streaming TTSSpeakeraudio
VADStreaming TTSbarge-in cancel
Sources

Capstone scheduler, one turn

  1. IDLE the first speech frame moves the state to LISTENING.
  2. LISTENING each silent frame adds 20 ms to silenceRunMs.
  3. LISTENING at 500 ms of silence, turnCompletionScore reads the last partial.
  4. WAITING a score of 0.6 or more commits the turn and sets turnCompleteMs.
  5. THINKING the tool call fires. At 300 ms the agent says "one second, let me check".
  6. THINKING the tool returns at 420 ms. The first LLM token comes 140 ms later.
  7. SPEAKING the first audio out comes 180 ms after the first LLM token.
  8. Barge-in speech in THINKING or SPEAKING cancels TTS and re-arms ASR.
Sources

Latency budget per turn

SettingDefaultNote
Mic to buffer20 msOne frame
VAD10 msSilero runs under 1 ms per frame
ASR (streaming)150 msPartial transcripts
LLM (first token)100 msStream the tokens
TTS (first chunk)100 msStart before the full reply
Render to speaker20 msOne frame
Total~400 msAbove 500 ms feels robotic
First TTS audio, laptop CPU800 msPhase 6 capstone target
Sources

VAD and turn settings

SettingDefaultNote
Silero threshold0.50.3 is sensitive. Fewer first-word clips
Minimum speech250 msRejects coughs and chair noise
Silence hangover500-800 msToo short cuts the user off
Pre-roll buffer300-500 msKeeps the first word
Energy gate-40 dBFSFires on any noise above it
Frame size20 ms320 samples at 16 kHz
Ring buffer32,000 samples2 s at 16 kHz
Turn commit (capstone)0.6Plus 500ms of VAD silence
Sources

Silero VAD segments

from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
audio = torch.tensor(waveform_16k, dtype=torch.float32)
segments = get_speech_timestamps(
audio, vad, sampling_rate=16000,
threshold=0.5,0.3 is sensitive
min_speech_duration_ms=250,drops coughs
min_silence_duration_ms=500,silence hangover
speech_pad_ms=300,pre-roll pad
)
for s in segments:
print(f"{s['start']/16000:.2f}s - {s['end']/16000:.2f}s")
Sources

Barge-in and the flush trick

tts_task = asyncio.create_task(tts_loop())
while True:
chunk = await mic.get()
if vad(chunk):user speaks during TTS
tts_task.cancel()stop the reply
await speaker.stop()
await new_turn()restart STT
break
def flush_on_end(stt_client, audio_buffer):
stt_client.send_audio(audio_buffer)
stt_client.send_flush()forces output now
return stt_client.recv_transcript(timeout_ms=150)
Sources

Run the lessons

CommandWhat it does
python3 code/main.pyRuns the simulator of a lesson
python main.pyCapstone scheduler, clean and barge-in calls
pip install silero-vadReal VAD in place of the stub
npm installInstalls the TypeScript client
npm startTwo offline sessions and a ws probe, exits 0
npm start -- --serveKeeps the ws server up
npm testnode --test runner via tsx
npm run typecheckRuns tsc --noEmit
Sources

Core terms

Ring buffer
fixed circular buffer. No allocations in the hot path.
Jitter buffer
reorders late packets. Too small gives gaps. Too large adds latency.
Pre-roll
audio kept before VAD fires. It prevents the first-word clip.
Silence hangover
the wait after VAD returns to 0, before end of turn.
Semantic endpoint
a classifier that reads the words, not only the silence.
Flush trick
flush the STT at end of speech. 500 ms of look-ahead ends in ~125 ms.
Filler
a short phrase when a tool takes more than 300ms.
DOWNSTREAM
Pipecat frames from source to sink, audio in and speech out.
UPSTREAM
Pipecat control frames, such as cancel, metrics and barge-in.
Sources

Full-duplex Moshi, one 80 ms step

  1. Mimi encodes user audio at 12.5 Hz with 8 codebooks.
  2. Temporal Transformer 7B parameters. Reads the user tokens and its own last tokens.
  3. Temporal Transformer emits the next text token, the inner monologue.
  4. Depth Transformer 2 layers. Predicts the 8 codebooks one after the other.
  5. Client decode_audio_mimi turns the tokens into audio and plays it.
  6. Budget 80 ms frame plus 80 ms acoustic delay is 160 ms. 200 ms on one L4 GPU.
Sources

Troubleshooting

SymptomCauseFix
First word is missingVAD starts a beat too lateThreshold 0.3 and a pre-roll
Bot talks over the userLLM keeps going after interruptWire VAD to cancel the LLM
"Thanks for watching" on silenceWhisper on silent framesAlways VAD-gate
ASR hears the bot voiceNo echo cancellationWebRTC AEC3
Agent cuts user off mid-sentenceHangover too short500-800 ms and a turn detector
Slow first replyTTS warm-up on first requestWarm it with a dummy run
Audible vocoder artifactsSub-200 ms TTS chunks320 ms chunks
Glitches under loadGIL starves the audio threadC-callback audio library
Sources

Glossary

AEC
echo cancellation. Subtracts the speaker to mic path.
TPR @ 5% FPR
VAD benchmark point. Silero 87.7%, WebRTC VAD 50.0%.
MultimodalAgent
LiveKit class. Audio in, audio out, no text in the middle.
VoicePipelineAgent
LiveKit class. STT, LLM, TTS with text-level control.
Sources

Q&A

How fast do humans take turns?
About 230 ms. Above 1500 ms feels broken.
Pipeline or Moshi?
Use a pipeline for tools. Moshi is not trained for tool calling.
Why no flush with Whisper streaming?
It is block-based and always waits for chunks.
What is end-to-end premium?
450 to 600 ms. 800 to 1200 ms is common.
What is the capstone bar?
First-audio-out p50 under 800ms, false-cutoff under 3%.
What does barge-in need in Pipecat?
UPSTREAM cancel frames back to TTS and LLM.
Sources
Original source
---
id: voice-assistants-realtime
title: Real-Time Voice Assistants
code: RT
subtitle: VAD, turn detection, barge-in, latency budgets and full-duplex speech for a voice agent that answers in time.
hue: 285
sources: phases/06-speech-and-audio/11-real-time-audio-processing, phases/06-speech-and-audio/12-voice-assistant-pipeline, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking, phases/06-speech-and-audio/15-streaming-speech-to-speech-moshi-hibiki, phases/14-agent-engineering/22-voice-agents-pipecat-livekit, phases/19-capstone-projects/03-realtime-voice-assistant
source_hash: bd10481585db3e17
---

## Cascaded voice pipeline
type: architecture
from: phases/06-speech-and-audio/12-voice-assistant-pipeline/docs/en.md, phases/19-capstone-projects/03-realtime-voice-assistant/docs/en.md

```arch
node | mic | 1,1 | Mic | 16 kHz mono, 20 ms | client
node | vad | 2,1 | VAD | Silero, speech gate | process
node | stt | 3,1 | Streaming STT | partial transcripts | model
node | turn | 4,1 | Turn detector | score above 0.6 | model
node | llm | 4,2 | LLM with tools | streams tokens | model
node | tool | 3,2 | Tool side channel | filler after 300ms | process
node | tts | 2,2 | Streaming TTS | first chunk in 200ms | model
node | spk | 1,2 | Speaker | WebRTC jitter buffer | client
edge | mic | vad | frames
edge | vad | stt | speech
edge | stt | turn | partials
edge | turn | llm | commit
edge | llm | tool | call
edge | llm | tts | tokens
edge | tts | spk | audio
edge | vad | tts | barge-in cancel
```

## Capstone scheduler, one turn
type: flow
from: phases/19-capstone-projects/03-realtime-voice-assistant/code/ts/src/orchestrator.ts, phases/19-capstone-projects/03-realtime-voice-assistant/code/main.py

1. **IDLE**: the first speech frame moves the state to LISTENING.
2. **LISTENING**: each silent frame adds 20 ms to `silenceRunMs`.
3. **LISTENING**: at 500 ms of silence, `turnCompletionScore` reads the last partial.
4. **WAITING**: a score of 0.6 or more commits the turn and sets `turnCompleteMs`.
5. **THINKING**: the tool call fires. At 300 ms the agent says "one second, let me check".
6. **THINKING**: the tool returns at 420 ms. The first LLM token comes 140 ms later.
7. **SPEAKING**: the first audio out comes 180 ms after the first LLM token.
8. **Barge-in**: speech in THINKING or SPEAKING cancels TTS and re-arms ASR.

## Latency budget per turn
type: defaults
from: phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/06-speech-and-audio/12-voice-assistant-pipeline/docs/en.md

| Setting | Default | Note |
|---|---|---|
| Mic to buffer | 20 ms | One frame |
| VAD | 10 ms | Silero runs under 1 ms per frame |
| ASR (streaming) | 150 ms | Partial transcripts |
| LLM (first token) | 100 ms | Stream the tokens |
| TTS (first chunk) | 100 ms | Start before the full reply |
| Render to speaker | 20 ms | One frame |
| Total | ~400 ms | Above 500 ms feels robotic |
| First TTS audio, laptop CPU | 800 ms | Phase 6 capstone target |

## VAD and turn settings
type: defaults
from: phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/19-capstone-projects/03-realtime-voice-assistant/docs/en.md

| Setting | Default | Note |
|---|---|---|
| Silero threshold | 0.5 | 0.3 is sensitive. Fewer first-word clips |
| Minimum speech | 250 ms | Rejects coughs and chair noise |
| Silence hangover | 500-800 ms | Too short cuts the user off |
| Pre-roll buffer | 300-500 ms | Keeps the first word |
| Energy gate | -40 dBFS | Fires on any noise above it |
| Frame size | 20 ms | 320 samples at 16 kHz |
| Ring buffer | 32,000 samples | 2 s at 16 kHz |
| Turn commit (capstone) | 0.6 | Plus 500ms of VAD silence |

## Silero VAD segments
type: annotated-code
lang: python
from: phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md

```python
from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
audio = torch.tensor(waveform_16k, dtype=torch.float32)
segments = get_speech_timestamps(
    audio, vad, sampling_rate=16000,
    threshold=0.5,  # 0.3 is sensitive
    min_speech_duration_ms=250,  # drops coughs
    min_silence_duration_ms=500,  # silence hangover
    speech_pad_ms=300,  # pre-roll pad
)
for s in segments:
    print(f"{s['start']/16000:.2f}s - {s['end']/16000:.2f}s")
```

## Barge-in and the flush trick
type: annotated-code
lang: python
from: phases/06-speech-and-audio/12-voice-assistant-pipeline/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md

```python
tts_task = asyncio.create_task(tts_loop())
while True:
    chunk = await mic.get()
    if vad(chunk):  # user speaks during TTS
        tts_task.cancel()  # stop the reply
        await speaker.stop()
        await new_turn()  # restart STT
        break

def flush_on_end(stt_client, audio_buffer):
    stt_client.send_audio(audio_buffer)
    stt_client.send_flush()  # forces output now
    return stt_client.recv_transcript(timeout_ms=150)
```

## Run the lessons
type: command-table
from: phases/06-speech-and-audio/11-real-time-audio-processing/code/main.py, phases/06-speech-and-audio/12-voice-assistant-pipeline/docs/en.md, phases/19-capstone-projects/03-realtime-voice-assistant/code/main.py, phases/19-capstone-projects/03-realtime-voice-assistant/code/ts/README.md

| Command | What it does |
|---|---|
| `python3 code/main.py` | Runs the simulator of a lesson |
| `python main.py` | Capstone scheduler, clean and barge-in calls |
| `pip install silero-vad` | Real VAD in place of the stub |
| `npm install` | Installs the TypeScript client |
| `npm start` | Two offline sessions and a ws probe, exits 0 |
| `npm start -- --serve` | Keeps the ws server up |
| `npm test` | node --test runner via tsx |
| `npm run typecheck` | Runs `tsc --noEmit` |

## Core terms
type: concepts
from: phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, phases/19-capstone-projects/03-realtime-voice-assistant/docs/en.md, phases/14-agent-engineering/22-voice-agents-pipecat-livekit/docs/en.md

- **Ring buffer**: fixed circular buffer. No allocations in the hot path.
- **Jitter buffer**: reorders late packets. Too small gives gaps. Too large adds latency.
- **Pre-roll**: audio kept before VAD fires. It prevents the first-word clip.
- **Silence hangover**: the wait after VAD returns to 0, before end of turn.
- **Semantic endpoint**: a classifier that reads the words, not only the silence.
- **Flush trick**: flush the STT at end of speech. 500 ms of look-ahead ends in ~125 ms.
- **Filler**: a short phrase when a tool takes more than 300ms.
- **DOWNSTREAM**: Pipecat frames from source to sink, audio in and speech out.
- **UPSTREAM**: Pipecat control frames, such as cancel, metrics and barge-in.

## Full-duplex Moshi, one 80 ms step
type: flow
from: phases/06-speech-and-audio/15-streaming-speech-to-speech-moshi-hibiki/docs/en.md, phases/06-speech-and-audio/15-streaming-speech-to-speech-moshi-hibiki/code/main.py

1. **Mimi**: encodes user audio at 12.5 Hz with 8 codebooks.
2. **Temporal Transformer**: 7B parameters. Reads the user tokens and its own last tokens.
3. **Temporal Transformer**: emits the next text token, the inner monologue.
4. **Depth Transformer**: 2 layers. Predicts the 8 codebooks one after the other.
5. **Client**: `decode_audio_mimi` turns the tokens into audio and plays it.
6. **Budget**: 80 ms frame plus 80 ms acoustic delay is 160 ms. 200 ms on one L4 GPU.

## Troubleshooting
type: troubleshooting
from: phases/06-speech-and-audio/12-voice-assistant-pipeline/docs/en.md, phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| First word is missing | VAD starts a beat too late | Threshold 0.3 and a pre-roll |
| Bot talks over the user | LLM keeps going after interrupt | Wire VAD to cancel the LLM |
| "Thanks for watching" on silence | Whisper on silent frames | Always VAD-gate |
| ASR hears the bot voice | No echo cancellation | WebRTC AEC3 |
| Agent cuts user off mid-sentence | Hangover too short | 500-800 ms and a turn detector |
| Slow first reply | TTS warm-up on first request | Warm it with a dummy run |
| Audible vocoder artifacts | Sub-200 ms TTS chunks | 320 ms chunks |
| Glitches under load | GIL starves the audio thread | C-callback audio library |

## Glossary
type: glossary
from: phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, phases/14-agent-engineering/22-voice-agents-pipecat-livekit/docs/en.md

- **AEC**: echo cancellation. Subtracts the speaker to mic path.
- **TPR @ 5% FPR**: VAD benchmark point. Silero 87.7%, WebRTC VAD 50.0%.
- **MultimodalAgent**: LiveKit class. Audio in, audio out, no text in the middle.
- **VoicePipelineAgent**: LiveKit class. STT, LLM, TTS with text-level control.

## Q&A
type: qa
from: phases/06-speech-and-audio/11-real-time-audio-processing/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, phases/06-speech-and-audio/15-streaming-speech-to-speech-moshi-hibiki/docs/en.md, phases/14-agent-engineering/22-voice-agents-pipecat-livekit/docs/en.md, phases/19-capstone-projects/03-realtime-voice-assistant/docs/en.md

- **How fast do humans take turns?** About 230 ms. Above 1500 ms feels broken.
- **Pipeline or Moshi?** Use a pipeline for tools. Moshi is not trained for tool calling.
- **Why no flush with Whisper streaming?** It is block-based and always waits for chunks.
- **What is end-to-end premium?** 450 to 600 ms. 800 to 1200 ms is common.
- **What is the capstone bar?** First-audio-out p50 under 800ms, false-cutoff under 3%.
- **What does barge-in need in Pipecat?** UPSTREAM cancel frames back to TTS and LLM.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓