TL;DR
RAG hallucination mitigation is an evidence-control problem, not a prompt trick. A production system must preserve authorized source revisions, retrieve evidence that is sufficient rather than merely similar, expose conflicts, bind every material claim to supporting passages, and apply a calibrated policy that can release, narrow, abstain, or escalate. The model proposes an answer; deterministic application code decides whether that answer is safe to show.
Table of Contents
- What Counts as a RAG Hallucination?
- Diagnose the Failed Layer First
- Control 1: Govern the Evidence Corpus
- Control 2: Gate Retrieval on Sufficiency
- Control 3: Assemble Context as Untrusted Evidence
- Control 4: Verify Claims and Citations
- Control 5: Make Release and Abstention Deterministic
- Build a Minimal Evidence Gate
- Test the Complete Control Loop
- Frequently Asked Questions
Key Takeaways
- Retrieval relevance is not evidence sufficiency, and a high similarity score is not proof.
- A faithful answer can still be wrong when its source is stale or incorrect.
- Prompt constraints, low temperature, citations, and a second model are useful signals, not enforcement boundaries.
- Permissions must be applied before retrieval; filtering a leaked answer afterward is too late.
release,partial,abstain, andreviewshould be explicit policy outcomes with tested loss tradeoffs.
What Counts as a RAG Hallucination?
A RAG hallucination is a material answer claim that contradicts or cannot be inferred from the evidence supplied for that run. This operational definition focuses on answer faithfulness. It does not collapse every quality problem into one label.
Four properties must remain separate:
| Property | Question | Example failure |
|---|---|---|
| retrieval coverage | Did the required evidence reach the candidate set? | the refund exception was never retrieved |
| evidence quality | Is the source authoritative, current, and applicable? | an expired policy was retrieved |
| answer faithfulness | Does each claim follow from supplied evidence? | the answer adds an unsupported deadline |
| external correctness | Is the claim true against the real-world reference? | the source itself contains an error |
An answer can faithfully repeat an outdated document. It can also state a true fact from model memory that is unsupported by the authorized evidence snapshot. The first is a source-governance failure; the second violates a closed-evidence task even if the sentence happens to be correct.
For a broader detection contract, see LLM Hallucination Detection. This guide addresses the narrower implementation question: how should a RAG application prevent unsupported answers from reaching users?
Diagnose the Failed Layer First
The first mitigation step is to identify where the evidence chain broke. Changing the generator cannot recover a passage that retrieval never supplied, while increasing top_k does not repair a generator that ignores valid context.
Use a failure record that preserves the exact run:
{
"run_id": "rag-202",
"query": "Can a contractor expense international travel?",
"principal": "user-42",
"tenant": "acme",
"corpus_revision": "policy-index@sha256:7ad1",
"retriever_version": "hybrid-v4",
"reranker_version": "rerank-v3",
"generator_version": "answer-v8",
"policy_version": "evidence-policy-v5",
"evidence_ids": ["travel-2026#p18", "contractors-2026#p4"],
"decision": "review"
}
Without this snapshot, a later replay may use different documents, rankings, prompts, or policy and fail to reproduce the incident.
Control 1 Govern the Evidence Corpus
The first control is to turn each retrieved passage into a versioned evidence object. Plain text plus a filename is not enough to decide whether a source applies to the current user and question.
An evidence record should carry:
{
"evidence_id": "travel-2026#p18",
"document_id": "travel-policy",
"revision": "sha256:3c81",
"locator": {"page": 18, "section": "International travel"},
"authority": "finance-policy-owner",
"valid_from": "2026-01-01",
"valid_to": null,
"tenant": "acme",
"allowed_roles": ["employee", "contractor-manager"],
"status": "active",
"content_hash": "sha256:915f"
}
This contract supports controls that an embedding cannot provide:
- Authority: distinguish approved policy from drafts, comments, copied pages, and generated summaries.
- Freshness: retain effective dates and supersession relationships rather than sorting by upload time.
- Applicability: preserve jurisdiction, product, plan, tenant, role, and other scope qualifiers.
- Traceability: keep immutable revision and locator fields so a citation resolves to the same evidence later.
- Deletion: propagate revocation and deletion into every derived chunk and index.
Apply access control before retrieval and ranking. Retrieving another tenant's passage and suppressing it after generation still exposes the content to the model and to logs. The Grounding glossary explains why provenance, authorization, and validity are part of the evidence contract rather than optional metadata.
Control 2 Gate Retrieval on Sufficiency
Retrieval should answer two different questions: which passages are candidates, and are those candidates sufficient to answer this query? Similarity, a reranker score, or a non-empty result list answers neither question universally.
Build the retrieval stage in two passes:
- Recall candidates using the methods justified by the query distribution, such as lexical, dense, structured, graph, or API retrieval.
- Rerank and classify the selected evidence for relevance, coverage, applicability, and conflict.
Hybrid retrieval can improve exact-identifier and paraphrase coverage, but it must be compared against a labeled baseline. Anthropic's Contextual Retrieval results, for example, were measured on its selected corpora, embedding configurations, and Recall@20 setup; they are evidence that the method is worth testing, not a universal production gain.
The same rule applies to query rewriting. A rewrite is a search proposal, not evidence. Preserve the original query, version the rewriter, cap the number of variants, and test for intent drift. A generated expansion that invents a product name or policy assumption can retrieve a persuasive answer to the wrong question.
Define sufficiency by query requirements
For each evaluated query, label the evidence requirements:
query_id: contractor-international-travel
required_facts:
- contractor eligibility
- international travel rule
- approval authority
required_qualifiers:
- employment type
- effective date
unanswerable_when:
- contractor policy is absent
- applicable revisions conflict without precedence
A retrieval gate can then distinguish:
- sufficient: all required facts and qualifiers have applicable evidence;
- partial: a useful subset is supported, but the full question is not;
- conflicting: applicable sources disagree and precedence cannot resolve them;
- insufficient: required evidence is missing;
- unauthorized: evidence may exist but is outside the principal's access scope.
Calibrate any model score or threshold on representative answerable, unanswerable, conflict, stale-source, and cross-tenant slices. A threshold copied from another corpus has no established meaning in yours.
Control 3 Assemble Context as Untrusted Evidence
Retrieved content is data, not instruction. Documents can be stale, mutually inconsistent, malformed, or intentionally carry prompt injection text.
The context assembler should:
- keep the system instruction outside evidence delimiters;
- label every passage with an opaque evidence ID, revision, locator, and applicability;
- exclude unauthorized or inactive evidence before the model call;
- preserve material qualifiers instead of truncating them away;
- group or flag conflicting claims rather than asking the model to silently choose;
- deduplicate near-identical passages so repeated text does not look like independent corroboration;
- enforce a token budget without dropping the source span behind a citation.
OWASP's Prompt Injection guidance explicitly treats RAG content as a path for indirect injection. Delimiters and instructions reduce risk, but they do not create a security boundary. The model must not gain permissions, execute tools, or select protected sources because a retrieved passage says so.
Context should expose conflicts:
<evidence id="travel-2026#p18" status="active" valid_from="2026-01-01">
International travel requires approval from the contractor's sponsoring director.
</evidence>
<evidence id="travel-2024#p11" status="superseded" valid_to="2025-12-31">
International travel requires approval from the department manager.
</evidence>
Application code can remove the superseded passage when precedence is deterministic. If validity or authority is ambiguous, retain the conflict and route the answer to review instead of asking the generator to invent a policy.
Control 4 Verify Claims and Citations
Citations become useful only when the system checks resolution, entailment, and coverage. A generated [Source: policy.pdf] string proves only that the model produced a plausible identifier.
Use a claim-first response contract:
{
"claims": [
{
"claim_id": "c1",
"text": "Contractors need sponsoring-director approval for international travel.",
"evidence_ids": ["travel-2026#p18"]
}
],
"answer": "Contractors need sponsoring-director approval for international travel [c1]."
}
Validate in this order:
- The response matches the schema.
- Every evidence ID came from the authorized evidence snapshot for this run.
- Every material answer sentence maps to one or more atomic claims.
- Each cited passage supports, contradicts, or is insufficient for its claim.
- Every released claim has sufficient citation coverage.
The ALCE benchmark separates answer correctness from citation quality because a citation can be present but incomplete or unsupported. RAGAS similarly separates context relevance, answer relevance, and faithfulness. Preserve those distinctions in production instead of reducing them to one is_hallucinated Boolean.
An LLM or NLI model can propose claim-support verdicts, but it is still an evaluator with errors. Version its prompt and model, retain the evidence pairs, and calibrate contradiction and insufficient-evidence precision and recall against human labels. RAGTruth contains both contradictory claims and baseless additions, which is useful for testing whether a verifier detects different failure types.
Control 5 Make Release and Abstention Deterministic
The final release decision should be an application policy, not another free-form model answer. The policy consumes evidence and verifier results, then chooses a bounded outcome:
| Outcome | Required condition | User-visible behavior |
|---|---|---|
release |
every material claim is supported | return the complete answer with resolvable citations |
partial |
a useful subset is supported | return only supported claims and name the missing scope |
abstain |
evidence is absent or insufficient | state what could not be established |
review |
sources conflict or risk is high | hold the answer for an authorized reviewer |
deny |
request or evidence is unauthorized | return no protected evidence |
OpenAI's hallucination research explains why accuracy-only evaluation rewards guessing. RAG policy should therefore score confident errors as more costly than appropriate abstention, while also measuring false refusals. A system that always abstains has low hallucination but no utility.
Do not automatically regenerate after every failed verification. A bounded retry is justified only when it changes something testable: a corrected query, another authorized source, a smaller claim, or a different generation configuration. Retrying the same evidence and prompt can repeat the same error while increasing latency and cost.
Build a Minimal Evidence Gate
The following Python 3.9+ program implements the deterministic part of the contract. A retriever and verifier may use ML models, but only this policy decides what can be released.
from dataclasses import dataclass
from enum import Enum
from typing import Dict, FrozenSet, List, Optional
class Verdict(str, Enum):
SUPPORTED = "supported"
CONTRADICTED = "contradicted"
INSUFFICIENT = "insufficient"
class Outcome(str, Enum):
RELEASE = "release"
PARTIAL = "partial"
ABSTAIN = "abstain"
REVIEW = "review"
DENY = "deny"
@dataclass(frozen=True)
class Evidence:
evidence_id: str
tenant: str
allowed_roles: FrozenSet[str]
status: str
valid_from: int
valid_to: Optional[int]
@dataclass(frozen=True)
class Claim:
claim_id: str
text: str
evidence_ids: tuple
verdict: Verdict
is_material: bool = True
@dataclass(frozen=True)
class Decision:
outcome: Outcome
released_claim_ids: tuple
reasons: tuple
def is_authorized(
evidence: Evidence,
*,
tenant: str,
roles: FrozenSet[str],
now: int,
) -> bool:
return (
evidence.tenant == tenant
and bool(evidence.allowed_roles & roles)
and evidence.status == "active"
and evidence.valid_from <= now
and (evidence.valid_to is None or now <= evidence.valid_to)
)
def decide(
claims: List[Claim],
evidence_by_id: Dict[str, Evidence],
*,
tenant: str,
roles: FrozenSet[str],
now: int,
high_risk: bool,
) -> Decision:
reasons = []
supported = []
for claim in claims:
evidence = [evidence_by_id.get(item) for item in claim.evidence_ids]
if not evidence or any(item is None for item in evidence):
reasons.append("%s:unknown_evidence" % claim.claim_id)
continue
if not all(
is_authorized(item, tenant=tenant, roles=roles, now=now)
for item in evidence
):
return Decision(Outcome.DENY, (), ("%s:unauthorized" % claim.claim_id,))
if claim.verdict == Verdict.CONTRADICTED:
return Decision(Outcome.REVIEW, (), ("%s:contradicted" % claim.claim_id,))
if claim.verdict == Verdict.SUPPORTED:
supported.append(claim.claim_id)
elif claim.is_material:
reasons.append("%s:insufficient" % claim.claim_id)
material = [claim for claim in claims if claim.is_material]
if not material or not supported:
return Decision(Outcome.ABSTAIN, (), tuple(reasons or ["no_material_claims"]))
if high_risk and reasons:
return Decision(Outcome.REVIEW, (), tuple(reasons))
if reasons:
return Decision(Outcome.PARTIAL, tuple(supported), tuple(reasons))
return Decision(Outcome.RELEASE, tuple(supported), ())
evidence = {
"travel-2026#p18": Evidence(
evidence_id="travel-2026#p18",
tenant="acme",
allowed_roles=frozenset({"employee", "contractor-manager"}),
status="active",
valid_from=1_767_225_600,
valid_to=None,
)
}
supported_claim = Claim(
claim_id="c1",
text="Contractors need sponsoring-director approval.",
evidence_ids=("travel-2026#p18",),
verdict=Verdict.SUPPORTED,
)
missing_claim = Claim(
claim_id="c2",
text="The company always reimburses business-class fares.",
evidence_ids=("travel-2026#p18",),
verdict=Verdict.INSUFFICIENT,
)
release = decide(
[supported_claim],
evidence,
tenant="acme",
roles=frozenset({"contractor-manager"}),
now=1_800_000_000,
high_risk=False,
)
partial = decide(
[supported_claim, missing_claim],
evidence,
tenant="acme",
roles=frozenset({"contractor-manager"}),
now=1_800_000_000,
high_risk=False,
)
denied = decide(
[supported_claim],
evidence,
tenant="other",
roles=frozenset({"contractor-manager"}),
now=1_800_000_000,
high_risk=False,
)
assert release.outcome == Outcome.RELEASE
assert partial.outcome == Outcome.PARTIAL
assert partial.released_claim_ids == ("c1",)
assert denied.outcome == Outcome.DENY
print(release.outcome.value, partial.outcome.value, denied.outcome.value)
Expected output:
release partial deny
The program intentionally does not infer semantic support. It consumes versioned verifier verdicts and proves the policy behavior independently. In a real system, store the verifier version, evidence digest, claim digest, policy version, and decision together.
Test the Complete Control Loop
Tests should perturb every boundary, not only ask whether a few happy-path answers look good.
| Test slice | Injected condition | Expected behavior |
|---|---|---|
| missing evidence | required passage absent from top candidates | abstain or retrieve again within budget |
| stale source | superseded policy ranks first | filter before generation |
| cross-tenant source | relevant passage belongs to another tenant | deny, with no content sent to the model |
| conflicting sources | two applicable revisions disagree | review or explicit conflict answer |
| lost qualifier | chunk omits an exception or date | fail sufficiency gate |
| indirect injection | document tells the model to ignore policy | treat text as data; no privilege change |
| invented citation | claim names an unseen evidence ID | reject before release |
| unsupported addition | answer adds an uncited material claim | partial, abstain, or review |
| false refusal | sufficient evidence exists but system abstains | count against abstention recall and utility |
| verifier drift | judge version changes verdict distribution | block release until recalibrated |
For metric definitions and release-set design, use the Production RAG Evaluation Guide. At minimum, report:
- evidence Recall@k and sufficiency recall;
- stale, unauthorized, and conflicting evidence rate;
- claim support and contradiction precision/recall;
- citation precision and material-claim coverage;
- abstention precision, abstention recall, selective risk, and answer coverage;
- external correctness and end-to-end task success;
- latency, model calls, token use, and human-review load.
Report these metrics by query type, source type, language, tenant, risk, and answerability. Averages can hide a complete failure on the one policy or customer segment that matters most.
Operational Checklist
Before release, verify:
- Every corpus build has an immutable revision and deletion manifest.
- Authorization filters run before candidate retrieval.
- Query rewrites are bounded, logged, and evaluated for intent drift.
- Retrieval tests include answerable, unanswerable, conflicting, stale, and cross-tenant cases.
- Context preserves qualifiers, source locators, validity, and conflict state.
- Retrieved text cannot authorize tools or override system policy.
- Every material claim maps to a resolvable evidence ID.
- Claim-support judges are versioned and calibrated against human labels.
- Release policy can return
partial,abstain,review, anddeny. - Production events contain digests and decisions, not raw secrets or unnecessary document text.
Frequently Asked Questions
Why does RAG still hallucinate when it retrieves documents?
Retrieval provides candidate evidence, not a factual guarantee. The correct passage may be absent, qualifiers may be split away, sources may be stale or conflicting, and the generator may add claims that no passage supports. Diagnose corpus, retrieval, context, generation, citation, and release-policy failures separately.
Can a similarity or reranker threshold prevent RAG hallucinations?
No universal threshold proves answerability. Score distributions change across retrievers, corpora, languages, query classes, and model revisions. Calibrate thresholds on labeled answerable and unanswerable cases, then combine them with required-evidence coverage and conflict detection.
Do citations guarantee that a RAG answer is faithful?
No. Validate three properties: the citation resolves to an evidence object from this authorized run, the cited span supports the exact claim, and citations cover every material claim. Citation presence without entailment and coverage is presentation, not verification.
Should a RAG system regenerate an unsupported answer?
Only use a bounded retry when it changes a testable condition, such as retrieving another authorized source, repairing a drifted query, or narrowing the requested claim. Otherwise the safer outcome is a supported partial answer, abstention, or authorized review.
How should teams measure RAG hallucination mitigation?
Measure the complete evidence chain: source authority and freshness, retrieval coverage and sufficiency, conflicts and permission leakage, claim support and contradiction, citation quality, abstention behavior, external correctness, task success, latency, cost, and review burden. Preserve denominators and report risk slices.
Summary
RAG hallucination mitigation works when the application treats every answer as a claim-evidence proposal. Govern source identity and scope, retrieve for sufficiency, assemble untrusted context without hiding conflicts, verify each material claim and citation, and let deterministic policy decide whether to release, narrow, abstain, review, or deny. No prompt, score, citation label, model upgrade, or judge can replace that control loop.
Related Resources
- RAG: Retrieval-Augmented Generation Guide
- RAG Chunking Experiments
- Hybrid Search and Reranking
- Production RAG Evaluation
- Answer Faithfulness
- Citation
- Context Recall
Primary Sources
- Ragas: Automated Evaluation of Retrieval Augmented Generation
- RAGTruth: A Hallucination Corpus for Retrieval-Augmented Language Models
- Enabling Large Language Models to Generate Text with Citations
- Seven Failure Points When Engineering a RAG System
- OpenAI: Why Language Models Hallucinate
- OWASP LLM01:2025 Prompt Injection
- Anthropic: Introducing Contextual Retrieval