Voice Agent Configuration
Vapi.ai Squad Architecture — v2.3.0 (Production) & v3.0 (Deployed)
Version History
| Version |
Status |
Architecture |
Assistants |
Languages |
Squad ID |
| v2.3.0 |
Released |
6-agent multilingual squad |
6 |
Auto-detect (EN/ZH) |
775db28c-... |
| v3.0 |
Deployed (phone-assigned, testing) |
9-agent dual-track EN/ZH |
9 |
Explicit language gate |
13fdfd19-... |
v3.0 — Dual-Track Bilingual Squad
Overview
| Setting |
Value |
| Architecture |
9-Agent Dual-Track (Router + 4 roles x 2 languages) |
| Squad ID |
13fdfd19-a2cd-4ca4-8e14-ad2275095e32 |
| LLM |
OpenAI GPT-4o (all 9 assistants) |
| Temperature |
0.3 (Router), 0.5 (all others) |
| Max Tokens |
150 (Router), 200 (standard), 250 (Registration) |
| Handoff Mode |
Silent (no announcement) |
| Config Management |
Vapi GitOps (config-as-code) |
| GitOps Path |
vitara-platform/vapi-gitops/ |
| Webhook |
https://api-dev.vitaravox.ca/api/vapi/* |
Key Design Changes from v2.3.0
| Change |
v2.3.0 |
v3.0 |
Rationale |
| Language handling |
Single multilingual agent |
Explicit Router language gate |
Eliminates LLM language confusion; dedicated STT/TTS per track |
| Patient identification |
Combined in Router |
Separate Patient-ID agent per track |
Cleaner prompt; Router stays lightweight |
| Reschedule + Cancel |
Two separate agents |
Single Modification agent per track |
Reduces squad complexity; both share same tools |
| Confirmation agent |
Exists (rarely used) |
Eliminated |
log_call_metadata absorbed into Booking/Modification/Registration |
| STT (English) |
Deepgram nova-2 multi |
Deepgram nova-2 en |
Language-specific = higher accuracy |
| STT (Chinese) |
Deepgram nova-2 multi |
Deepgram nova-2 zh |
Language-specific = higher accuracy |
| STT (Router) |
Deepgram nova-2 multi |
AssemblyAI Universal |
Bilingual detection before routing |
| TTS (English) |
ElevenLabs multilingual_v2 |
ElevenLabs multilingual_v2 |
No change |
| TTS (Chinese) |
ElevenLabs multilingual_v2 |
Azure zh-CN-XiaoxiaoNeural |
Native Mandarin voice quality |
| ZH Endpointing |
Same as EN |
Longer pauses (1.0s wait, 0.6s punct) |
Mandarin speech patterns need longer pauses |
v3.0 Squad Members
| # |
Agent |
Vapi Name |
ID |
Role |
Tools |
| 1 |
Router |
vitara-router-v3 |
4f70e214-... |
Language gate: detect EN/ZH, route to correct track |
get_clinic_info, transfer_call, log_call_metadata |
| 2 |
Patient-ID (EN) |
vitara-patient-id-en-v3 |
7d054785-... |
Identify caller by phone, detect intent, route |
search_patient_by_phone, search_patient, get_clinic_info, transfer_call |
| 3 |
Patient-ID (ZH) |
vitara-patient-id-zh-v3 |
7585c092-... |
Same as EN but in Mandarin |
Same as EN |
| 4 |
Booking (EN) |
vitara-booking-en-v3 |
ac25775b-... |
Find slots, book appointments |
find_earliest_appointment, check_appointments, create_appointment, get_providers, log_call_metadata |
| 5 |
Booking (ZH) |
vitara-booking-zh-v3 |
6ef04a40-... |
Same as EN but in Mandarin |
Same as EN |
| 6 |
Modification (EN) |
vitara-modification-en-v3 |
9cd8381d-... |
Reschedule, cancel, check appointments |
check_appointments, find_earliest_appointment, update_appointment, cancel_appointment, create_appointment, get_providers, log_call_metadata, transfer_call |
| 7 |
Modification (ZH) |
vitara-modification-zh-v3 |
e348cd2f-... |
Same as EN but in Mandarin |
Same as EN |
| 8 |
Registration (EN) |
vitara-registration-en-v3 |
9fcfd00d-... |
Register new patients |
register_new_patient, add_to_waitlist, log_call_metadata |
| 9 |
Registration (ZH) |
vitara-registration-zh-v3 |
ce50df43-... |
Same as EN but in Mandarin |
Same as EN |
v3.0 Call Flow
CALL START (Telnyx inbound)
|
v
+--------------------------------------------------+
| 1. ROUTER (vitara-router-v3) |
| STT: AssemblyAI Universal (bilingual) |
| TTS: ElevenLabs multilingual_v2 |
| |
| - System greeting plays: "Hi, this is Vitara |
| Clinic. How can I help you?" |
| - Wait for caller to speak |
| - Detect language from first utterance |
| - Route immediately to language track |
+---------------------+----------------------------+
|
+-----------+-----------+
v v
ENGLISH TRACK CHINESE TRACK
| |
v v
+-------------------+ +-------------------+
| 2. PATIENT-ID-EN | | 2. PATIENT-ID-ZH |
| STT: Deepgram | | STT: Deepgram |
| nova-2 en | | nova-2 zh |
| TTS: ElevenLabs | | TTS: Azure |
| | | XiaoxiaoNeural |
| - search by phone | | - search by phone |
| - detect intent | | - detect intent |
| - route to role | | - route to role |
+--------+----------+ +--------+----------+
| |
+------+------+ +------+------+
v v v v v v
+----+ +----+ +-----+ +----+ +----+ +-----+
|BOOK| |MOD | |REG | |BOOK| |MOD | |REG |
| EN | | EN | | EN | | ZH | | ZH | | ZH |
+----+ +----+ +-----+ +----+ +----+ +-----+
v3.0 Handoff Matrix (20 Routes)
| From |
To |
Trigger |
| Router |
Patient-ID-EN |
Caller speaks English |
| Router |
Patient-ID-ZH |
Caller speaks Chinese |
| Patient-ID-EN |
Booking-EN |
Patient found + intent = book |
| Patient-ID-EN |
Modification-EN |
Patient found + intent = reschedule/cancel/check |
| Patient-ID-EN |
Registration-EN |
Patient not found + new patient |
| Patient-ID-ZH |
Booking-ZH |
Patient found + intent = book |
| Patient-ID-ZH |
Modification-ZH |
Patient found + intent = reschedule/cancel/check |
| Patient-ID-ZH |
Registration-ZH |
Patient not found + new patient |
| Booking-EN |
Modification-EN |
Patient says "reschedule" or "cancel" |
| Booking-EN |
Router |
Unrelated request |
| Booking-ZH |
Modification-ZH |
Patient says "改时间" or "取消" |
| Booking-ZH |
Router |
Unrelated request |
| Modification-EN |
Booking-EN |
After cancel, patient wants to rebook |
| Modification-EN |
Router |
Unrelated request |
| Modification-ZH |
Booking-ZH |
After cancel, patient wants to rebook |
| Modification-ZH |
Router |
Unrelated request |
| Registration-EN |
Booking-EN |
After registration, patient wants first appointment |
| Registration-ZH |
Booking-ZH |
After registration, patient wants first appointment |
| Any EN agent |
transfer_call |
Error fallback / frustrated caller |
| Any ZH agent |
transfer_call |
Error fallback / frustrated caller |
v3.0 Voice & Transcription Configuration
English Track
| Setting |
Value |
| STT Provider |
Deepgram |
| STT Model |
nova-2 |
| STT Language |
en |
| TTS Provider |
ElevenLabs |
| TTS Voice ID |
fQj4gJSexpu8RDE2Ii5m |
| TTS Model |
eleven_multilingual_v2 |
| Stability |
0.5 |
| Similarity Boost |
0.7 |
Endpointing (EN standard agents):
| Setting |
Value |
| Wait Seconds |
0.6 |
| On Punctuation |
0.3s |
| On No Punctuation |
0.8s |
| On Number |
0.5s |
| Stop on Words |
2 |
Endpointing (EN registration — longer pauses for spelling):
| Setting |
Value |
| Wait Seconds |
1.6 |
| On Punctuation |
1.0s |
| On No Punctuation |
2.5s |
| On Number |
1.2s |
| Stop on Words |
2 |
Chinese Track
| Setting |
Value |
| STT Provider |
Deepgram |
| STT Model |
nova-2 |
| STT Language |
zh |
| TTS Provider |
Azure |
| TTS Voice ID |
zh-CN-XiaoxiaoNeural |
Endpointing (ZH standard agents — tuned for Mandarin speech patterns):
| Setting |
Value |
| Wait Seconds |
1.0 |
| On Punctuation |
0.6s |
| On No Punctuation |
1.5s |
| On Number |
0.8s |
| Stop on Words |
3 |
Endpointing (ZH registration — longer pauses for spelling):
| Setting |
Value |
| Wait Seconds |
1.6 |
| On Punctuation |
1.0s |
| On No Punctuation |
2.5s |
| On Number |
1.2s |
| Stop on Words |
2 |
Router
| Setting |
Value |
| STT Provider |
AssemblyAI |
| STT Mode |
Universal (bilingual auto-detect) |
| TTS Provider |
ElevenLabs |
| TTS Voice ID |
fQj4gJSexpu8RDE2Ii5m |
| TTS Model |
eleven_multilingual_v2 |
v3.0 Agent Behaviors
Router (Language Gate)
- On call start: System greeting plays ("Hi, this is Vitara Clinic. How can I help you?")
- Does NOT: Look up patients, ask for details, perform booking
- ONLY JOB: Detect language from first utterance, route to correct track
- English detected: "One moment please." -> Patient-ID-EN
- Chinese detected: "请稍等。" -> Patient-ID-ZH
- Clinic info shortcut: If caller asks for hours/address, call
get_clinic_info and answer directly
- Emergency: Detects keywords in both EN and ZH, responds in detected language, ends call
- Fallback: After 3 unclear attempts, transfer to staff
Patient-ID (EN/ZH)
- On handoff: Does NOT re-greet. Waits for caller to speak, analyzes intent
- Phone lookup: Calls
search_patient_by_phone with phone: "0000000000" (server substitutes real number)
- Found + intent known: "Hi [Name]!" then route immediately (no "how can I help")
- Found + intent unknown: "Hi [Name], how can I help you today?"
- Not found: "Are you a new patient?" -> Registration or manual search
- On behalf of: Supports "calling for my husband/child" flow
- Routes to: Booking, Modification, Registration, or answers clinic info directly
Booking (EN/ZH)
- First turn: Immediately calls
find_earliest_appointment with NO filters
- Presents: One slot at a time ("How about [day] at [time] with Dr. [name]?")
- Asks reason: Maps to appointmentType (B=general, 2=follow-up, 3=complaint, P=prescription)
- Books: Calls
create_appointment with demographicId, providerId, startTime, appointmentType, reason, language
- Never says "booked" without calling the tool first
- Post-booking: Confirms details, calls
log_call_metadata, reminds about health card
- Wrong intent: Redirects to Modification or Router
Modification (EN/ZH)
- Consolidates: Reschedule + Cancel + Check into one agent
- First: Calls
check_appointments to find existing appointments
- Multiple found: Lists briefly, asks which one
- Reschedule: Calls
find_earliest_appointment then update_appointment
- Cancel: Confirms, calls
cancel_appointment, offers rebooking
- Check only: Reads appointment details, asks if changes needed
- Never says "moved/cancelled" without calling the tool first
- Post-action: Calls
log_call_metadata with outcome
Registration (EN/ZH)
- Collects 7 fields one at a time: name, gender, DOB, phone, address, health card, email
- Spelling: Uses NATO-style phonetic confirmation ("A as in Apple")
- Silent during spelling: Does NOT acknowledge individual letters
- Health card types: BC (10-digit PHN), OUT_OF_PROVINCE, PRIVATE
- Confirms all before calling
register_new_patient
- Post-registration: Offers first appointment booking (routes to Booking)
- Not accepting: Offers waitlist via
add_to_waitlist
| Tool |
Slug |
Vapi ID |
Used By |
search_patient_by_phone |
search-patient-by-phone-8474536c |
8474536c-... |
Patient-ID |
search_patient |
search-patient-4889f4e5 |
4889f4e5-... |
Patient-ID |
get_patient |
get-patient-d86dee47 |
d86dee47-... |
(reserved) |
get_clinic_info |
get-clinic-info-aaec50cf |
aaec50cf-... |
Router, Patient-ID |
get_providers |
get-providers-1ffa2c33 |
1ffa2c33-... |
Booking, Modification |
find_earliest_appointment |
find-earliest-appointment-7fc7534d |
7fc7534d-... |
Booking, Modification |
check_appointments |
check-appointments-74246333 |
74246333-... |
Booking, Modification |
create_appointment |
create-appointment-65213356 |
65213356-... |
Booking, Modification |
update_appointment |
update-appointment-635f59ef |
635f59ef-... |
Modification |
cancel_appointment |
cancel-appointment-f6cef2e7 |
f6cef2e7-... |
Modification |
register_new_patient |
register-new-patient-9a888e09 |
9a888e09-... |
Registration |
add_to_waitlist |
add-to-waitlist-0153bac0 |
0153bac0-... |
Registration |
log_call_metadata |
log-call-metadata-4619b3cb |
4619b3cb-... |
All (except Patient-ID) |
transfer_call |
transfer-call-d95ed81e |
d95ed81e-... |
Router, Patient-ID, Modification |
| Tool |
Router |
Patient-ID |
Booking |
Modification |
Registration |
| search_patient_by_phone |
|
EN/ZH |
|
|
|
| search_patient |
|
EN/ZH |
|
|
|
| get_clinic_info |
EN/ZH |
EN/ZH |
|
|
|
| get_providers |
|
|
EN/ZH |
EN/ZH |
|
| find_earliest_appointment |
|
|
EN/ZH |
EN/ZH |
|
| check_appointments |
|
|
EN/ZH |
EN/ZH |
|
| create_appointment |
|
|
EN/ZH |
EN/ZH |
|
| update_appointment |
|
|
|
EN/ZH |
|
| cancel_appointment |
|
|
|
EN/ZH |
|
| register_new_patient |
|
|
|
|
EN/ZH |
| add_to_waitlist |
|
|
|
|
EN/ZH |
| log_call_metadata |
EN/ZH |
|
EN/ZH |
EN/ZH |
EN/ZH |
| transfer_call |
EN/ZH |
EN/ZH |
|
EN/ZH |
|
v3.0 Global Behaviors (All Agents)
- Emergency detection: Both EN and ZH keywords trigger 911 message in caller's language
- Silent transfers: No agent ever mentions "transferring", "assistant", or system internals
- Date awareness: All prompts include
{{now | date: ...}} template for current date/time
- Past-date clamping: Server rejects past dates and clamps to today
- Phone auto-detection: Server extracts real caller phone from
call.customer.number
- One question at a time: Never overwhelm the caller
- Delay handling: "Just one moment..." / "请稍等..." if tool takes >3 seconds
- Never say technical terms: No "function", "tool", "API", "database"
v3.0 Appointment Type Mapping
| Patient Says (EN) |
Patient Says (ZH) |
Code |
Meaning |
| "checkup", "general", "physical", "not sure" |
"体检", "普通检查", "不确定" |
B |
General visit |
| "follow-up", "results", "check results" |
"复查", "看结果" |
2 |
Follow-up |
| "pain", "illness", "specific complaint" |
"不舒服", "疼痛", "生病" |
3 |
Specific concern |
| "prescription refill", "medication" |
"配药", "续药" |
P |
Prescription |
Server-side validation: appointmentType must be one of ['B', '2', '3', 'P']. Invalid values default to 'B'.
v3.0 Server-Side Middleware Changes
v3.0 introduced an in-memory callMetadataCache to bridge tool-call webhooks and end-of-call-report webhooks:
Tool call (log_call_metadata) End-of-call-report
| |
v v
setCallMetadata(callId, { getCallMetadata(callId)
language, outcome, -> merges into saveCallLog
demographicId, appointmentId })
- Why: Vapi's
end-of-call-report doesn't include tool results. The cache lets the server persist metadata that was set during the call.
- Fallback:
create_appointment and register_new_patient also cache language and demographicId as a safety net if log_call_metadata is never called.
- TTL: Cache entries auto-expire (in-memory Map, cleared on process restart).
Clinic Resolution Chain
Incoming webhooks resolve the clinic via this chain:
metadata.clinicId -> assistantId lookup -> squadId lookup -> phoneNumber lookup
v3.0 added vapi_squad_id_v3 column to clinic_config so both v2.3.0 and v3.0 squads resolve to the same clinic:
SELECT clinic_id FROM clinic_config
WHERE vapi_squad_id = $1 OR vapi_squad_id_v3 = $1
LIMIT 1
v2.3.0 — Production Squad (6 Agents)
Overview
| Setting |
Value |
| Architecture |
6-Agent Squad |
| Squad ID |
775db28c-21cf-4eec-a643-d078cf9bc5c1 |
| LLM |
OpenAI GPT-4o |
| Temperature |
0.5 |
| Languages |
English, Mandarin, Cantonese, French, Punjabi (auto-detect) |
| Handoff Mode |
Silent (no announcement) |
| Transcriber |
AssemblyAI Universal Multilingual |
| TTS |
ElevenLabs eleven_multilingual_v2, voice fQj4gJSexpu8RDE2Ii5m |
| Webhook |
https://api-dev.vitaravox.ca/api/vapi/* |
v2.3.0 Squad Members
| Agent |
Vapi Name |
ID |
Role |
Tools |
| Router |
vitara-router-v2 |
45f98810-... |
Greeting, phone auto-ID, intent routing |
search_patient_by_phone, get_clinic_info, transfer_call |
| Booking |
vitara-booking-v2 |
e5ad78c3-... |
Find slots, book appointments |
find_earliest_appointment, create_appointment, get_providers, transfer_call |
| Reschedule |
vitara-reschedule-v2 |
c1e6a6d8-... |
View/modify existing appointments |
check_appointments, find_earliest_appointment, cancel_appointment, create_appointment |
| Cancel |
vitara-cancel-v2 |
0d2f2b38-... |
Cancel appointments |
check_appointments, cancel_appointment, transfer_call |
| Registration |
vitara-registration-v2 |
f8e68875-... |
New patient registration |
get_clinic_info, register_new_patient, add_to_waitlist |
| Confirmation |
vitara-confirmation-v2 |
e1d5d83a-... |
Wrap-up (rarely used) |
log_call_metadata |
v2.3.0 Key Design Decisions
-
Phone auto-detection: Router calls search_patient_by_phone on call start. Server extracts real caller phone from call.customer.number, ignoring LLM hallucinations.
-
Booking-first flow: Booking assistant immediately searches for earliest available slot with NO filters. Preferences (doctor, date, time) applied only on request.
-
Single-slot returns: find_earliest_appointment returns exactly 1 slot per call. Rejected slots added to excludeDates for next search.
-
Inline confirmation: All assistants handle their own confirmation and goodbye. Confirmation agent exists but is rarely used.
-
Silent transfers: No assistant mentions "transferring" or other assistant names.
-
Single multilingual agent: All agents handle EN/ZH/FR/PA via auto-detection (one prompt per agent, not per language).
v2.3.0 Call Flow
CALL START
|
v
+-------------------------------+
| 1. ROUTER |
| - Greeting |
| - search_patient_by_phone |
| - Emergency/frustration detect |
+---------------+---------------+
|
+-----------+------+-------+
v v v v
+--------+ +------+ +------+ +------+
| BOOKING| |RESCHED| |CANCEL| |REGIST|
+--------+ +------+ +------+ +------+
|
v
+------------+
|CONFIRMATION|
| (fallback) |
+------------+
v2.3.0 Voice Configuration
| Setting |
Value |
| STT Provider |
AssemblyAI Universal Multilingual |
| TTS Provider |
ElevenLabs |
| TTS Voice ID |
fQj4gJSexpu8RDE2Ii5m |
| TTS Model |
eleven_multilingual_v2 |
| Silence Timeout |
30 seconds |
| Max Duration |
600 seconds (10 min) |
v2.3.0 Member Destinations Matrix
| From |
To |
Trigger |
| Router |
Booking |
Intent = book |
| Router |
Reschedule |
Intent = reschedule |
| Router |
Cancel |
Intent = cancel |
| Router |
Registration |
Patient not found |
| Registration |
Booking |
After reg, wants appointment |
| Booking |
Router |
Booking complete |
| Reschedule |
Booking |
After cancel, wants to rebook |
| Cancel |
Booking |
After cancel, wants to rebook |
Comparison: v2.3.0 vs v3.0
| Feature |
v2.3.0 |
v3.0 |
| Agents |
6 |
9 |
| Language approach |
Auto-detect in each agent |
Explicit Router language gate |
| Patient identification |
In Router agent |
Separate Patient-ID agent |
| Reschedule + Cancel |
2 separate agents |
1 Modification agent |
| Confirmation agent |
Exists (rarely used) |
Eliminated (absorbed) |
| STT |
AssemblyAI Universal (all) |
AssemblyAI (Router) + Deepgram per-language |
| TTS (Chinese) |
ElevenLabs multilingual |
Azure XiaoxiaoNeural |
| ZH endpointing |
Same as EN |
Tuned for Mandarin (longer pauses) |
| Config management |
Manual Vapi dashboard |
Vapi GitOps (config-as-code) |
| log_call_metadata |
On Confirmation agent |
On every role agent |
| Clinic info |
On Router |
On Router + Patient-ID |
| Handoff routes |
8 |
20 |
Testing
Test v3.0 Squad Flow
# 1. Open Vapi Dashboard -> Squads -> vitaravox-v3
# 2. Click "Test" to start a web call
# 3. Router greeting plays, say "I'd like to book"
# 4. Patient-ID-EN identifies you, routes to Booking-EN
# 5. Booking finds slot, you confirm
# 6. Verify in OSCAR EMR
# Chinese track:
# 3b. Say "我想预约" (I want to book)
# 4b. Patient-ID-ZH identifies, routes to Booking-ZH
# Test patient search
curl -X POST https://api-dev.vitaravox.ca/api/vapi/search-patient-by-phone \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $VAPI_SECRET" \
-d '{"phone": "+16045551234"}'
# Test clinic info
curl -X POST https://api-dev.vitaravox.ca/api/vapi/get-clinic-info \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $VAPI_SECRET" \
-d '{}'
Test Language Switching
1. Start in English: "I'd like to book an appointment"
2. Switch to Mandarin mid-call: "我想预约下周三"
3. In v2.3.0: Agent adapts seamlessly (auto-detect)
4. In v3.0: Router detects initial language, routes to correct track
(mid-call language switch stays in current track)