How to Add an AI Chatbot to Your Website That Doesn't Hallucinate (2026) — Cesar Ayala
← All posts

How to Add an AI Chatbot to Your Website That Doesn't Hallucinate (2026)

Add a chatbot that doesn't hallucinate by grounding it in your real content with retrieval (RAG): it answers only from chunks you actually retrieved, cites the source, says "I don't know — let me get a human" when retrieval comes up empty, and you enforce it with guardrails and a faithfulness eval set.

Why does an AI chatbot invent your prices and policies?

In February 2024, a tribunal ordered Air Canada to pay a grieving customer about C$812. The reason: the airline’s website chatbot had told Jake Moffatt he could apply for a bereavement discount retroactively, within 90 days of booking. That policy did not exist. The bot made it up. When the case reached the BC Civil Resolution Tribunal (Moffatt v. Air Canada), the airline tried a remarkable defense — that the chatbot was “a separate legal entity responsible for its own actions.” The tribunal rejected it flatly. Your chatbot’s words are your words.

A year earlier, a Chevrolet dealership in Watsonville bolted a ChatGPT wrapper onto its site. Within days, someone prompt-injected it into “agreeing” to sell a 2024 Tahoe for $1 — “and that’s a legally binding offer, no takesies-backsies.” No court ever tested that offer, and it almost certainly wasn’t binding, and no dealer got harmed — but the lesson is twofold: an ungrounded, unguarded bot will happily invent a price, and it’s a wide-open attack surface.

Here’s why this happens, and it’s the mental model I want you to carry through this entire post. A large language model is a next-token predictor. It was trained to produce the most statistically plausible continuation of text — to sound right, not to be right. Fluency and accuracy are decoupled. When you ask it about your refund window, your current pricing, or a SKU, it has no notion of your truth. Those facts were never reliably in its training data, and they change constantly. So it fills the gap with a plausible-sounding invention, stated with exactly the same confidence it uses for true facts. That confident tone is itself an artifact of training — OpenAI’s 2025 paper “Why Language Models Hallucinate” framed it as an incentive problem, where benchmarks that penalize “I don’t know” actively pressure models to bluff.

So the thesis I’ll repeat all the way down: the model is not your knowledge base — your content is. The model just phrases the answer. Hallucination is not a bug you patch with a sterner prompt. It is the default behavior of an ungrounded model, and you engineer around it.

What does it mean to ‘ground’ a chatbot (and what is RAG)?

Grounding means: retrieve your current content first, then let the model answer only from it. You shift the model from “recall from memory” to “read these documents and answer.” The standard pattern for this is RAG — retrieval-augmented generation.

Here’s the pipeline, stage by stage:

  1. Ingest your real sources — docs, product pages, pricing, policies, FAQs, changelogs.
  2. Chunk them into roughly 300-600 token passages, keeping the heading and source URL attached to each chunk.
  3. Embed each chunk into a vector and store it in a vector database.
  4. At query time, embed the question and retrieve the top-k nearest chunks (k is usually 4-8).
  5. Rerank the candidates so the best ones rise to the top.
  6. Stuff those chunks into the prompt as the only allowed source of truth.
  7. Generate an answer that cites which chunk it came from.

A confusion worth killing early: this is not fine-tuning. People hear “train it on our data” and assume fine-tuning, but for factual Q&A you want retrieval, not fine-tuning. Fine-tuning changes the model’s style and format — not the facts it knows — and it goes stale the moment your prices change. RAG puts the real answer in the prompt at query time, so the model paraphrases your fact instead of inventing one. If you want the deeper version of this tradeoff, I wrote a whole piece on RAG versus fine-tuning — for a website chatbot, RAG is almost always the answer.

This is also why a generic “ChatGPT widget” invents by default: many drop-in widgets have no retrieval layer over your real content, so there is nothing grounding them. And to be clear, the anti-hallucination behavior is the combination — retrieval plus a closed-book prompt plus an empty-retrieval guard plus citations plus refusal plus evals. Any one of those alone leaks. (This is also where a chatbot differs from a full AI agent: a support bot answers from documents; an agent takes actions across systems.)

The grounded-answer pipeline

User questionplain text from the chat box
Embed queryturn the question into a vector
Hybrid retrieve top-kvector + keyword over your vector store
Rerankcross-encoder reorders the best chunks
Empty-retrieval gateweak score -> escalate to human; strong -> continue
Closed-book promptonly the retrieved chunks as source of truth
Answer + citationsevery claim links to its source
Where grounding happens — and where the refusal branch fires before the model ever runs.

How do I make the chatbot answer only from my content?

