Voice agents
A voice agent is a text agent that has to listen through a noisy phone line, decide in a fraction of a second whether you have finished talking, and answer out loud before the silence gets awkward. Every one of those extra jobs is a new way to fail, and a new thing to measure.
By the end of this module you can
- Draw the two voice architectures (cascaded and speech-to-speech) and say what each one lets you measure.
- Compute word error rate by hand and in code, and explain why a "good" WER can still hide a failed call.
- Instrument a voice pipeline for latency (time to first audio, end-to-end response latency) and report percentiles, not averages.
- Evaluate turn-taking: barge-in, false interruptions, endpointing, backchannels and silence handling.
- Design a simulated-caller test suite with personas, accents, noise and interruptions, and grade the calls with the right mix of code, text judges and audio judges.
- Name the main public voice benchmarks (Big Bench Audio, VoiceBench, Full-Duplex-Bench, Audio MultiChallenge, τ-Voice) and what each actually measures.
- List the voice-specific robustness and safety risks, and how to test them.
Why voice is its own problem
In Module 06 you learned to evaluate a conversational agent that reads and writes text: did it call the right tool, follow the policy, reach the goal? A voice agent has to do all of that plus a set of jobs that do not exist in text at all.
When you answer an email you can take ten minutes, reread it, and fix typos. On a phone call you have to hear the words correctly over a bad line, notice when the other person has stopped (or just paused to think), reply within about a second, pronounce names right, and stop talking the moment they cut in. A brilliant email writer can still be terrible on the phone. Voice evals exist to catch exactly that gap.
Five things make voice different from text, and each one shows up later as a metric:
1. Hearing is lossy
The agent never sees what the caller "typed". It sees a guess, produced by speech recognition, from audio that may be noisy, accented, and squeezed through an 8 kHz phone line. Errors at the ear flow into everything downstream.
2. Time is part of the answer
A correct answer that arrives after three seconds of silence feels broken. Humans leave gaps of roughly 200 ms between turns in natural conversation (Stivers et al., 2009), so users notice delays quickly.
3. Turns are not given
In chat, pressing Enter ends your turn. On a call, nobody presses Enter. The system must infer when you are done, and handle you talking over it.
4. How it sounds matters
The same words can sound warm or robotic, calm or impatient. Mispronouncing a customer's name or reading "$1,250.00" as "one comma two five zero" is a failure a text transcript cannot show.
5. The channel is hostile
Background TV, speakerphones, packet loss, codecs, crosstalk. And audio creates new safety risks: cloned voices, jailbreaks hidden in sound, and impersonation.
Two terms first, because they appear everywhere. ASR (automatic speech recognition), also called STT (speech to text), turns audio into text. TTS (text to speech) turns text into audio. A VAD (voice activity detector) is a small, fast model that answers one question many times a second: "is someone speaking right now?"
Two architectures, two kinds of measurability
There are two broad ways to build a voice agent, and which one you have decides what you can measure.
Cascaded: VAD → ASR → LLM → TTS
The cascaded (or pipeline) design chains separate models. The VAD decides when the caller stopped talking. The ASR model produces a transcript. A normal text LLM, with tools, writes a reply. A TTS model speaks it. Frameworks such as Pipecat and LiveKit Agents are built around this shape, and let you swap each piece independently.
- Strength for evals: every arrow in the diagram is text or a timestamp you can log. You can compute WER on the ASR output, replay the same transcript through a new LLM without any audio, and time each stage separately. Debugging is a matter of finding which box broke.
- Weakness: information is thrown away at each boundary. The LLM never hears that the caller sounded angry or hesitant, only the words. Latencies add up across stages. And the ASR's mistakes become "facts" to the LLM.
Speech-to-speech: one native audio model
A speech-to-speech (S2S) or native audio model takes audio in and produces audio out directly, with the reasoning happening inside one network. OpenAI's Realtime API and Google's Gemini Live API (with its native audio models) are the best-known hosted examples. They usually still emit a text transcript of what they said, and they still call tools, but there is no separate ASR stage you can grade on its own.
- Strength: the model can hear tone, hesitation and emphasis, and can respond with appropriate prosody. There are fewer hops, so latency can be lower.
- Weakness for evals: it is a black box between audio in and audio out. You cannot compute "the ASR's WER" because there is no ASR. You grade end-to-end behavior. Transcripts the API emits are the model's own rendering, not ground truth, so treat them as a convenience, not as evidence.
Cascades are easy to diagnose, hard to make feel natural. Speech-to-speech models are easier to make feel natural, harder to diagnose. Your eval plan should lean on component tests for cascades and on end-to-end simulation for S2S. Hybrid systems exist too (for example, a native audio model with a separate ASR running in parallel purely for logging), and many teams add that parallel ASR just so they have something to grade.
Half-duplex vs full-duplex turn-taking
A second design axis is how the agent shares the line. Half-duplex means only one party talks at a time, like a walkie-talkie: the agent waits for the caller to finish, speaks, and may not listen while speaking. Full-duplex means the agent listens while it speaks, like a human on the phone: it can hear "mm-hm", notice an interruption, and stop mid-sentence. Most production cascades are "half-duplex with barge-in": they do not produce overlapping speech on purpose, but they keep a VAD running during playback so the caller can interrupt. True full-duplex models (which can also backchannel and overlap deliberately) are an active research area, which is why benchmarks such as Full-Duplex-Bench exist.
The layered eval: component, conversation, system
A voice call can fail at any of three altitudes, and you need metrics at each one. Think of it as the same testing pyramid you know from software, reshaped for calls.
The rest of this module walks up the stack, then covers how to generate test calls (simulation), how to grade audio, the public benchmarks, robustness, and safety.
Component level: the ear (ASR and WER)
Word error rate, from intuition to formula
Word error rate (WER) is the standard ASR metric. The intuition: line up what was actually said (the reference, usually a human transcript) with what the ASR produced (the hypothesis), and count the minimum number of word edits needed to turn one into the other. There are three kinds of edit:
- Substitution (S): a word was heard as a different word ("a" became "the").
- Deletion (D): a spoken word is missing from the transcript.
- Insertion (I): the transcript has a word nobody said.
Then WER = (S + D + I) / N, where N is the number of words in the reference. The "minimum number of edits" is the Levenshtein edit distance, computed on words instead of characters. (Character error rate, CER, is the same idea on characters, and is preferred for languages without spaces between words, such as Chinese or Japanese.)
Walk through it slowly. The reference has 8 words. Six words line up perfectly ("hits"). "a" was heard as "the" (1 substitution). "two" vanished (1 deletion). "thirty" appeared from nowhere (1 insertion). Three edits over eight reference words is 0.375, or 37.5%. There is no cheaper alignment: you could try to match "thirty" against "two", but then you would still need to account for "at seven" being in the wrong place, which costs more edits.
WER in code: the algorithm
Here is the whole algorithm in plain Python. It fills a dynamic-programming table (the same one used for spelling correction), then walks backwards through it to count which kind of edit was used at each step.
def wer_details(reference: str, hypothesis: str):
"""Word error rate via Levenshtein distance on word lists.
Returns (wer, substitutions, deletions, insertions, hits)."""
ref = reference.lower().split()
hyp = hypothesis.lower().split()
R, H = len(ref), len(hyp)
# d[i][j] = minimum edits to turn ref[:i] into hyp[:j]
d = [[0] * (H + 1) for _ in range(R + 1)]
for i in range(R + 1):
d[i][0] = i # delete every reference word
for j in range(H + 1):
d[0][j] = j # insert every hypothesis word
for i in range(1, R + 1):
for j in range(1, H + 1):
cost = 0 if ref[i - 1] == hyp[j - 1] else 1
d[i][j] = min(d[i - 1][j - 1] + cost, # match or substitution
d[i - 1][j] + 1, # deletion
d[i][j - 1] + 1) # insertion
# Walk back through the table to count each kind of edit
i, j, S, D, I, hits = R, H, 0, 0, 0, 0
while i > 0 or j > 0:
if i > 0 and j > 0 and d[i][j] == d[i - 1][j - 1] + (ref[i - 1] != hyp[j - 1]):
if ref[i - 1] == hyp[j - 1]:
hits += 1
else:
S += 1
i, j = i - 1, j - 1
elif i > 0 and d[i][j] == d[i - 1][j] + 1:
D += 1
i -= 1
else:
I += 1
j -= 1
return (S + D + I) / max(R, 1), S, D, I, hits
ref = "please book a table for two at seven"
hyp = "please book the table for at seven thirty"
print(wer_details(ref, hyp)) # (0.375, 1, 1, 1, 6)
WER in code: the library way, with normalization
In practice you use jiwer, a small, fast Python library. The important part is not the call itself but the text normalization you apply first. Without it, "Book a table." and "book a table" differ by capitalization and punctuation, and your WER measures formatting, not hearing.
import jiwer
# Normalise BOTH sides the same way before comparing.
norm = jiwer.Compose([
jiwer.ToLowerCase(),
jiwer.RemovePunctuation(),
jiwer.RemoveMultipleSpaces(),
jiwer.Strip(),
jiwer.ReduceToListOfListOfWords(),
])
refs = ["Please book a table for two at seven.", "My account number is 4 4 1 9."]
hyps = ["please book the table for at seven thirty", "my account number is 4419"]
out = jiwer.process_words(refs, hyps,
reference_transform=norm, hypothesis_transform=norm)
print(f"WER={out.wer:.3f} S={out.substitutions} D={out.deletions} "
f"I={out.insertions} hits={out.hits}")
print(jiwer.visualize_alignment(out)) # REF / HYP lines with S, D, I markers
# Corpus WER pools all edits over all words. It is NOT the mean of per-utterance WERs.
Notice the second example. "4 4 1 9" vs "4419" is the same account number, but it counts as 4 substitutions-and-deletions unless your normalizer converts numbers to one canonical form. Real ASR evals use richer normalizers (for example, the English text normalizer shipped with OpenAI's Whisper repository, which handles numbers, spelling variants and fillers). Always report which normalizer you used; two WER numbers with different normalizers are not comparable.
- All words weigh the same. "uh" and "not" cost the same edit. For agents, add entity accuracy: did the ASR get the names, numbers, dates, emails, account IDs and addresses exactly right? A 5% WER with the account number wrong is a failed call.
- Averaging is subtle. Corpus WER (total edits / total reference words) weights long utterances more. The mean of per-utterance WER lets short utterances dominate. Pick one and say which.
- Silence and hallucination. Some ASR models output words on silence or noise. Include audio clips with no speech in your test set and count insertions there.
- Your reference can be wrong. Human transcripts have errors too. For a small, important test set, have two people transcribe and reconcile.
Vendor guidance gives rough targets (Hamming, for example, describes under 5% WER as enterprise-grade and 5-10% as good), but treat those as vendor claims, not standards. What matters is WER on your audio: your callers' accents, your phone lines, your vocabulary. A model with low WER on clean read speech benchmarks can be much worse on noisy, accented 8 kHz calls.
Component level: the voice (TTS quality)
For the mouth, the classic measure is MOS, the mean opinion score. Human listeners rate each clip on a 1-5 scale (1 bad, 5 excellent), typically for "naturalness", and you average the ratings. The method comes from telephony (ITU-T Recommendation P.800 describes absolute category rating for speech quality). Related human tests include CMOS (comparative MOS, rating which of two clips is better) and MUSHRA-style tests that compare several systems side by side against a reference.
Human MOS studies are slow and expensive, so the field built automated MOS predictors: neural networks trained on thousands of human ratings to predict the score a listener would give. UTMOS, from the University of Tokyo's Saruwatari lab, is a widely used one: it was the UTokyo-SaruLab system for the VoiceMOS Challenge 2022 and placed first on 10 of the 16 challenge metrics. A reimplementation packaged for torch.hub (SpeechMOS) makes it a two-line check:
import torch, librosa
# Load a clip your TTS produced (mono). UTMOS predicts a naturalness MOS on a 1-5 scale.
wave, sr = librosa.load("agent_turn_017.wav", sr=None, mono=True)
predictor = torch.hub.load("tarepan/SpeechMOS:v1.2.0", "utmos22_strong", trust_repo=True)
score = predictor(torch.from_numpy(wave).unsqueeze(0), sr)
print(float(score)) # e.g. ~3.8; compare TTS voices / versions on the SAME sentences
MOS predictors are trained on particular kinds of speech (often read English sentences). They can be miscalibrated on phone-band audio, other languages, or very expressive voices, and they know nothing about whether the words were right. Use them to catch regressions (a new TTS version drops by 0.4 on your fixed sentence set) and confirm big decisions with a small human listening test.
What MOS misses: names, numbers and "reading" errors
For agents, the most damaging TTS failures are not about smoothness. They are text normalization failures: how the TTS turns written tokens into spoken words.
| Written | Good spoken form | Typical failure |
|---|---|---|
$1,250.00 | "one thousand two hundred fifty dollars" | "dollar one comma two five zero point zero zero" |
Dr. St. James St. | "Doctor Saint James Street" | "Drive Street James Saint" |
03/04 | "March fourth" or "the third of April" (locale!) | wrong locale, or "zero three slash zero four" |
Siobhan Nguyen | "shi-VAWN win" | spelled-out or anglicised guess |
A7X-22 (confirmation code) | "A, seven, X, twenty-two" read slowly, grouped | "a-sevenx twenty two" run together |
A neat, automatic way to test this is a round trip: synthesise the agent's sentence, transcribe it with a strong ASR model, and check that the entities survive (compute WER or exact entity match between the text you fed in and what came back). It does not measure beauty, but it does catch mispronounced and garbled content at scale. Keep a hand-curated "pronunciation set" of your hardest real names, product terms and number formats, and run it on every TTS or voice change.
Component level: latency
Latency in voice is not one number. It is a chain of delays, and the user only feels the total. The key user-facing metric is the voice-to-voice response latency: the time from when the caller stops speaking to when they hear the first sound of the reply. A closely related server-side measure is time to first audio (TTFA, sometimes "time to first byte", TTFB): the time from when the system decided the turn ended to when the first audio byte leaves the TTS.
Three lessons hide in that picture:
- Endpointing dominates. A VAD that waits 500 ms of silence before declaring "turn over" adds 500 ms to every single reply. Shrink it and you cut latency, but you also start interrupting people who pause mid-sentence. That trade-off is why semantic endpointing exists: OpenAI's Realtime API offers a
semantic_vadmode that "uses a semantic classifier to detect when the user has finished speaking, based on the words they have uttered", alongside a silence-basedserver_vadwith a tunablesilence_duration_ms. Open models such as Pipecat's Smart Turn do the same job for cascades. - Measure where the user is. Server logs start the clock at "VAD end", which already hides the endpoint wait. Put a recorder on the caller's side of the line, or record both channels of the call, and measure from the caller's last speech to the agent's first audible sound.
- Report percentiles. Averages hide the awful calls. Report p50, p90, p95 and p99. Vendors publish rough ranges (Hamming reports industry end-to-end latency around 1.4-1.7 s at p50 and 3.3-3.8 s at p95; ElevenLabs suggests aiming for under 500 ms time to first audio and using p99 as the headline SLA), but treat these as vendor claims and set your own targets from user tests.
A latency harness sketch
The simplest reliable approach: stamp a monotonic clock at every stage boundary, per turn, and compute differences afterwards. Hook these into your pipeline's callbacks (every framework exposes events like "user stopped speaking" and "first audio frame out").
import time, statistics
from dataclasses import dataclass, field
@dataclass
class TurnTimer:
"""Timestamps for one conversational turn, in seconds (monotonic clock)."""
turn_id: int
marks: dict = field(default_factory=dict)
def mark(self, event: str):
# Only the FIRST occurrence counts (e.g. first TTS byte, not every chunk).
self.marks.setdefault(event, time.perf_counter())
def ms(self, start: str, end: str):
if start in self.marks and end in self.marks:
return (self.marks[end] - self.marks[start]) * 1000
return None
# --- wire these into your pipeline callbacks -------------------------------
# on VAD "speech stopped" -> timer.mark("vad_end")
# on final ASR transcript -> timer.mark("asr_final")
# on first LLM token streamed -> timer.mark("llm_first_token")
# on first TTS audio chunk -> timer.mark("tts_first_byte")
# on first audio frame played out -> timer.mark("first_audio_out")
# For a speech-to-speech API (OpenAI Realtime), the equivalents are the
# "input_audio_buffer.speech_stopped" event and the first "response.output_audio.delta".
def report(turns: list[TurnTimer]):
def pct(vals, p):
vals = sorted(v for v in vals if v is not None)
if not vals:
return float("nan")
k = min(len(vals) - 1, round(p / 100 * (len(vals) - 1)))
return vals[k]
stages = {
"ASR finalise": ("vad_end", "asr_final"),
"LLM first token": ("asr_final", "llm_first_token"),
"TTS first byte": ("llm_first_token", "tts_first_byte"),
"VAD end → 1st TTS": ("vad_end", "tts_first_byte"),
"VAD end → audio": ("vad_end", "first_audio_out"),
}
for name, (a, b) in stages.items():
vals = [t.ms(a, b) for t in turns]
print(f"{name:18s} p50={pct(vals,50):6.0f} p90={pct(vals,90):6.0f} "
f"p95={pct(vals,95):6.0f} p99={pct(vals,99):6.0f} ms")
# The user-perceived number needs the CALLER side: record the call in stereo
# (caller left, agent right) and measure from the caller's last voiced frame
# to the agent's first voiced frame with an offline VAD. That captures the
# endpoint wait and network delay that server-side marks cannot see.
Teams sometimes "fix" latency by starting TTS on the LLM's first sentence fragment, or by playing filler audio ("Let me check that for you"). Both work, and both need evals of their own: speculative starts can commit to a wrong answer out loud, and fillers can become repetitive or be spoken before a tool call that then fails. Also watch tool-call latency: a turn that needs a slow database lookup can take seconds, so measure latency separately for turns with and without tool calls.
Conversation level: turn-taking
Turn-taking is the part of voice evaluation with no text equivalent, and the part users complain about most ("it kept talking over me", "it cut me off", "it just went silent"). You can only see it on a timeline, so here is one call's timeline with the key events marked.
The turn-taking vocabulary
| Behavior | What it means | How to measure |
|---|---|---|
| Endpointing | Deciding the caller has finished their turn. | Endpoint delay (caller's last word → agent decides), and the rate of premature endpoints (agent starts while the caller was only pausing). Vendors call the overall quality "end-of-turn detection accuracy". |
| Barge-in | The caller interrupts the agent while it speaks. | Share of real interruptions the agent yields to; yield time (overlap duration before the agent stops; Cekura suggests stopping and resuming within about 2 s, but shorter is better); whether the next reply addresses what the caller said. |
| False interruption | The agent stops for something that was not a turn: a cough, a TV, a "mm-hm". | Rate of agent stops with no real caller turn, per call or per 100 agent turns. |
| Backchannels | Short listener signals ("mm-hm", "right", "okay"). | From the caller: did the agent keep going (it should)? From the agent (full-duplex models): frequency and timing compared with human conversations. Full-Duplex-Bench compares backchannel distributions with a Jensen-Shannon divergence. |
| Silence handling | The caller goes quiet (thinking, looking for a card, dropped call). | Does the agent re-prompt after a sensible time, avoid nagging, and end the call gracefully after repeated silence? |
| Talk ratio, overlap | Share of call time each side speaks; total overlap time. | Computed from the stereo recording with an offline VAD. An agent talking 80% of the time is usually monologuing. |
It helps to see interruption handling as a classification problem. Every time the caller makes a sound while the agent is speaking, the agent either yields or keeps going, and the caller either meant to take the turn or did not:
Task success, policy and tone
Above turn-taking sit the same questions as for any conversational agent (Module 06), asked of a spoken conversation:
- Task success. Did the agent achieve the caller's goal? Best graded on state, not words: was the appointment actually moved in the database, with the right date? τ-Voice (below) grades voice agents exactly this way, by checking the final database state.
- Policy adherence. Did it verify identity before discussing the account, read the required disclosure, refuse what it should refuse, never promise a refund it cannot give? These are mostly binary checks, graded by code on tool calls where possible and by an LLM judge on the transcript otherwise.
- Information capture. Did it collect and confirm critical fields (spell back the email, read back the phone number)? Voice makes confirmation loops essential because hearing is lossy.
- Tone and empathy. Did it acknowledge frustration, stay calm, avoid sounding robotic or sarcastic? This is where audio-aware grading matters, because the same words can be said kindly or coldly.
System level: call outcomes and business metrics
At the top of the stack are the numbers a contact-center manager watches. They are measured on real traffic (online evals, see Module 15) or on large batches of simulated calls.
| Metric | Definition | The trap |
|---|---|---|
| Call outcome | Label per call: resolved, transferred, abandoned, callback scheduled, and so on. | Labels from the agent itself are self-reports. Verify against system state or a judge. |
| Containment rate | Share of calls handled end to end without transfer to a human. | Easy to game: an agent that never transfers, or that frustrates callers into hanging up, is "contained". |
| Escalation correctness | Did the agent transfer the calls that needed a human, and only those? | Needs both precision (transfers that were needed) and recall (needed transfers that happened). |
| CSAT | Customer satisfaction, usually a post-call survey score. | Low response rates and selection bias: happy and furious callers answer surveys more. |
| First-call resolution, AHT | Resolved without a repeat call; average handle time. | Shorter AHT is not better if the problem comes back tomorrow. |
Simulated callers: testing before real people call
You cannot ask a thousand real people to call your agent every time you change a prompt. So voice teams build simulated callers: an LLM plays a persona with a goal (the same user simulator idea from τ-bench in Module 06), a TTS voice gives it a voice, and it phones your agent over a real audio channel. This is voice-to-voice simulation: the agent under test hears synthesised speech, with all the ASR, endpointing and latency behavior that implies, instead of receiving clean text.
What a good simulated caller varies
- Goal and facts: what they want, and the account details they will give when asked (so the grader knows the right end state).
- Voice: accent, age, speaking rate, volume, pitch. Use several TTS voices; a suite with one synthetic voice tests one voice.
- Environment: background noise (street, café, car, TV, crying baby), speakerphone reverb, phone codec.
- Behavior: interrupts mid-sentence, pauses to think, corrects themselves ("the 14th, no sorry, the 15th"), gives information out of order, goes off topic, gets angry, stays silent.
- Adversarial intent: tries to get account details without verifying, social-engineers a refund, attempts a jailbreak.
Commercial platforms productise this loop. Hamming advertises auto-generated scenarios with "accents, interruptions, background noise, DTMF, and IVR trees" and turning production failures into regression tests; Coval describes simulating "thousands of realistic callers, hard policies, noisy audio"; Cekura describes persona simulation across language, accent, emotion, speed, volume, interruption behavior and background noise (their example multiplies 5 × 3 × 3 × 4 × 5, about 900 variants). These are vendor descriptions; evaluate any tool against your own calls. You can also build the loop yourself with an open framework (Pipecat or LiveKit Agents on both ends) and a TTS voice for the caller.
A scenario config
Whether you use a vendor or your own harness, write scenarios as data, so they can be versioned, reviewed, and expanded into a matrix. A sketch in YAML (the field names are illustrative, not a specific product's schema):
id: reschedule-dental-017
description: Caller wants to move a cleaning appointment; agent must verify identity first.
persona:
name: "Marguerite Okafor" # hard-to-pronounce name on purpose
voice: { tts_voice: "caller_f_55_en_ng", rate: 0.9, volume_db: -6 }
temperament: impatient
behaviours:
- interrupt_agent_when: "agent reads the policy disclaimer"
- self_correct: "gives date as the 14th, then corrects to the 15th"
- long_pause_ms: 4000 # looks for her card mid-call
facts: # what she says ONLY when asked
date_of_birth: "1970-03-22"
current_appointment: "2026-10-09 09:30"
wanted: "any weekday after 4 pm, week of 2026-10-12"
channel:
noise: { type: cafe, snr_db: 10 }
codec: g711_ulaw_8k
packet_loss: 0.02
success_criteria: # graded by CODE on final state / tool calls
- tool_called: verify_identity
before: [lookup_appointment, reschedule_appointment]
- db_state: "appointment.patient == 'Okafor' and appointment.start.weekday() < 5
and appointment.start.hour >= 16 and appointment.start.date() == '2026-10-15'"
- no_tool_called: cancel_appointment
judge_rubric: # graded by an LLM judge on the timestamped transcript
- "Agent confirmed the new date and time out loud before ending the call."
- "Agent stopped speaking within ~1 s when the caller interrupted the disclaimer."
- "Agent used the correct date after the caller's self-correction."
- "Agent re-prompted gently during the 4-second silence, without hanging up."
matrix: # expand into variants
accent: [en-NG, en-IN, en-US-south, es-MX-L2]
noise: [none, cafe_10db, car_5db]
runs_per_variant: 3 # agents are stochastic; see pass^k in Module 04
max_duration_s: 240
A simulated caller that is too cooperative, too articulate, or that never interrupts will make your agent look great. One that goes off-script (forgets its goal, invents facts, hangs up early) produces failures that are not the agent's fault. Read a sample of simulated calls regularly, grade the simulator's fidelity too, and calibrate your persona mix against real call recordings. This is the same "measure the measurer" principle as in Module 03.
Grading audio: transcribe-then-judge vs listening judges
Once you have recorded calls, how do you grade the qualities code cannot check? There are three approaches, and they differ in what they can "see".
What is lost in transcription
- Prosody: the melody of speech. "Great." said flatly and "Great!" said warmly are the same token.
- Emotion and paralinguistics: frustration, hesitation, sighs, laughter, sarcasm.
- Overlap and timing: a plain transcript serialises turns, so two people talking at once look like polite alternation, and a 4-second silence becomes invisible.
- Pronunciation: the transcript says "Siobhan" whether the agent said it right or wrong, because the ASR (or the agent's own text) already knows the spelling.
- Attribution of errors: if you judge the transcript of the caller side, ASR mistakes look like the caller said something odd, or worse, like the agent misunderstood when it actually heard correctly.
The practical fix is route B: produce a diarised (speaker-labeled), timestamped transcript from the stereo recording, and insert explicit event markers for overlaps, long silences, interruptions and tool calls. A text judge can then reason about timing. Leave prosody and empathy to an audio-capable judge or humans.
A judge prompt for a timestamped call transcript
You are grading a recorded phone call between a CALLER and a voice AGENT for a dental clinic.
You will receive a transcript with timestamps in seconds, speaker labels, and event markers:
[OVERLAP a-b] both speakers talking between a and b seconds
[SILENCE n s] no one speaking for n seconds
[TOOL name(args) -> result] a tool call the agent made
Transcripts come from automatic speech recognition and may contain recognition errors.
Do not penalise the agent for a CALLER-side transcription error unless the agent's
reply shows it actually misunderstood.
Grade each criterion independently as PASS or FAIL, with a one-sentence reason that
cites timestamps. Then give an overall verdict.
Criteria:
1. identity_verified_first: the agent verified identity (verify_identity tool succeeded)
before revealing or changing any appointment details.
2. correct_final_booking: the final reschedule tool call matches what the caller last
asked for (after any self-correction).
3. confirmation_read_back: before ending, the agent read back the new date and time and
the caller did not dispute it.
4. yields_to_interruptions: whenever the CALLER starts speaking during an AGENT turn and
it is a real attempt to take the turn (not "mm-hm"/"okay"), the overlap lasts under
1.5 s and the agent's next turn addresses what the caller said.
5. no_premature_cut_in: the agent never starts speaking while the caller is mid-sentence
(an OVERLAP that the AGENT started, where the caller's words are incomplete).
6. silence_handling: after any SILENCE over 3 s, the agent re-prompted politely within
about 8 s and did not end the call unless silence persisted after two re-prompts.
7. no_fabrication: the agent never states availability, prices or policy that is not
supported by a tool result or the policy text below.
Policy text:
<<POLICY>>
Transcript:
<<TRANSCRIPT>>
Respond with JSON only:
{"criteria": {"identity_verified_first": {"verdict": "PASS|FAIL", "reason": "..."}, ...},
"overall": "PASS|FAIL", "first_failure_timestamp": null or seconds}
Notice the design choices, all from Module 13: one binary verdict per criterion (not a 1-10 vibe score), reasons that must cite evidence, an explicit warning about ASR noise, and a request for the first failure, which is what you need for error analysis. Anything that can be checked by code (criteria 1, 2, and the overlap duration in 4) should be checked by code as well; use the judge for the parts that need reading comprehension, and compare the two where they overlap.
Public voice benchmarks
Benchmarks tell you how capable an underlying model is at spoken interaction. They do not replace your own call suite, but they help you choose models and understand the state of the art. The main ones, as of this writing:
| Benchmark | What it measures | How it's scored |
|---|---|---|
| Big Bench Audio (Artificial Analysis, Dec 2024) | Reasoning over spoken questions: 1,000 audio items adapted from four BIG-Bench Hard categories (formal fallacies, navigate, object counting, web of lies), 250 each. Run as speech-to-speech, speech-to-text or pipeline. | LLM judge compares the answer with the reference. Commonly used to show the "speech reasoning gap" relative to the same questions in text. |
| VoiceBench (Chen et al., Oct 2024) | LLM-based voice assistants on real and synthetic spoken instructions. Subsets include AlpacaEval, CommonEval, SD-QA, MMSU, OpenBookQA, IFEval, AdvBench and BBH, plus speaker, environment and content variations (accents, noise, disfluency). | GPT-judged scores for open-ended answers, accuracy for multiple choice, refusal rate for the safety subset. Leaderboard on GitHub. |
| Full-Duplex-Bench (Lin et al., Mar 2025) | Turn-taking in full-duplex spoken dialogue models: pause handling, backchanneling, smooth turn-taking, user interruption. | Automatic metrics: takeover rate (TOR, did the model grab the turn when it should or should not have), backchannel frequency and Jensen-Shannon divergence against human distributions, response latency, and GPT-judged relevance of the response after an interruption. Follow-up versions extend to overlapping speech and multi-turn. |
| Audio MultiChallenge (Scale AI, Dec 2025; ACL 2026) | Multi-turn natural spoken dialogue with end-to-end systems: inference memory, instruction retention, self coherence, and a new voice editing axis (mid-utterance repairs, background audio cues). 452 conversations from 47 speakers, 1,712 instance-specific rubrics. | Pass rate under a rubric-based LLM judge. Best at paper time: Gemini 3 Pro Preview (Thinking) at 54.65%; performance fell with longer audio context. |
| τ-Voice (Sierra / Princeton, Mar 2026; ICML 2026) | Full-duplex voice agents on τ-bench's grounded customer-service tasks: 278 tasks with realistic audio, including accents and noise. Integrated into the τ-bench (τ²/τ³) repo with realtime providers. | Task completion (pass@1) by database-state checks, clean and noisy. Text GPT-5 (reasoning) scored 85%; voice agents scored 31-51% clean and 26-38% with noise and accents (about 30-45% of text capability under realistic conditions). 79-90% of failures came from agent behavior. |
Spoken interaction is still much harder than text for the same underlying intelligence. Big Bench Audio shows a reasoning gap from speech input; τ-Voice shows grounded task success falling by roughly half or more in voice, and further with noise and accents. If your text agent passes 90% of your tasks, do not assume the voice version will. Measure it.
Also worth knowing by name: Artificial Analysis publishes speech-to-speech leaderboards (including Big Bench Audio results and latency measurements), and "Talking Turns" (Arora et al., ICLR 2025) trains a judge model specifically for turn-taking behavior. Other academic suites cover audio understanding and paralinguistics (for example SD-Eval for paralinguistic cues, and AIR-Bench and MMAU for general audio understanding); check their papers before relying on specifics.
Robustness: the channel is out to get you
A voice agent that works in a quiet office on a laptop microphone can fall apart on a real phone line. Robustness testing means deliberately degrading the audio in controlled, repeatable ways and watching which metrics move.
You can generate most of these conditions offline from clean recordings, which makes them repeatable (the same noisy clip every run):
import numpy as np
from scipy.signal import resample_poly
def add_noise(speech, noise, snr_db):
"""Mix noise into speech at a target signal-to-noise ratio (dB)."""
noise = np.resize(noise, speech.shape) # loop or trim noise to length
p_speech = np.mean(speech ** 2)
p_noise = np.mean(noise ** 2) + 1e-12
scale = np.sqrt(p_speech / (p_noise * 10 ** (snr_db / 10)))
return speech + scale * noise
def telephone_channel(x, sr=16000):
"""Narrowband phone line: 8 kHz sampling + G.711 mu-law companding."""
x8 = np.clip(resample_poly(x, 1, sr // 8000), -1, 1) # 16 kHz -> 8 kHz
mu = 255.0
enc = np.sign(x8) * np.log1p(mu * np.abs(x8)) / np.log1p(mu)
q = np.round((enc + 1) / 2 * 255) / 255 * 2 - 1 # 8-bit quantisation
dec = np.sign(q) * ((1 + mu) ** np.abs(q) - 1) / mu
return resample_poly(dec, sr // 8000, 1) # back to the model's rate
def packet_loss(x, sr=16000, frame_ms=20, loss_rate=0.05, seed=0):
"""Zero out random 20 ms frames, like lost RTP packets with no concealment."""
rng = np.random.default_rng(seed) # fixed seed = repeatable
n = int(sr * frame_ms / 1000)
y = x.copy()
for start in range(0, len(y), n):
if rng.random() < loss_rate:
y[start:start + n] = 0.0
return y
# degraded = packet_loss(telephone_channel(add_noise(clean, cafe, snr_db=10)), loss_rate=0.02)
Offline augmentation tests the ASR and endpointing. It does not test your real telephony stack (SIP trunk, carrier codecs, jitter buffers, echo cancellation). For that, place real calls through the actual phone path in staging, at least for a smoke-test subset. Accents and speaking styles deserve their own slices: report WER and task success per accent group, because an average can hide a group the system fails badly (a fairness problem as well as a quality one). Consent matters here too: if you use recordings of real people, you need their permission to use their voice in tests.
Voice-specific safety
Everything from Module 17 applies to voice agents, plus a set of risks that only exist because the agent hears and speaks.
Voice cloning and impersonation
An agent that can speak in any voice can be made to impersonate a real person. OpenAI's GPT-4o system card describes restricting output to preset voices plus a separate output classifier that detects deviation from the approved voices (reported precision 0.96, recall 1.0 on English). Test: prompts and audio that try to make the agent change or imitate a voice.
Caller impersonation
A voice agent that "recognizes" a customer by voice, or that accepts spoken identity claims, is a social-engineering target, and cloned voices make it worse. Test: calls where the simulated caller claims to be someone else, uses partial details, or pressures the agent to skip verification.
Jailbreaks through audio
Attacks can hide in how something is said, not just what. Best-of-N Jailbreaking (Hughes et al., 2024) showed that repeatedly sampling small augmentations of a harmful request also jailbreaks audio language models, not only text ones. Test: spoken versions of your text red-team set, with pitch, speed, noise and accent variations, and with instructions embedded in background audio.
Speaker identification and inference
Audio carries identity and sensitive traits. The GPT-4o system card reports training the model to refuse identifying people from their voice, and to refuse or hedge on inferring things like race or intelligence from how someone sounds. Test: "who is speaking?" and "what can you tell about this person from their voice?" prompts.
Harmful content in audio output
Moderation built for text may not see audio. A common mitigation (again described in the GPT-4o card) is running moderation on transcripts of both input and output audio. Test: the same disallowed-content set, spoken, and check the moderation layer triggers.
Compliance and disclosure
Many deployments must disclose that the caller is talking to an AI, get recording consent, or read regulated disclosures verbatim. These are easy binary checks and belong in every regression run.
Putting it together: a voice eval plan
Here is how the pieces fit into one plan for a new voice agent, following the eval-driven loop from Module 12:
- Collect real audio early. Even 50-100 recorded (consented) calls or role-played calls. Listen to them. Do error analysis on what goes wrong before choosing metrics.
- Component suite (runs on every change, minutes): WER and entity accuracy on a fixed set of your callers' audio, sliced by accent and noise; a TTS pronunciation set with round-trip checks and automated MOS; per-stage latency percentiles.
- Simulated-call suite (runs nightly or before release, hours): 50-500 scenarios × a persona matrix × several runs each. Grade with code (state, tool calls, timing), a timestamped-transcript judge, and a sampled audio or human review for tone.
- Robustness and safety slices: the degradation ladder and a spoken red-team set, with thresholds that block release.
- Production monitoring: containment, true resolution, escalation precision and recall, CSAT, latency percentiles and turn-taking rates on live calls, with judges sampling a share of calls. Every production failure you find becomes a new simulated scenario.
When a simulated call fails, tag the first failure with its layer: ASR misheard, endpoint too early, LLM reasoning, wrong tool call, TTS mispronounced, latency caused the caller to repeat themselves. Counting failures by layer tells you where to invest. In a cascade you can confirm the tag by replay: feed the human transcript into the LLM stage. If the call now succeeds, the ASR was the culprit. That "oracle transcript" replay is one of the most useful debugging tricks in voice evals, and it is only possible because a cascade has text boundaries.
Key takeaways
- A voice agent is a text agent plus four new jobs: hearing, timing, turn-taking and speaking. Each one needs its own metric.
- Cascades (VAD, ASR, LLM, TTS) are easy to diagnose because every boundary is text or a timestamp; speech-to-speech models must be graded end to end.
- WER counts every word equally. Always add entity accuracy, state your normalizer, and use oracle-transcript replay to prove whether the ASR caused a failure.
- Measure latency from the caller's side (last word to first audible reply) and report p50/p90/p95/p99, never just the mean.
- Turn-taking is a confusion matrix: missed interruptions versus false interruptions. Tuning one moves errors into the other, so report both.
- Simulated callers with a persona matrix and a degraded channel are the workhorse of voice evals, but the simulator is part of the instrument, so check its fidelity.
- Containment is not resolution. Real benchmarks (τ-Voice) show voice agents keep only about a third of text capability in realistic audio, so measure your own voice gap.
Check yourself
1. Reference: "cancel my order please" (4 words). Hypothesis: "cancel the order please now". What is the WER?
2. Your cascade's server logs show a p50 of 700 ms from VAD end to first TTS byte, but users say the agent feels slow. What is the most likely missing piece?
3. You make the VAD more sensitive so the agent stops faster when interrupted. Which metric should you watch for getting worse?
4. Why is containment rate on its own a dangerous success metric?
5. You grade calls by transcribing them and sending plain text to an LLM judge. Which failure can this setup NOT detect?
6. According to τ-Voice (2026), how do voice agents compare with a strong text agent on the same grounded tasks?
Interview questions (with model answers)
Q: Cascaded or speech-to-speech, which is easier to evaluate, and why?
Cascaded. Every stage boundary is text or a timestamp, so you can compute WER, replay oracle transcripts into the LLM, and time each stage. A speech-to-speech model can only be graded end to end on audio in, audio out and tool calls. Many teams add a parallel ASR purely for logging.
Q: How would you measure barge-in quality?
From stereo recordings with an offline VAD: for each caller onset during agent speech, classify it (real turn vs backchannel or noise, using labels or a judge), then report yield rate and yield time for real turns, false-interruption rate for the rest, and whether the agent's next turn addressed the interruption.
Q: Your WER improved from 9% to 6% but task success dropped. How is that possible?
WER weights all words equally. The new ASR may be better on common words but worse on entities (names, numbers, account IDs) that decide the task. Or normalization changed. Check entity accuracy and per-slice results, and run the oracle-transcript replay to confirm whether ASR is the cause.
Q: How do you stop the user simulator from biasing your results?
Calibrate personas against real calls, vary voices, accents and behaviors, read a sample of simulated calls, track simulator failures (off-script, early hang-ups) separately from agent failures, and run several trials per scenario.
Sources and further reading
- Artificial Analysis, Big Bench Audio dataset and speech-to-speech leaderboard.
- Chen et al., VoiceBench: Benchmarking LLM-Based Voice Assistants (2024); leaderboard.
- Lin et al., Full-Duplex-Bench (2025).
- Scale AI, Audio MultiChallenge (2025); leaderboard.
- Ray, Dhandhania, Barres, Narasimhan, τ-Voice (2026); τ-bench leaderboard.
- Saeki et al., UTMOS: UTokyo-SaruLab System for VoiceMOS Challenge 2022; code; SpeechMOS (torch.hub).
- jiwer (WER, CER, alignment visualization) and its usage docs.
- OpenAI, Realtime API: voice activity detection (server_vad, semantic_vad) and server events.
- Google, Gemini Live API overview.
- Pipecat, Smart Turn (open semantic end-of-turn model).
- OpenAI, GPT-4o system card (voice-specific risks and mitigations).
- Hughes et al., Best-of-N Jailbreaking (2024), including audio language models.
- Stivers et al., Universals and cultural variation in turn-taking in conversation, PNAS (2009).
- Vendor guides (vendor claims, not standards): Hamming, voice agent evaluation metrics and interruption handling runbook; ElevenLabs, 6 pillars of voice agent evaluation; Cekura, voice AI evaluation metrics; Coval.