TAI BUI
Back to curriculum
CapstonePython (agent + pipeline), TypeScript (web client)

Capstone 03 — Real-Time Voice Assistant (ASR to LLM to TTS)

A voice agent that feels right answers in under 800ms end to end, knows when you've stopped talking, handles barge-in, and calls a tool without stalling. Retell, Vapi, LiveKit Agents, and Pipecat all hit this bar in 2026, and they share one shape: streaming ASR, a turn-detector, a streaming LLM, and streaming TTS, wired through WebRTC with a tight latency budget at every hop. Build one, measure WER, MOS, and false-cutoff rate, then run it under packet loss.

TIME: 30 hoursPREREQUISITES: Phase 6 (speech and audio), Phase 7 (transformers), Phase 11 (LLM engineering), Phase 13 (tools), Phase 14 (agents), Phase 17 (infrastructure)

Learning Objectives

  • Describe the five streaming stages of a realtime voice agent (audio in, ASR, turn detection, LLM, TTS) and why each must pass partial results downstream.
  • Build a turn-taking gate that commits a turn only after 500ms of VAD silence and a turn-detector completion score above 0.6.
  • Implement barge-in cancellation and a tool side channel that emits a filler when a call runs past 300ms.
  • Measure WER, first-audio-out p50, false-cutoff rate, and TTS MOS, including a run with 3% packet loss injected.

Problem

A realtime voice assistant is a pipeline that listens to speech, decides when the user has finished, and answers aloud while audio is still streaming in both directions. Voice has been the fastest-moving AI UX category of 2025-2026, and the technical ceiling dropped every quarter. OpenAI Realtime API, Gemini 2.5 Live, Cartesia Sonic-2, ElevenLabs Flash v3, LiveKit Agents 1.0, and Pipecat 0.0.70 all put sub-800ms first-audio-out within reach.

Latency alone doesn't make it feel right. The feel comes from not cutting the user off, not getting cut off, recovering from a mid-sentence interruption, calling a tool without stalling the audio, and surviving a jittery mobile network.

Three chained REST calls won't get you there. The architecture has to be streaming end to end, and building it makes the failure modes visible: a VAD tuned for phone audio firing on a background TV, a turn-detector waiting for punctuation that never comes, a TTS that buffers 400ms before emitting. The capstone is to fix these one at a time under load and publish a latency-and-quality report.

Warm-up0/3
1. Why do three chained blocking calls (ASR, then LLM, then TTS) feel slow in a live conversation?Warm-up
2. A voice app must decide when a caller has finished speaking. Why is a fixed silence timeout alone a weak signal?Warm-up
3. What does a streaming ASR model give a voice app that a batch ASR model does not?Warm-up
Check: Problem0/3
1. A TV plays in the caller's room and the agent keeps cutting off its own replies. Which component is most likely at fault?Analyze
2. Streaming partials rarely end in punctuation, and the agent answers late even after callers stop. What is the likeliest flaw?Analyze
3. Every stage is fast, but p50 first-audio-out misses 800ms, and traces show the TTS holds 400ms before emitting. Which requirement is being violated?Analyze

Concept

A realtime voice agent is a pipeline of five streaming stages, each passing partial results downstream before its own input is finished:

  1. Audio in. WebRTC from a browser, or PSTN from a phone line.
  2. ASR. Streaming partial transcripts from Deepgram Nova-3 or faster-whisper.
  3. Turn detection. VAD plus a small turn-detector model that reads partial transcripts for completion cues.
  4. LLM. Streams tokens as soon as the turn is judged complete.
  5. TTS. Streams audio out within ~200ms of the first LLM token.

Three concerns cut across all of them. Barge-in means that when the user starts speaking while the agent is speaking, the TTS cancels and the ASR picks up immediately. Tool use covers mid-conversation function calls (weather, calendar), which run on a side channel so the audio never stalls. If a call takes longer than 300ms, the agent pre-fills an acknowledgement ("one second..."). Backpressure is what happens under packet loss: partial transcripts are held, VAD raises the speech-gate threshold, and the agent avoids speaking over a message the user hasn't acknowledged.