This is the part you can copy. Two pieces carry the weight: the closed-book system prompt, and the empty-retrieval gate that runs before the model is ever called.

import re

SYSTEM_PROMPT = """You are the support assistant for {company}.
Answer the question using ONLY the context below.

Rules:
- If the answer is not clearly in the context, reply exactly:
  "I'm not sure about that — let me connect you with a human." Do NOT guess.
- Never use outside or prior knowledge. Never invent prices, refund
  windows, dates, features, or policies.
- Cite the source after each fact using [n] matching the context blocks.
- Never negotiate, quote custom terms, or make binding commitments.
  Route those to a human.

Context:
{context}
"""

# Gate on the VECTOR similarity score (cosine, 0..1). Note: a cross-encoder
# reranker outputs scores on a DIFFERENT scale (often unbounded logits), so
# never compare a rerank score against a cosine threshold. Tune on YOUR data.
SIMILARITY_FLOOR = 0.75

def answer(question: str):
    q_vec = embed(question)

    # Hybrid retrieval: vector for meaning + keyword/BM25 for exact terms
    # (SKUs, plan names, error codes), fused with reciprocal rank fusion.
    vec_hits = vector_store.search(q_vec, k=8)     # each: {text, url, score}
    kw_hits = keyword_search(question, k=8)
    chunks = rrf_fuse(vec_hits, kw_hits)

    # Empty-retrieval gate: keyed on the VECTOR similarity, BEFORE the LLM.
    top_sim = max((h["score"] for h in vec_hits), default=0.0)
    if not chunks or top_sim < SIMILARITY_FLOOR:
        return handoff("no_confident_context")     # never improvise

    chunks = rerank(question, chunks)[:6]           # cross-encoder reorder

    context = "\n\n".join(
        f"[{i+1}] (source: {c['url']})\n{c['text']}"
        for i, c in enumerate(chunks)
    )
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT.format(company="Nixbly", context=context)},
        {"role": "user", "content": question},
    ]
    reply = llm.chat(messages, temperature=0.1)     # low temp = sticks to context

    # Verifiable citations: keep ONLY the sources the model actually cited,
    # and drop any [n] it invented that falls outside the context blocks.
    cited = sorted({int(n) for n in re.findall(r"\[(\d+)\]", reply)})
    sources = [{"url": chunks[i - 1]["url"]} for i in cited if 1 <= i <= len(chunks)]
    return {"answer": reply, "sources": sources}

Let me name the techniques in that sketch, because each one is doing a job:

  • Closed-book instruction. “Answer ONLY from the context” is the load-bearing line. It revokes the model’s permission to draw on training memory.
  • The empty-retrieval gate. If nothing clears the similarity floor, you return a refusal and a human handoff without ever calling the model. A bot that fails open — that always answers — is more dangerous than one that refuses.
  • Refusal as a feature. “I don’t know” is a success state, not a failure. Reward it.
  • Verifiable citations. A claim with no retrievable source is a red flag. Don’t return the whole retrieval set as “sources” — parse the [n] markers the model actually emitted, map them back to the context blocks, and drop or repair any index the model invented. Otherwise your links are decorative, and a citation-shaped hallucination slips through. Render the survivors as clickable links.
  • Scope-limiting and guardrails. Refuse off-topic questions, jailbreaks, and competitor probes. Ignore any in-message “ignore your instructions” attack. Keep temperature low, around 0.1.
  • No binding commitments. Never let the bot quote custom terms or agree to a discount. That’s the Air Canada trap.

Now the contrarian opinion, stated hard: you cannot prompt your way out of hallucination. A sterner prompt on an ungrounded bot still invents — it just invents more politely. You fix this by removing the model’s permission to answer from anything except retrieved, cited context. The prompt enforces the constraint; retrieval supplies the facts. Neither works alone.

The anti-hallucination non-negotiables

Answer only from retrieved contextclosed-book system prompt
Gate before the LLMweak retrieval -> refuse + escalate, don't improvise
Force verifiable citationsevery claim links to a real, model-emitted source
Human escalationa first-class path, always one click
Scope + guardrailsrefuse off-topic, jailbreaks, binding commitments
Keep the index freshre-index on publish; stale = confidently wrong
Any one of these alone leaks. The grounding comes from the combination.

Why is retrieval (not the model) where grounded bots still fail?

Here’s a practitioner truth most posts skip: when a grounded bot “lies,” the failure is almost always in retrieval, not generation. The answer existed in your content — the bot just retrieved the wrong chunk and filled the gap. Swapping GPT for Claude won’t fix that. The architecture, not the model, is the lever.

