Frontdesk Agent

How a message is handled: deterministic rules, a classifier, validation by code, retrieval and claim checks, or a handoff to a person

Overview

A multilingual messaging receptionist for a clinic and a shop: one Python engine with Instagram and WhatsApp adapters that answers from approved information, books appointments and hands over to staff, without ever inventing a price, a free time or medical advice.

Stack

Python, Pydantic AI, Gemini on Vertex AI (EU), Postgres with pgvector, Cloud Tasks, cal.com, Google Calendar, Instagram and WhatsApp APIs, Hypothesis

What I built

  • Safety rules that run before any model call: emergencies, requests for medical advice and personal data get fixed, approved answers.
  • Facts written by code: prices, hours, doctors and order status come from approved records, and every number, time and link in a reply must trace back to a fact, a tool result or the conversation.
  • Document answers carry proof: each claim is a sentence, a passage ID and a word-for-word quote, and code checks the exact match, the numbers, prices, negation and semantic closeness.
  • Hybrid retrieval, full-text and pgvector fused with Reciprocal Rank Fusion, over versioned knowledge that moves from draft to approved to active and can roll back.
  • A Postgres inbox for both channels: each message stored once, one turn per conversation under a lease, and each reply recorded before sending and sent at most once.
  • A model gateway with routes approved per data class, a daily spend cap, a duplicate request when a call stalls and a fallback to a second model.
  • Bookings through cal.com and Google Calendar. On WhatsApp the staff approve a booking request with a single tap.
  • 4,155 tests at 97% branch coverage, property tests included, with import rules, offline evals and a container check in CI.

Key decisions

  • Code owns the facts. A model that writes and is checked afterwards can still quote the wrong price, so the model leads the conversation and code supplies every number.
  • A Postgres inbox instead of Temporal: Temporal Cloud would cost about $100 a month against about 0.014 EUR per message, and the channels need deadlines, not timers.
  • Choices made by measurement, with the bar set in advance: a hosted embedder had to beat the local one by 10 points of recall@3, gained only 2.8, and the local model stayed.
  • Live evals on demo businesses: 55 of 56 clinic cases and 23 of 23 shop cases passed, with no missed emergency and no invented fact getting through, at a p95 latency of 1.6 to 2.2 seconds.
  • Code and client configuration are kept apart: one deployment per business, and all data in the repository is synthetic.

What's next

Next: deploying the WhatsApp and Instagram channels, then a limited FAQ release and a gradual rollout.

Source is private