The measurement bar is quantitative, and the numbers are the deliverable:

  • WER under 8% on the Hamming VAD benchmark at 15 dB SNR
  • First-audio-out p50 under 800ms across 100 measured calls
  • False-cutoff rate under 3%
  • MOS above 4.2 on TTS
  • 50 concurrent calls on a single g5.xlarge
Check: Concept0/16
1. What does a turn detector decide in a realtime voice agent?Define
2. What is barge-in?Define
3. What does first-audio-out measure?Define
4. What is a filler in a voice agent?Define
5. What does MOS rate in this capstone's measurement bar?Define
6. What is the tool side channel in a voice agent?Define
7. What does word error rate (WER) measure?Define
8. Which metric directly measures over-eager turn closure or inappropriate barge-in?Define
9. Under packet loss, which two adaptive behaviors does the pipeline rely on?Define
10. Why must each stage pass partial results downstream before its own input is finished?Explain
11. What does the turn detector add on top of VAD silence?Explain
12. Why is a short filler such as "one second, let me check" emitted when a tool exceeds about 300ms?Explain
13. Why do tool calls run on a side channel instead of inline in the audio path?Explain
14. Why does the measurement bar include both WER and MOS?Explain
15. An eval shows WER of 6%, p50 first-audio-out of 700ms and MOS of 4.4, yet callers say the agent feels rude. Which metric is missing?Analyze
16. A commuter app has callers on mobile networks with frequent packet loss. Which design handles it?Design

Architecture

browser / Twilio PSTN
        |
        v
   WebRTC / SIP edge
        |
        v
  LiveKit Agents 1.0  (or Pipecat 0.0.70)
        |
   +----+--------------+--------------+-----------------+
   |                   |              |                 |
   v                   v              v                 v
  ASR              VAD v5         turn-detector     side-channel
(Deepgram         (Silero)          (LiveKit)        tools
 Nova-3 /         speech-gate    completion score    (weather,
 Whisper-v3)      per 20ms        on partials        calendar)
   |                   |              |
   +--------+----------+--------------+
            v
        LLM (streaming)
     GPT-4o-realtime / Gemini 2.5 Flash /
     cascaded Claude Haiku 4.5
            |
            v
        TTS streaming
     Cartesia Sonic-2 / ElevenLabs Flash v3
            |
            v
     audio back to caller
            |
            v
   OpenTelemetry voice traces -> Langfuse
Check: Architecture0/1
1. What does a VAD model such as Silero VAD v5 output in this pipeline?Define

Stack

  • Transport: LiveKit Agents 1.0 (WebRTC) plus Twilio PSTN gateway; Pipecat 0.0.70 as the alternate framework
  • ASR: Deepgram Nova-3 (streaming, sub-300ms first partial) or faster-whisper Whisper-v3-turbo self-hosted
  • VAD: Silero VAD v5 plus the LiveKit turn-detector (small transformer that reads partial transcripts)
  • LLM: OpenAI GPT-4o-realtime for tight integration, Gemini 2.5 Flash Live, or cascaded Claude Haiku 4.5 (streaming completions, separate audio path)
  • TTS: Cartesia Sonic-2 (lowest first-byte), ElevenLabs Flash v3, or open-source Orpheus for self-host
  • Tools: FastMCP side-channel for weather/calendar/booking; agent pre-emits filler if tool takes >300ms
  • Observability: OpenTelemetry voice spans, Langfuse voice traces with audio replay
  • Deployment: single g5.xlarge (24GB VRAM) for self-hosted Whisper + Orpheus; hosted APIs for lowest latency
Check: Stack0/3
1. How does a cascaded LLM option such as Claude Haiku 4.5 differ from GPT-4o-realtime in this stack?Explain
2. Audio must not leave your own infrastructure, but you still want a fast agent. Which stack in this lesson fits?Design
3. You swap hosted Deepgram Nova-3 for faster-whisper on a g5.xlarge. Which pair of measurements shows the cost of the swap?Design

