Evaluating Voice Agent Quality

10 min read Module 7 of 9 Topic 19 of 25

What you'll learn

  • Explain why WER alone cannot certify a voice agent as production-ready
  • Distinguish turn-level metrics from conversation-level metrics and know which stage each one localizes
  • Build a golden eval set from real call transcripts rather than synthetic scripts
  • Design an LLM-as-judge rubric that scores conversation quality without hand-waving
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Every earlier module in this course built a stage of the pipeline: ASR, the dialogue core, TTS, telephony. This lesson is where you find out whether the stages, assembled, actually work for a real caller. A voice agent that looks flawless on a benchmark transcript can still fail the person on the phone, and the gap between those two pictures is exactly what a proper evaluation framework exists to close.

WER is a component metric, not a verdict

Word error rate tells you how faithfully Deepgram Nova-3 turned audio into text. It says nothing about whether the LLM core understood the resulting text, called the right tool, or produced a response the caller could act on. Treat WER the way Module 7’s earlier lessons treated recall in a RAG system: necessary, insufficient, and dangerous if it’s the only number on your dashboard.

# eval_metrics.py - component metrics, kept separate on purpose
def score_turn(turn: dict) -> dict:
    return {
        "wer": word_error_rate(turn["asr_transcript"], turn["reference_transcript"]),
        "intent_correct": turn["predicted_intent"] == turn["gold_intent"],
        "tool_call_correct": turn["tool_call"] == turn["gold_tool_call"],
        "ttfa_ms": turn["time_to_first_audio_ms"],
    }

A 4% WER call can still fail if the LLM core, running on claude-sonnet-5 or GPT-5, misreads a correctly transcribed sentence, skips a required tool call, or the TTS output mispronounces an account number. Each of those is a different failure with a different fix, and a blended score hides which one happened.

Latency percentiles, not averages

Callers judge a system by its worst moments. Report p50, p95, and p99 time-to-first-audio (TTFA) separately, and alert on the tail, not the mean.

flowchart TB
    T1["Caller finishes speaking"] --> T2["ASR endpoint detection\n~150ms (Nova-3 streaming)"]
    T2 --> T3["LLM first token\np50 300ms / p95 900ms / p99 2100ms"]
    T3 --> T4["TTS first audio chunk\np50 120ms / p95 280ms"]
    T4 --> T5["Caller hears response\nTTFA p50 ~570ms / p95 ~1.3s / p99 ~2.6s"]

    style T1 fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style T2 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style T3 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style T4 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style T5 fill:#fff7ed,stroke:#f59e0b,color:#0F172A

A p99 of 2.6 seconds means roughly 1 in 100 turns feels like a stall to the caller, and stalls are exactly what get repeated in a complaint transcript, even if the p50 dashboard looks pristine.

Task success rate and containment rate

These are the numbers that actually correlate with whether the agent is doing its job. Task success rate asks: did the caller’s stated goal get accomplished (a claim was filed, a balance was checked, a reservation was changed)? Containment rate asks: did the call finish without escalating to a human agent? Neither can be inferred from ASR or latency metrics alone; both require labeling the outcome of the whole conversation, not any single turn.

def containment_rate(calls: list[dict]) -> float:
    resolved = sum(1 for c in calls if not c["escalated_to_human"])
    return resolved / len(calls)

Track these split by intent category. A blended 70% containment rate can hide a category (say, claims disputes) sitting at 30%, exactly the kind of stage-level signal a single top-line number erases.

Turn-level vs. conversation-level evaluation

Turn-level eval scores one exchange in isolation: was the intent classified correctly, was the tool call right, was the response accurate. Conversation-level eval scores the whole call: did the caller’s actual goal get met, was the tone appropriate throughout, did the agent recover gracefully from an early misunderstanding. A voice agent can pass every turn-level check and still fail conversation-level eval if it never resolves accumulated confusion from turn 3 by turn 8. Run both, and treat conversation-level scores as the one that gates launch, per the launch-gate pattern used in the capstone.

Building a golden set from real transcripts

Synthetic, scripted test calls are a starting point, not a golden set. Real callers interrupt, trail off, restate themselves, and have background noise. Sample your golden set from anonymized production or pilot-phase call transcripts, stratified across intent category, outcome (resolved vs. escalated), and audio conditions. Aim for 150-300 real conversations before you trust the eval numbers it produces.

Managed platforms often ship a coarser version of this signal automatically. NextNeural attaches a sentiment field (positive, neutral, or negative) to every call record, delivered via the call.ended webhook covered in Lesson 21, with no eval infrastructure required. It’s a useful first-pass filter for which calls are worth pulling into your golden set, not a substitute for the dimension-level rubric below, since “sentiment: negative” tells you a call went badly without telling you whether it was a tool-call error, a tone problem, or an unresolved intent.

LLM-as-judge scoring

Use a specific rubric, not a vague “rate this call” prompt. Score each dimension independently with claude-sonnet-5 as the judge:

JUDGE_RUBRIC = """
Score this call transcript on each dimension, 1-5, with a one-sentence justification:
1. task_completion: was the caller's stated goal accomplished?
2. tone_appropriateness: professional, empathetic where needed, no robotic repetition?
3. tool_use_correctness: were the right tools called with the right arguments?
4. recovery: if the agent misunderstood early, did it correct course by the end?
Return JSON only, one score object per dimension.
"""

Independent, checkable dimensions is what makes judge scores discriminate between a genuinely good call and a mediocre one that merely sounded confident.

Knowledge Check

4 questions to test your understanding

1 A team reports their voice agent has 4% WER on a benchmark set and ships it. Two weeks later, containment rate (calls resolved without human handoff) is only 41%, far below the 70% target. What does the WER number fail to capture?

2 Your dashboard shows p50 time-to-first-audio of 380ms, which looks healthy. Callers are still complaining the agent feels sluggish. What should you check first?

3 A team builds their golden eval set entirely from scripted test calls written by the QA team, following the 'happy path' through each intent. What is the main risk of this approach?

4 An LLM-as-judge is scoring conversation quality on a 1-5 scale with the single instruction 'rate how good this conversation was.' Scores cluster tightly around 4 for both genuinely excellent and mediocre calls. What is the fix?

Discussion

Questions and notes from learners on this topic

Loading discussion…

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan