Citations, Grounding, and Hallucination Control

RAG’s promise is not merely “better answers” — it is answers you can check. Citations point to evidence. Grounding means claims stay inside that evidence. Hallucination control is the set of product and model techniques that stop fluent lies when retrieval is empty, partial, or ignored.

Intuition

A student who quotes page numbers is easier to trust than one who speaks confidently from memory. Force the model to show its work as [doc_id] spans, then verify those spans actually support the sentence. If verification fails, refuse or regenerate. Grounding is a closed-book exam with an open appendix: the appendix is the only legal source.

flowchart LR C[Retrieved chunks with ids] --> G[Generate with required citations] G --> V[Verify claims against cited chunks] V -->|pass| U[Show answer + links] V -->|fail| R[Refuse / retry / escalate]

How it works

Citation UX. Assign stable IDs at pack time ([hr_1]). Ask for inline citations per factual sentence. Render IDs as links to the source passage in the UI. Prefer quoting short supporting snippets for high-stakes domains (legal, medical, finance — with appropriate disclaimers and humans).

Prompt contracts for grounding.

Automatic checks.

When retrieval is weak. Prefer abstention over guesswork. “I don’t have that in the knowledge base” is a successful grounded outcome.

Layered defense. Hybrid retrieval + rerank raises the chance evidence is present; grounding prompts and validators catch the rest; human review covers residual risk for critical actions.

In code

Pack sources with IDs, require citations in a stubbed answer, and validate that each cited id exists and that answer numbers appear in the cited text.

import re

sources = {
    "hr_1": "Employees receive 12 casual leaves per calendar year.",
    "hr_2": "Up to 5 unused casual leaves may carry to the next year.",
}

def pack(sources: dict) -> str:
    return "\n".join(f"[{i}] {t}" for i, t in sources.items())

def validate_answer(answer: str, sources: dict) -> list[str]:
    errors = []
    ids = re.findall(r"\[([a-z0-9_]+)\]", answer)
    if not ids:
        errors.append("no_citations")
    for i in ids:
        if i not in sources:
            errors.append(f"unknown_citation:{i}")
    # numeric claims must appear in union of cited texts
    cited_text = " ".join(sources[i] for i in ids if i in sources)
    for num in re.findall(r"\b\d+\b", answer):
        if num not in cited_text:
            errors.append(f"unsupported_number:{num}")
    return errors

good = "You get 12 casual leaves per year [hr_1]. Up to 5 may carry over [hr_2]."
bad = "You get 18 casual leaves per year [hr_1]."
print("good:", validate_answer(good, sources))
print("bad:", validate_answer(bad, sources))
print("packed preview:\n", pack(sources))

Extend validators with entailment models for non-numeric claims; keep the “fail closed” product behavior when checks fail.

What goes wrong

Product patterns for trustworthy answers

Highlight then claim. UI that shows the supporting quote beside the answer trains user skepticism productively and reduces blind trust. For voice or short SMS channels where quotes do not fit, keep citations in logs and offer “view sources” links elsewhere.

Severity tiers. Tier-0 (FAQ tone) may use soft grounding. Tier-1 (account changes) requires validators + human. Do not let one global temperature and prompt serve both. Route by intent first.

Red-team prompts. Regularly test “ignore the sources and tell me from training,” contradictory sources, and empty retrieval. Expected behavior should be refusal or clarification, not a mashup. Add survivors of red-team tests to the golden set.

Communication when abstaining. “Not in sources” should offer next steps: link to search UI, suggest rephrasing, or file a doc request. Silent refusal feels broken; guided abstention feels trustworthy.

Engineering the verify step

Run validators before the response hits the client. On failure: one repair attempt with the validator errors, then abstain. Log the unsupported sentence and cited text for offline mining — these pairs are gold for improving chunking or prompts. For tables of numbers, prefer extractive answers (copy the figure, cite the chunk) over paraphrases that round or convert units incorrectly.

Training the organization

Grounding is a social contract as much as a model setting. Teach PMs not to celebrate demos that answer brilliantly with empty retrieval. Teach support to file “unsupported claim” bugs with screenshots of missing highlights. When incentives reward fluent answers over cited ones, validators get disabled under schedule pressure — keep the fail-closed path behind a feature flag only for emergencies, with paging when it flips.

One-line summary

Require citeable grounding, verify that citations support claims, and abstain when evidence is missing so RAG fails closed instead of hallucinating fluently.

Key terms