Build It

  1. WebRTC session. Stand up a LiveKit room and a web client that streams microphone audio. On the server, attach an agent worker that joins the room.

  2. ASR streaming. Feed 20ms PCM frames to Deepgram Nova-3 (or faster-whisper on GPU). Subscribe to partial and final transcripts. Log per-partial latency.

  3. VAD and turn detector. Run Silero VAD v5 on the frame stream. On speech-end event, fire the LiveKit turn-detector against the latest partial transcript. Commit to "turn complete" only when VAD reports 500ms of silence and the turn-detector scores completion above 0.6.

  4. LLM stream. On turn complete, start the LLM call with the running conversation plus the final transcript. Stream tokens out. At the first token, hand off to TTS.

  5. TTS stream. Cartesia Sonic-2 streams audio chunks back. The first chunk must leave the server within 200ms of the first LLM token. Emit chunks to the LiveKit room, and the client plays them through the WebRTC jitter buffer.

  6. Barge-in. When VAD detects new user speech while TTS is playing, cancel the TTS stream immediately, drop the remaining LLM output, and re-arm the ASR. Publish a tts_canceled span.

  7. Tool side channel. Register weather and calendar as function-calling tools. When one is invoked, fire the call concurrently. If it hasn't resolved within 300ms, have the LLM emit "one second, let me check" as a filler, then resume once the tool returns.

  8. Eval harness. Record 100 calls. Compute WER (against a held-out transcript), false-cutoff rate (TTS cancelled while user was mid-sentence), first-audio-out p50, TTS MOS (human or NISQA), and a jitter-loss test (drop 3% of packets).

  9. Load test. Drive 50 concurrent calls on a single g5.xlarge with a synthetic caller. Measure sustained first-audio-out p95.

Check: Build It0/15
1. What does the client-side jitter buffer do for the audio stream?Define
2. Why does the gate require both 500ms of VAD silence and a completion score above 0.6?Explain
3. When the user starts speaking while the agent is mid-response, what must happen for barge-in to feel right?Explain
4. Why does barge-in also drop the remaining LLM output instead of only stopping the audio?Explain
5. Why does the TTS start streaming as soon as the first LLM token arrives instead of after the full reply?Explain
6. Why does the eval harness include a run with 3% of packets dropped?Explain
7. The user speaks while TTS is playing and the barge-in path runs. Which span does Build It say to publish?Explain
8. VAD reports 600ms of silence after the user says "book a table for", and the turn detector scores the partial 0.55. What does the gate do?Apply
9. VAD reports 400ms of silence after "what is the weather in tokyo tomorrow", and the turn detector scores it 0.9. Does the turn commit?Apply
10. A weather call returns after 250ms. What does the caller hear before the answer?Apply
11. The first LLM token arrives at 3400ms. Which first-audio-out time meets the requirement for the first TTS chunk?Apply
12. A voice agent shows 3 false cutoffs in 100 recorded calls. Does it meet the false-cutoff bar?Apply
13. A calendar tool takes 1.2 seconds to respond and callers hear dead air. Which change fits this lesson's design?Analyze
14. p50 first-audio-out is 700ms, but callers say the agent keeps cutting them off mid-sentence. What do you adjust first?Analyze
15. You have one week and cannot tell whether threshold changes help. What do you build first?Design

Use It

Running python main.py replays a simulated caller through the scheduler. Session 1 (the weather call) prints this:

$ python main.py
=== session 1: clean call with tool (weather) ===
  120ms LISTENING
  2840ms TURN COMPLETE (score=0.75) partial='what is the weather in tokyo tomorrow'
  2840ms LLM call fired
  3140ms filler 'one second, let me check'
  3260ms tool result: 68/52 partly cloudy
  3400ms LLM first token
  3580ms TTS first audio-out
  turn_complete  @ 2840ms
  first_llm_tok  @ 3400ms
  first_audio_out @ 3580ms
  turn latency   = 740ms