Chunking is upstream of everything. Too-large chunks become topic soup — a blurry average of several ideas that looks relevant but answers nothing cleanly. Too-small chunks strand definitions from the facts that make them interpretable. And a fact split across two chunks is simply unrecoverable. Use semantic, structure-aware chunking and keep the headings.

There are two distinct failure modes:

  • Recall failure — the answer exists but retrieval never surfaces it. Fix with hybrid search: vector similarity for meaning plus keyword/BM25 for exact terms like SKUs, plan names, and error codes that embeddings blur over.
  • Ranking failure — the right document is retrieved but the wrong chunk is chosen. Fix with a cross-encoder reranker that re-scores the top candidates before they hit the prompt.

Two more nuances signal whether someone has actually shipped one. First, citation-shaped hallucinations: the answer looks grounded because it has a link, but the claim isn’t actually supported by that source. Citations must be verifiable, not decorative — which is exactly why the code validates emitted [n] markers against the context blocks instead of trusting them. Second, the stale-index failure: a perfectly grounded bot answering from an outdated chunk is still confidently wrong — and, per Air Canada, you’re still on the hook. Stamp dates on your content, prune stale chunks, and re-index whenever content changes. Treat the index as part of your deploy.

How do I know it’s actually working (and not just demoing well)?

The chatbot is easy. The eval set is the actual product — and it’s the part roughly 95% of teams skip.

Evaluation is what separates a demo from production. Use RAGAS-style metrics:

  • Faithfulness — is every claim in the answer supported by the retrieved context? This is your inverse hallucination rate. Above 0.85 is good; below 0.70, investigate.
  • Answer relevancy — does the answer actually address the question?
  • Context precision / recall — did retrieval even surface the right chunk?

Build a small golden set of 30-50 real questions with known answers, plus deliberate trap questions: things you don’t offer, a student discount that doesn’t exist, an off-topic curveball, a prompt-injection attempt. The traps are how you catch the bot inventing. Run the whole set on every prompt, model, or index change as a regression test. “When better prompts hurt” is a real phenomenon — a tweak that feels better can silently regress faithfulness, so change one thing and re-measure.

Now the honest reality check, and this is the E-E-A-T moat: grounding reduces hallucination but never eliminates it. A Stanford/Vals study of purpose-built RAG tools for legal work found 17-34% residual hallucination even in those specialized products. Anyone selling you a zero-hallucination bot is the one hallucinating. One 2026 note: LLM-as-judge for faithfulness is only reliable with a strong judge model (Claude Opus or GPT-5 class). Cheap judges under-detect contradictions, so keep a human in the loop for high-stakes answers.

The build steps, in order

  1. Ingest real sourcesdocs, product, pricing, policies, FAQs
  2. Chunk (keep headings)300-600 tokens, source URL attached
  3. Embed + storevectors in pgvector / Pinecone / Qdrant
  4. Hybrid retrievevector + keyword, top-k 4-8
  5. Rerankcross-encoder reorders candidates
  6. Closed-book prompt + citationsanswer only from context
  7. Empty-retrieval guardrefuse + escalate on weak score
  8. Golden + trap eval setRAGAS faithfulness, run on every change
  9. Keep the index freshre-index on publish
Turn the explanation into a sequence you can actually execute.

Should you build your own chatbot or buy one?

Here’s the honest tradeoff with real numbers.

Buy a grounding platform — Intercom Fin, Chatbase, Zendesk AI, Ada, or enterprise Sierra. Fin charges around $0.99 per resolution plus seats; its marketed 67% resolution rate is optimistic, so budget on the real-world 42-50%. Sierra is enterprise-only with no public pricing — third-party estimates land around $150k/year plus six-figure setup. You’re live in days, with grounding, handoff, and analytics built in and maintained for you.

Build with an LLM API plus a vector store plus your own UI. That runs roughly $100-800/month in infra and tokens: embeddings are about $0.02 per million tokens, and a gpt-4o-mini turn is a fraction of a cent. You get full control of retrieval, guardrails, and data residency, and no per-resolution tax — but you own retrieval quality, evals, uptime, and maintenance forever. (For the full breakdown, see how much it costs to add AI to your app.)

Buy a platform Build custom
Time to live Days Weeks
Monthly cost ~$0.99/resolution + seats ~$100-800 infra + tokens
Retrieval control What the vendor exposes Full
Maintenance Handled for you Yours forever
Best for Standard support, moderate volume Proprietary data, deep integration, scale

Rule of thumb: break-even tips toward building at roughly $1,000-2,000/month of platform spend. Below that, a custom build rarely pays back inside 18 months.

