10voice · telephony
A dispatcher that answers at midnight and refuses what it cannot serve
ridgeway-hvac-voice-agent · Python · Vapi, GPT-4.1, Deepgram nova-3, FastAPI, AWS Lambda
A homeowner whose air conditioning has died calls an HVAC company after hours. The agent takes
the area code from caller ID, checks coverage against the company's API, offers the real
repair windows by their human names rather than by slot id, books the one the caller picks,
and reads the confirmation number back digit by digit. It is live on a real phone
number, answering right now.
The discipline is in what it will not do. Outside the service area it says so, offers nothing and
never calls the booking endpoint. It is forbidden from inventing a confirmation number:
if the booking fails it says the booking failed and offers to retry. Both tools POST straight to the
API's own paths with no adapter in between, so what the service receives is what the agent decided,
and 46 tests pin every row of that contract down to the 401, the 400s and the 409.
The lesson it cost: given a hang-up tool and told to use it, GPT-4.1 hung up in the same turn as a
tool result, before speaking. The tool came out and the line now ends on a word the
telephony layer listens for, so the goodbye is said before the call drops.
- vapi
- gpt-4.1
- deepgram nova-3
- barge-in
- fastapi
- aws lambda
- 46 tests
- transcripts vs logs
one call, end to end
inbound call +1 971 338 7356 ──▶ caller id ──▶ area code
│
▼
the dispatcher gpt-4.1 · deepgram nova-3 · cut off after 2 words
│
├─▶ lookup_avail_area_codes_serviced
│ 503 ──▶ Portland metro, two windows
│ 206 ──▶ not serviced. nothing offered
│
└─▶ book_appointment ──▶ 201 ──▶ AC-87116 read back digit by digit
mock two endpoints on Lambda, the packet's own shapes, no adapter
guards 401 without the key · 400 on a bad area code · 409 on a dead slot
proof each transcript sits beside the mock's log for the same seconds
46tests on the contract
2endpoints, no adapter
0.2sto barge in on it