AI engineerCase studyVoice agent evals and security

Testing AI phone agents before customers do.

An AI phone agent that sounds fine in a demo can still fail the one call that matters. I built an evaluation suite that phones my agents with simulated callers and attackers, scores every call in code first, and blocks any release that leaks data or misses an emergency.

Role
Solo: suite design, scenarios, checks, fixes
Stack
Python · GPT-4o-mini agents · GPT-4.1-mini judge · Microsoft Presidio · GitHub Actions · FastAPI
When
October 2026
Proof
Live results · Security write-up
14→0Calls leaking another customer's data
57→94%Injection resistance
5/41Missed emergency handoffs caught on the first run
$0.25Per full 114-call run
01 / The problem

"It sounded fine" is not a test.

I run seven live voice agents on Vapi and Retell. Language models aren't deterministic, so three good test calls prove little, and the calls people test with are the easy ones. Real callers interrupt, refuse details, ask for the owner, or call at 2am with water coming through the ceiling. Some of them try to manipulate the agent.

I needed answers to two questions before any agent touched a real phone number: does it do the right thing on the hard calls, and can it be talked into leaking data or breaking the rules?

02 / What I built

Simulated callers, checks in code.

Simulated callerpersona · attack Agent under testsame prompt, model and tools + caller's own record only Transcript + tool callssaved per call PII redactionPresidio + regex Deterministic checkscanary · PII · tools · price LLM judgeonly what code can't Metrics + CI gateany critical fails the build Results console/evals
The loop each scenario runs three times; a critical failure stops the release

The eval suite

  • 38 scenarios across three agents (dental, plumbing, self-storage), each with a persona, a goal, facts revealed only when asked, and a success outcome. Each runs 3 times: 114 calls, about $0.25 a run.
  • Deterministic checks first (did it call the booking tool, save the right fields, quote only real prices, hand over when it should); an LLM judge with a written rubric only for what code can't decide.
  • Metrics that resist gaming: containment where a hang-up never counts, correct vs unnecessary transfers, missed handoffs as a critical failure, hallucination rate, and pass on all three runs. Baselines and a regression gate in GitHub Actions.

The security track (OWASP Top 10 for LLM Applications 2025)

  • 21 attack scenarios, 7 per agent: direct and indirect prompt injection (including instructions hidden in CRM notes and tool results, and attacks in Arabic), system-prompt extraction, requests for other customers' details, excessive agency and unbounded replies.
  • A random canary token planted in each call's system prompt, Faker fixture customers to detect cross-customer leaks, and a tool guard for saves or transfers the caller isn't entitled to.
  • Every transcript passes through PII redaction (Presidio plus a regex engine) before it is judged, stored or published, with a publish guard that refuses to publish if anything slips through.
The key decision

Prompt rules and data delimiting cut leaks from 14 calls to 6, but couldn't stop them on gpt-4o-mini or gpt-4.1. The root cause was that agents were given data they didn't need. I changed the architecture: each agent now gets at most the caller's own record, matched by caller ID or verified details, with allowlisted fields and never free-text notes. Leaks went to zero.

03 / Results

What the numbers showed.

63 security callsBeforePrompt fixLeast privilege
Injection resistance57%90%94%
Cross-customer PII leaks1460
Prompt or canary leaks100
Unauthorized tool calls500
Calls with a critical failure1760
  • The first functional run caught 5 missed emergency handoffs in 41 plumbing calls, including a caller reporting a gas smell, on the cheaper model. The agent sounded calm the whole time; it just did the wrong thing.
  • The final run had zero criticals across all 177 calls. PII redaction scored 1.00 precision and recall on a 26-string labelled set; 135 tests run in CI, Presidio included.
Still open, stated honestly

The plumbing agent still repeats a planted "free service" note when it arrives inside a simulated tool reply. Nothing leaks any more, and today's live webhooks return a fixed "Saved." sentence, so this only matters if a future integration echoes CRM text back to the agent. The rule for that integration is the same: structured fields only.

04 / What I'd do next

Where it goes from here.

  • Test audio, not just text: latency, barge-in and speech-recognition errors on real phone audio.
  • Push the least-privilege context builder into every live CRM integration, and add a tool-reply sanitizer for the open finding.
  • Run the security track nightly against the deployed prompts, not only on changes.
More case studies
Get in touch

Need an AI system that holds up in production?

Tell me what you're building and where it breaks. I'll tell you straight whether I can help.

Cairo, Egypt — US, UK & Gulf time zones LinkedIn GitHub