Fixes from a test call:
- Contradiction: AVA said "staff will confirm" then later "we've got your
appointment scheduled". Hardened the rule — never say booked/scheduled/set/
confirmed (even in the recap); it's always a REQUEST staff confirm on callback.
Wrap-up recaps as "I've noted your request...".
- Phone: it asked "may I read your number back?" then read it anyway. Now states
it directly in one line ("I have your number as <number> - is that best?"),
no permission ask, don't skip.
- Insurance: stop saying "we accept/take <plan>" (it said "we accept All State",
which isn't even a listed plan) — just note it, staff verify.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A caller trailed off ("My insurance plan is...") and AVA filled in "CarePlus",
which got logged to the lead. Tightened the insurance rule: ask open-endedly,
do NOT read out/suggest plan names from the accepted list, capture only what the
caller says, never fill in/complete/guess the plan, and ask them to repeat if
unclear. Verified 4/4 on the trail-off case (asks to repeat, no fabricated plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace loose "gather these details" with a directed script so the call has
clear direction:
1. Reason first — what are they calling about
2. Location — city/area, confirm the matching office
3. Caller info — full name, then address them by name; insurance (log only),
preferred day/time
4. Verify phone near the end by reading it back
5. Wrap up — recap, then "Is there anything else I can help you with?"
Closing hardened: "Goodbye" (which ends the call) is gated behind the
anything-else question, never said in the same turn as confirming details.
Be warm but direct; one short turn at a time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Phone: inject the caller-ID into the prompt already spelled digit-by-digit so
the model repeats clean words instead of mangling raw digits (it had emitted
"197-three five seven three..." -> Kokoro read "one hundred ninety-seven").
- Flow: stop leading with the phone number. Prompt now flows naturally and
saves the callback-number confirmation for the END; the caller-ID line says
not to recite it early. Verified 3/3 openings no longer recite the number.
- Name: Kokoro spelled all-caps "AVA" as "A-V-A". Respell to AGENT_NAME_SPOKEN
(default "Ava") in TTS only; logs/Odoo keep AGENT_NAME. Override e.g.
AGENT_NAME_SPOKEN=Eva for an "EE-vuh" sound.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a caller named a city matching an office ("I'm in Kendall"), AVA confirmed
Kendall then asked them to pick between unrelated offices ("Hollywood or
Miami?"), going off script. Tightened the prompt: on a city that matches an
office, confirm THAT office and move on; never offer/compare other offices or
ask the caller to choose; name the nearest only if nothing matches. Verified
3/3 on the failing scenario.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
run_tts is called as run_tts(self, text, context_id); the override only accepted
(self, text), so every utterance raised "takes 2 positional arguments but 3
were given" and produced no audio — callers heard nothing on every call since
the number-normalization change. Added context_id and pass it through. Verified
the service now emits audio (118KB for a sample) with digits normalized.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kokoro spoke "983-4969" as "nine hundred eighty-three dash forty-nine sixty-
nine". Added SpokenKokoroTTSService which normalizes text just before synthesis
(run_tts gets the full sentence): US phone patterns and 4-5 digit runs (street
numbers, zips) are spoken one digit at a time, country code dropped, no "dash"/
parens. Dates and times are left natural. Deterministic, so it's robust to
whatever the model emits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real call derailed: AVA argued about today's date, parroted the canned date
example, hallucinated appointment availability, and rambled. Root cause was the
date-validation feature — the local 8B model computes appointment dates wrong
~5/5 in testing, so having it state/correct dates is a liability.
- DATES: capture & defer — AVA takes the day/time in the caller's own words,
never computes/states/corrects the calendar date, never argues about today;
staff confirm the exact date on callback. Removed the 45-day calendar
injection and _date_context()/datetime use.
- Hardened the no-availability rule (no "openings", no "check availability",
no "I'll book").
- Brevity: one short sentence per reply (two at most).
Post-call extractor still records a best-effort resolved date (staff-verified).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In-call (system prompt + per-call calendar injection):
- Gather full name (prompt asks for last name if only first given).
- Confirm the caller-ID number; if declined, use the number the caller gives.
- Ask for and LOG insurance only — never promise/confirm/deny coverage or
treatment based on it; staff verify on callback.
- Validate the requested date against an injected 45-day calendar (recomputed
per call since the server is long-running). Push back on impossible/mismatched
dates, e.g. "Monday lands on the sixth — would you like that date?".
- AGENT_NAME=AVA; 4s grace pause before hang-up (HANGUP_DELAY_SECS).
Logging (post-call extraction -> Odoo):
- Extract full name, phone_confirmed, chosen callback (caller-ID or alternate),
insurance, reason, and preferred time annotated with a resolved YYYY-MM-DD
date (today's date is fed to the extractor).
- odoo_client: insurance row on the lead note (log only — staff verify).
.gitignore: ignore rotated avc_run.log* files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deepgram and the Twilio Standard API Key were reverted per decision:
- bot.py: restore HintedWhisperSTTService (faster-whisper hotwords), default
model medium; remove DeepgramSTTService import + DEEPGRAM_API_KEY.
- server.py: restore TWILIO_AUTH_TOKEN for X-Twilio-Signature validation and
the serializer auto-hang-up. Twilio signs webhooks with the Auth Token, so
an API Key Secret cannot validate signatures.
- .env.example: back to TWILIO_AUTH_TOKEN + Whisper STT vars.
- .gitignore: ignore runtime *.log (avc_run.log).
OLLAMA_MODEL stays activeblue-avc:latest (the existing pulled tag).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>