Dev.to AI 🤖 Ai 👁 0 📖 7 min read

Hours: Not Publicly Listed. Telling a Voice Agent What It Does Not Know

In code, a missing value is loud. You read a key that is not there and you get undefined, a null pointer, a stack trace. Something stops. In a prompt, a missing value is silent. A caller asks a dental receptionist agent

In code, a missing value is loud. You read a key that is not there and you get undefined, a null pointer, a stack trace. Something stops.

In a prompt, a missing value is silent. A caller asks a dental receptionist agent "do you take Aetna?", the answer is not in the prompt, not in a tool result, and the model does not stop. It produces the most likely sounding next sentence, and "yes, we accept Aetna" sounds very likely. On a phone line there is no source link next to it and no dead air allowed, so the caller hears one confident sentence and believes it.

I build voice agents for US clients on Retell, n8n and GoHighLevel, and almost every hallucination I have chased in production fell into four buckets: invented prices, invented availability, invented policies, and confident answers outside the business (a symptom, a legal question). This post is how I handle each, using the real prompts from nine client agent exports I keep on disk, including two places where those prompts were the problem.

Null is a value you have to write down

Here is my favourite line in any of my prompts. It sits in the Context section of an HVAC agent:

- Business hours: Not publicly listed - never quote specific hours. Just offer to schedule.

The water treatment agent has the same rule. Compare that with the dental and roofing agents, whose Context sections list real hours, day by day.

Both shapes are fine. What is not fine is the third shape, where hours are simply absent. An absent field is not null to a language model, it is an invitation. "We're open nine to five" is the most probable answer for a local business, so that is what you get. If the business genuinely does not publish its hours, the prompt has to say so explicitly, the same way a schema distinguishes "field missing" from "field present and null".

So my first pass on any new agent is boring: list the questions callers actually ask (hours, prices, insurance, financing, areas served, who the staff are) and make sure every one has either a value or a written-down "we do not say this, here is what we do instead".

A denylist only works with an instead

Every agent has a short list of things it must never state on its own, and the list is different per vertical, because the dangerous facts are different:

  • The two law firm agents: never give legal advice; the injury firm adds never quote settlement values or contingency percentages.
  • Credit repair and debt settlement: never give specific advice or guarantees, only collect information and schedule.
  • HVAC: never guarantee exact pricing without a diagnostic visit, never diagnose complex failures over the phone.
  • Water treatment: never diagnose water quality with certainty, never promise exact pricing.
  • Roofing: do not guarantee insurance claim approval.

A bare "NEVER" is half a rule, though. If the only options are "answer" or "awkward silence", the model will answer. The rule that works carries its own replacement line. The sales agent for CallSetter does this in one block:

### Pricing Lock
NEVER mention specific prices. Always say: "Pricing is customized based on your volume and will be covered on the demo."

The HVAC, dental and roofing agents each have a scripted price answer built around the same phrase, "I wouldn't want to guess", followed by the real next step: a diagnostic visit, a free consultation, a free estimate. That turns the most common hallucination trigger, a price shopper, into a booking path instead of a number.

The honest limit: a denylist covers what you anticipated. It is the security model you already know is weak. It stops the obvious cases and does nothing for the question nobody thought of, which is why the next two sections matter more.

Live facts come from a tool, as data

