Citation hallucination has four distinct failure modes — fabricated URLs, retrieve-then-misquote, URL substitution, and anchor-text drift. They look the same in the response but they have different causes and different fixes. A field report on measuring citation faithfulness in production.
The first time I caught it, I assumed I was misreading the response. The user query was about a specific competitor's pricing. The model produced a confident, well-structured answer with three inline citations. I clicked the first citation. The URL was real. The page existed. The page was about the competitor's product. The page did not contain the price the model had stated. The model had retrieved a real document and emitted a sourced-looking claim that the document did not actually support. Confident, well-cited, and wrong.
The measurement loop I now use, abstracted:
def measure_citation_faithfulness(query, response):
retrieved_urls = extract_retrieved_urls(response)
cited_urls = extract_cited_urls(response)
cited_claims = extract_claim_url_pairs(response)
# Class 1: fabricated URLs
fabricated = cited_urls - retrieved_urls
# Class 2: retrieve-then-misquote
misquote_count = 0
substitution_count = 0
drift_count = 0
for claim, url in cited_claims:
if url not in retrieved_urls:
continue # already counted as Class 1
page_text = fetch_and_extract(url)
if not text_supports_claim(page_text, claim):
# Check if the claim is supported by some other retrieved URL
other_urls = retrieved_urls - {url}
if any(text_supports_claim(fetch_and_extract(u), claim) for u in other_urls):
substitution_count += 1 # Class 3
else:
misquote_count += 1 # Class 2
elif not exact_phrasing_match(page_text, claim, threshold=0.85):
drift_count += 1 # Class 4
return {
"fabricated": len(fabricated),
"misquote": misquote_count,
"substitution": substitution_count,
"drift": drift_count,
"total_citations": len(cited_claims),
}
The non-trivial primitive is text_supports_claim. There are three implementation choices:
Exact-substring match. Look for the literal claim text in the page. This catches very few legitimate citations because models paraphrase routinely. Useful as a "definitely supported" upper-bound check.
Embedding similarity. Embed the claim and the page passages, take the maximum cosine similarity, threshold at something like 0.7. Catches paraphrase, misses subtle inversions ("does not support" embeds close to "supports").
NLI / entailment classifier. Use a
What I have been deploying for clients is a lightweight middleware layer between the model response and the user output. The rough shape:
CODEasync def render_with_citation_check(model_response):
parsed = parse_response_with_citations(model_response)
for citation in parsed.citations:
if citation.url not in parsed.retrieved_urls:
citation.status = "FABRICATED"
citation.display = None # suppress the link entirely
continue
page_text = await fetch_with_cache(citation.url)
entailment = await check_entailment(citation.claim, page_text)
if entailment.label == "entailed":
citation.status = "VERIFIED"
citation.display = citation
elif entailment.label == "contradicted":
citation.status = "CONTRADICTED"
citation.display = None
log_warning(citation)
else: # neutral
citation.status = "UNVERIFIED"
citation.display = citation_with_warning_icon(citation)
return render(parsed)
The entailment check uses a small NLI model the off-the-shelf , . The exact rates I have observed are not reported as quantitative benchmarks because the rates depend heavily on query mix, query class, and provider release; the qualitative claims (Class 2 is most common, Class 4 is hardest to measure, Class 1 is near zero with proper tool use) replicate across the providers I have tested. The measurement methodology is mechanistic and will apply to any provider that returns structured citation data alongside retrieved URLs. Provider behaviour shifts; verify against current docs and your own measurements before committing to a faithfulness-rate target.
Published by Cihangir Bozdogan
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR