Dhruv Patel

← Work

Ovela

A voice receptionist on a real phone line. Replies that need no tool start in 0.55–0.71 s, and it's built around the parts of a call that usually go wrong.

measured · 6 days, 12–51 turns each

Personal project · no customers; every call is my own test call · Python, FastAPI, asyncio, Twilio, Deepgram Flux, gpt-4.1-nano, Cartesia, Appwrite, Next.js · 1,922 tests

How it was earned

A "yes" isn't agreement to everything

The booking tool trusted a flag the model filled in to say the summary had been read back. In 4 of 7 attempts it claimed a confirmation the caller never gave; in one, a "yes" to an email spelling booked a room. The gate now reads the transcript: a name, price and dates spoken by the agent, followed by the caller agreeing to that.

measured · 7 attempts

A prompt rule is not a boundary

With the guest's name in the prompt behind a "do not reveal" rule, the agent volunteered it on the first turn in 4 of 5 replays. Now the lookup tool withholds the name, and code releases it only after identity is confirmed: 0 of 5. Four actions are gated in code the same way; tone stays in the prompt.

measured · 5 replays

My own instrument was lying

The trace reader read one 100-row page and labelled it with the whole period I'd asked for. It reported a 3× slowdown that never happened, and it had also produced a "27% faster" claim, which I withdrew. Fixed and split by turn type, the honest picture is this: replies with no tool were already inside budget, and replies with a tool were not. The real win came from a per-call cache: a repeat booking lookup went from 1,073 ms to 0.4 ms.

withdrawn → replaced by the per-turn-type figures · measured · 15 → 21 lookups

Re-measuring, even when the answer was convenient

Models are benchmarked under the production prompt (~8,900 tokens, 12 tools), never on a bare "hello". Re-run in September, the two candidates were equal on speed, so the earlier finding that separated them no longer held. gpt-4.1-nano stays on cost, and any switch would now need a scored behaviour evaluation, not a latency number.

What did the caller actually hear?

Text is generated faster than it's spoken. When a caller cuts in, the model has "said" words the caller never heard. Ovela keeps only the part that played, estimated from the audio Twilio confirms it delivered. And "mhmm" isn't an interruption.

measured · interruptions caught up to 6.5 s into an answer · 6 interruptions

The slowest reply was the first one

  1. SAWThe first reply of each call was the slowest, well over a second behind the rest.3.7 sfirst reply
  2. SAWTwo causes: a cold first model call, and a lookup that could only say “ask who is calling”.12 / 15replays wasted a lookup
  3. CHANGEDThe first request is sent once while the greeting plays. The agent asks who’s calling before looking anything up.0 / 15wasted lookups
  4. HOLDSFirst reply of a call.0.9 sone call before, one after
  5. NOT YETReplies that need a tool still wait on the tool’s round trip.1.1–1.7 stwo tool turns

Silence while a tool runs

When the model reached for a tool, the caller heard nothing for 2.3–4.3 s. Now code says a short line first ("Let me have a look."), and the caller hears a first word in 0.7–0.8 s. The answer itself still waits on the tool; the silence is gone.

measured · 4 tool turns before, 2 after

What stands

  • No-tool replies in 0.55–0.71 s.
  • Identity: 24 of 28 real guests found from misheard details, 0 of 8 non-guests invented, 0 wrong guest.
  • Four actions gated in code.
  • 1,922 tests.

What it still doesn't do

  • Tool replies take 1.1–1.7 s. The remaining latency is the tool round trip, not the model.
  • Identity is confirmed on one matching word of the name, so a relative sharing the surname passes.
  • Gates judge agreement from word patterns, so an unusual "yes" can be refused.
  • One tenant, no real customers.

Sources