Anything that changes (open slots, today's schedule, a job status) should never be a sentence in the prompt the model can paraphrase or extrapolate from. My agents get availability from tool calls (get_slots_from_ghl or checkCalendar, depending on the client) at the moment the caller asks, and the prompt says "always give exactly two specific times", so the agent reads back what the tool returned instead of summarising a calendar.

Two rules make this hold:

  1. Return facts, not prose. {"available": false, "next_slot": "Thursday 10:00"} is much harder to embellish than a paragraph about the week.
  2. Handle the empty case in the prompt. Most invented availability I have debugged happened when a tool returned nothing or failed and the model filled the gap. I wrote about the failure path, including the two-minute timeouts I found on every tool, in the outages post.

How to shape the tool parameters themselves is in Every Tool Parameter Is a Question Your Voice Agent Has to Ask.

Sometimes the model is right and the prompt is wrong

While going back through the exports for this post I counted where the dental agent's business hours appear. The string 8:30 shows up nine times in that one prompt: the Context section, a rules section, the "not ready to book" line, and several of the worked examples. The roofing agent's hours appear ten times.

That is a config bug waiting to happen. When the practice changes its Friday hours, someone edits the Context section, misses one of the example dialogues, and now the agent has two answers for the same question. When it gives the old one, it will look exactly like a hallucination in the transcript. It is not. The model is faithfully reading a stale copy.

Then I found a better one. The dental agent's cost answer:

- Cost question: "The consultation and exam pricing depends on what the doctor recommends. The inspection is free if you'd like to start with that."

Dental practices do not do inspections. Roofers do. Several of these agents were cloned from a shared base (I covered the HVAC agent listening for "Invisalign" in an earlier post), and this line leaked across from roofing. The same sentence is in the updated dental prompt I keep in a separate file, so it survived a revision. I have not re-exported the live agent to check whether it is still there, and I am not going to claim a caller heard it. But if one did, the transcript would read like the model making up a free inspection, and I would have gone looking for a model problem.

The lesson I took: before you blame the model, grep the prompt. Treat it like config. One fact, one place, and examples that reference the fact instead of restating it. That is also the argument for keeping facts out of fine-tuning, which I made in the fine-tuning post.

Narrow the room to improvise

All nine agents run on gpt-4.1. Only one of the nine exports set model_temperature explicitly (to 0), and the fixed HVAC export added a second. The rest were left at the platform default. A receptionist does not need creative phrasing, so this is now part of my setup checklist rather than something I remember to do.

Structure helps too. For fixed intake flows, a conversation flow gives the model fewer chances to wander than one long prompt. All nine of mine are single prompts of roughly 2,400 to 4,400 words, and that post covers where that stops working.

Test off the happy path, then keep reading transcripts

Happy-path testing (book an appointment, ask the hours) finds almost no hallucinations, because they live off the path. Before go-live I run calls designed to provoke one:

  1. Ask for a price on a service not in the prompt
  2. Ask for a slot that is already booked
  3. Ask about an insurance provider or payment plan the business does not offer
  4. Ask a medical, legal or safety question
  5. Ask for a staff member who does not exist
  6. Interrupt mid-answer and change the question
  7. Say something ambiguous like "is it open?" with no context

Every one should end in a grounded answer or the approved fallback. Anything else goes back into the prompt. Why these have to be scripted rather than just "try it" is in There Is No Repro for a Phone Call.

After launch, real callers are more creative than any script. What catches the rest is post-call analysis fields that flag calls where the agent mentioned a price, a policy or a date, plus a weekly read of the flagged calls and a random sample. Fifteen minutes per client per week finds most problems before the client does. And since clients will hear about a bad answer from their own customers eventually, they need to be able to open the transcript themselves. That is a big part of why we built VoiceDash, the white-label portal where each client sees their own calls and transcripts.

The short version

You will not get a language model to zero hallucinations. You can make them rare and boring:

  • Write down what the agent does not know, as explicitly as what it does.
  • Pair every "never" with the line to say instead.
  • Pull every live fact from a tool, as data, with the empty case handled.
  • Keep each fact in one place in the prompt, and grep before you blame the model.
  • Set the temperature on purpose.
  • Test off the happy path, then read transcripts every week.

The fix I keep relearning is the fourth one. Both hallucination-shaped bugs I found in my own exports were in the prompt, not the model.

What is the strangest thing you have found when you went looking for a model bug and the bug was in your own prompt?

This is adapted from a longer guide on the VoiceDash blog: How to Stop a Voice AI Agent From Hallucinating.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.