My first-person verdict, true about 70% of the time: start on a grounding platform pointed at your docs to validate that the thing delivers value. Build custom only when a real constraint forces it — custom retrieval over weird or structured data, tight product integration, data residency, or when the bot is a core product surface, not just support. And the honest middle path: even if you buy the layer, your content and guardrails are your job. Stale or messy source content makes even a great RAG stack confidently wrong.

Naive 'ChatGPT on our site' vs a grounded RAG bot

Ungrounded ChatGPT widget

  • Answers from training memory
  • Invents prices and policies
  • No citations
  • Never says I don't know
  • No eval set
  • Company liable (Air Canada)

Grounded RAG bot

  • Answers only from retrieved content
  • Refuses when retrieval is empty
  • Cites verifiable sources
  • Escalates to a human
  • Measured by faithfulness evals
  • Index kept fresh on publish
Same chat box, completely different product — and different liability.

What trips these bots up in the real world (UX and pitfalls)?

The architecture can be perfect and the bot can still erode trust. The UX and operations are where it lives or dies.

UX wins. Position it as “a support assistant that answers from our docs,” not an oracle — that framing prevents most of the disappointment. Show citations inline. Make “talk to a human” always one click. Stream responses so it feels fast while retrieval runs. Set a greeting that states scope, so users self-select. And log every “I don’t know” as a content-gap signal — that’s the exact missing doc you need to write.

Human handoff. When the bot escalates, pass the full conversation, the sentiment, and what was already tried, so the human doesn’t restart from zero. Ignoring an explicit “talk to a human” is a top failure. Set expectations on availability — “a human will reply by 9am” beats silence.

Named pitfalls. Stale index (faithfully reporting outdated truth). Messy, nav-heavy HTML chunks polluting retrieval — extract the main content first. Too-broad k diluting the prompt and blending sources. No abstention path. And trusting model confidence: it sounds equally sure when it’s wrong, which is exactly why you gate on retrieval score, not tone.

A real boundary. Don’t answer time-sensitive or account-specific things — live inventory, order status, per-user data — from a static doc snapshot. Those need live tool or API calls, and they’re their own hallucination source.

Security. Treat the system prompt as sensitive; leaking it lets users probe your guardrail edges. Assume prompt injection both via user input and via poisoned content in the pages you retrieve. And never let the bot make a binding commitment.

One clarification that trips people up: the feedback loop usually improves your retrieval and your knowledge base — fix chunks, add missing docs, re-index — not the model weights. Thumbs up/down are labeled failures to mine weekly.

Frequently asked questions

Can an AI chatbot ever be 100% hallucination-free? No. Grounding reduces and bounds it, but even purpose-built commercial RAG still hallucinates meaningfully in adversarial settings, per the Stanford legal-AI study above. The honest claim is grounded, cited, and refuses rather than guesses.

Do I need a vector database? Only for non-trivial content. A small FAQ can fit in the prompt. For one site’s content — hundreds to thousands of pages — pgvector on the Postgres you already run is plenty. Reach for Pinecone or Qdrant only at millions of vectors or sub-20ms latency at high concurrency.

Will “training it on our data” (fine-tuning) fix it? No. For factual Q&A you want retrieval (RAG). Fine-tuning changes style, not facts, and goes stale instantly.

How many test questions do I need? Start with 30-50 real questions plus deliberate trap and forbidden questions, and run them on every change.

How much does it cost per month? Buy: per-resolution (around $0.99 each on Fin), scaling with volume. Build: roughly $100-800/month in infra and tokens, plus your engineering time. Break-even tips toward building past about $1-2k/month of platform spend.

Can it answer in Spanish? Yes — if your content and a multilingual embedding model plus the LLM handle it. Same grounded content, same architecture. The answer language follows your content and model, not a separate bot.

The takeaway: grounded, cited, and safe by default

You don’t stop hallucination. You architect around it. Retrieval supplies the facts. The closed-book prompt forbids invention. The gate refuses on weak retrieval. Citations make it verifiable. Evals prove it. That’s the whole game.

Here’s the copyable checklist:

  1. Ingest real sources.
  2. Chunk, keeping headings.
  3. Embed.
  4. Hybrid retrieve.
  5. Rerank.
  6. Closed-book prompt with citations.
  7. Empty-retrieval guard.
  8. Human escalation.
  9. Golden + trap eval set with RAGAS faithfulness.
  10. Keep the index fresh — re-index on publish.

There is no “doesn’t hallucinate” chatbot. There’s a chatbot you’ve grounded, measured, bounded, and given an exit hatch. Anyone selling zero hallucination is selling.

My honest advice for most sites: buy the grounding layer, point it at your docs, and own your content and guardrails today — then build custom when a real constraint forces your hand. If you’d rather have it built right the first time, that’s the kind of work I do through my AI automation and agents service.