The timestamps are simulated frame times, not wall-clock. The turn commits at 2840ms because seven words at 320ms each plus 120ms of leading silence end at 2360ms, and the 500ms silence gate then has to elapse. The 740ms latency is the three simulated delays stacked after the commit: the tool call (420ms), the LLM time-to-first-token (140ms), and the TTS start (180ms). The filler fires 300ms into the tool call, before the result arrives. These constants are stand-ins, so treat the 740ms as a demonstration of the accounting, not a benchmark.

Check: Use It0/2
1. In the session 1 run the tool call starts at 2840ms and takes 420ms. When does the filler fire?Apply
2. After the turn commits at 2840ms, the tool takes 420ms, the LLM time-to-first-token is 140ms and the TTS start is 180ms. What is the turn latency?Apply

Ship It

outputs/skill-voice-agent.md is the deliverable. Given a domain (customer support, scheduling, or kiosk), it stands up a LiveKit agent whose ASR/VAD/LLM/TTS pipeline is tuned to the measurement bar. The rubric:

WeightCriterionHow it is measured
25End-to-end latencyp50 first-audio-out under 800ms across 100 recorded calls
20Turn-taking qualityFalse-cutoff rate under 3% on the Hamming VAD benchmark
20Tool-use correctnessMid-conversation tool calls that return the right data without stalling audio
20Reliability under packet lossWER and turn-taking stability with 3% packet drop injected
15Eval harness completenessReproducible measurements with public config
100
Check: Ship It0/2
1. A build earns full marks except on reliability under packet loss, where it earns none. What is its Ship It score?Apply
2. A build measures p50 first-audio-out of 650ms over 100 calls but a 5% false-cutoff rate on the Hamming VAD benchmark. Which rubric criterion does it miss?Analyze
Author's Note

I'd tune turn-taking before latency. A 700ms agent that cuts people off mid-sentence is worse than a 900ms one that waits. The rubric weights turn-taking at 20 against latency's 25, but a user who gets cut off has already failed the call. Build the eval harness in step 8 first, even crudely, so every threshold change (the 500ms silence gate, the 0.6 completion score) shows up as a number instead of a feeling. My bet is that most of your time goes into those two thresholds and the barge-in policy, not into choosing between Cartesia and ElevenLabs.

Exercises

  1. Swap Deepgram Nova-3 for faster-whisper v3 turbo on a g5.xlarge. Measure the latency and WER gap. Identify where CPU-vs-GPU decisions matter.

  2. Add an interruption-arbitration policy: what does the agent do when the user barges in during a tool call? Compare three policies (hard cancel, finish-tool-then-stop, queue next turn).

  3. Run an adversarial turn-detector test: give the user long pauses mid-sentence. Tune the VAD silence threshold and the turn-detector score threshold for lowest false-cutoff without blowing past 900ms.

  4. Deploy the same agent on PSTN via Twilio. Compare PSTN first-audio-out to WebRTC. Explain the jitter-buffer and codec differences.

  5. Add voice activity detection for non-English languages (Japanese, Spanish). Measure the Silero VAD v5 false-trigger rate versus language-specific fine-tunes.

Key Terms

TermWhat people sayWhat it actually means
Turn detection"End of utterance"Classifier that, given VAD silence and a partial transcript, decides the user is done speaking
Barge-in"Interruption handling"Canceling TTS mid-playback when VAD detects new user speech
First-audio-out"Latency"Time from user stops speaking to the first audio packet leaving the server
VAD"Speech gate"Model classifying audio frames as speech vs silence; Silero VAD v5 is the 2026 default
Jitter buffer"Audio smoothing"Client-side buffer that holds packets briefly to absorb network variance
Filler"Acknowledgment token"Short phrase the agent emits to avoid silence when a tool is slow
MOS"Mean opinion score"Perceptual speech quality rating; NISQA is the automated proxy

Further Reading

Code

Ships With