Medical AI · Retrieval-augmented generation (RAG) · Lesson 5 of 7
Citations and answer grounding
Reviewed by Dr. Ram Kumar G, MBBS, MD (Internal Medicine), MPH (USA), on · Last updated
- Intermediate
- Technical focus
- About 30 minutes
- Includes exercise
Before this lesson: Evaluating retrieval, Hallucination and grounding
In short
In a RAG system, each statement in an answer should cite the passage it came from, so a reader can check it. But models attach citations to claims their sources do not support, cite the wrong passage, or reverse a rule while keeping its words. Check every claim against its cited source automatically where possible, flag or remove unsupported claims, and remember that simple word-overlap checks miss negation: 'start metformin' and 'do not start metformin' look almost the same to them.
You will learn to
- Explain why citations need to be verified, not just displayed
- Check claim-level support against cited passages in Python
- Find a better source for a mis-cited claim, or flag it as unsupported
- Recognise the limits of lexical checks, especially for negation
What can go wrong with citations
Each problem looks reassuring on screen. A citation creates the impression of evidence, so unverified citations can be more misleading than none.
| Problem | Example |
|---|---|
| Correct claim, wrong citation | 'Stop metformin below eGFR 30' cites the hypoglycaemia section |
| Unsupported claim with a citation | 'Metformin can be continued at any eGFR above 15' cites the kidney section, which says no such thing |
| Reversed meaning | 'Start metformin if eGFR is below 45' cites a passage saying not to |
| Citation to a retrieved but unused passage | A long list of sources, most of which the answer did not use |
Checking claims against their sources
The check below takes an answer split into claims, each with the section it cites, and measures how much of the claim's content, and every number in it, appears in the cited section. Run the protocol block from the chunking lesson first.
import re
sections = {f"s{i + 1}": p.strip() for i, p in enumerate(re.split(r"\n(?=## )", PROTOCOL)[1:])}
answer = [ # (claim, section it cites)
("Check eGFR at least once a year.", "s2"),
("Do not start metformin if eGFR is below 45.", "s2"),
("Stop metformin if eGFR falls below 30.", "s4"), # right fact, wrong citation
("Metformin can be continued at any eGFR above 15.", "s2"), # unsupported claim
("Start metformin if eGFR is below 45.", "s2"), # reverses the rule
]
STOP = {"the", "a", "an", "is", "if", "at", "of", "to", "and", "or", "be", "can", "any", "do", "not"}
def content_words(text):
return {w for w in re.findall(r"[a-z0-9]+", text.lower()) if w not in STOP}
def support(claim, source_text):
"""Share of the claim's content words, and all of its numbers, found in the cited source."""
words, src = content_words(claim), content_words(source_text)
numbers_ok = all(n in src for n in re.findall(r"\d+", claim))
return len(words & src) / len(words), numbers_ok
for claim, cited in answer:
overlap, numbers_ok = support(claim, sections[cited])
verdict = "supported" if overlap >= 0.8 and numbers_ok else "CHECK"
print(f"{verdict:<9} [{cited}] overlap {overlap:.2f} numbers {'ok' if numbers_ok else 'missing'} {claim}")
# supported [s2] overlap 1.00 numbers ok Check eGFR at least once a year.
# supported [s2] overlap 0.80 numbers ok Do not start metformin if eGFR is below 45.
# CHECK [s4] overlap 0.17 numbers missing Stop metformin if eGFR falls below 30.
# CHECK [s2] overlap 0.40 numbers missing Metformin can be continued at any eGFR above 15.
# supported [s2] overlap 0.80 numbers ok Start metformin if eGFR is below 45.- The wrong citation and the unsupported claim are both caught.
- The last line is the most dangerous failure: 'Start metformin if eGFR is below 45' reverses the rule, yet passes, because 'not' is ignored as a stop word and every other word matches. Word overlap measures topic, not meaning.
Repairing citations
def best_source(claim):
return max(sections, key=lambda sid: support(claim, sections[sid])[0])
for claim, cited in answer:
overlap, numbers_ok = support(claim, sections[cited])
if not (overlap >= 0.8 and numbers_ok):
better = best_source(claim)
o2, n2 = support(claim, sections[better])
fix = f"cite {better} instead" if o2 >= 0.8 and n2 else "no section supports it: remove or flag"
print(f"{claim}\n -> {fix}")
# Stop metformin if eGFR falls below 30.
# -> cite s2 instead
# Metformin can be continued at any eGFR above 15.
# -> no section supports it: remove or flag- A correct claim with the wrong citation can be repaired automatically; an unsupported claim should be removed or clearly flagged, never kept with a borrowed citation.
Stronger checks and safer designs
| Approach | Strength | Limitation |
|---|---|---|
| Word and number overlap (above) | Fast, transparent, catches invented numbers and wrong sections | Misses negation, paraphrase and reversed meaning |
| Entailment (natural language inference) models | Judge whether the source logically supports the claim | Can be wrong on clinical language; need testing |
| A second LLM as verifier | Handles paraphrase and context | Can share the generator's errors; must be evaluated |
| Extractive answers (quote the source) | Every word is in the source; citations are exact | Less fluent; must quote enough context |
| Quote then answer | Model first copies the supporting sentences, then answers from them | Still needs checking that the answer follows the quotes |
Negation is a known weak spot
- Clinical text is full of negation: 'no', 'not', 'denies', 'without', 'stop'. Any automated support check used in a clinical system should be tested specifically on negated and reversed claims.
Exercise
Build and stress-test a claim-level citation checker on the synthetic protocol.
Data: Synthetic protocol and answer claims in the notebook (Created for this course; not clinical guidance)
- Reproduce the support check and the citation repair.
- Write ten more claims, including three negated or reversed ones, and record which the checker gets wrong.
- Add a negation rule: flag any claim whose negation words ('not', 'no', 'stop', 'never') differ from the best-matching source sentence.
- Optional: try an open natural language inference model on the same claims and compare.
Open the notebook in ColabView on GitHubWorked solution coming soon
Key takeaways
- Citations only help if the cited passage actually supports the claim.
- Check every claim; repair wrong citations and remove unsupported claims.
- Word-overlap checks miss negation and reversed meaning; test for them specifically.
- Extractive or quote-then-answer designs make grounding easier to verify.
Terms used: Grounding, Hallucination, RAG (retrieval-augmented generation)
Related: Hallucination and grounding, Build a guideline Q&A assistant, Evaluating retrieval
FAQ
Do citations make RAG answers trustworthy?
Only if they are checked. Models can attach citations to statements the source does not support, so citations make verification possible rather than guaranteeing accuracy.
How can citations be checked automatically?
By comparing each claim with its cited passage, using word and number overlap, entailment models or a second model as verifier, and flagging claims that are not supported.
Why do simple checks miss reversed rules?
Because 'start' and 'do not start' share almost all their words. Overlap measures whether the topic matches, not whether the meaning is the same.
What is an extractive answer?
An answer made of sentences quoted directly from the source documents, so every statement can be traced exactly. It is less fluent than generated text but easier to verify.
Should unsupported claims be shown with a warning?
In clinical settings it is usually safer to remove them, or show them clearly separated from supported content, than to display them with a citation that does not support them.
Sources
- Gao T, Yen H, Yu J, Chen D. Enabling large language models to generate text with citations. EMNLP 2023. arXiv:2305.14627
- Liu NF, Zhang T, Liang P. Evaluating verifiability in generative search engines. Findings of EMNLP 2023. arXiv:2304.09848
- Asgari E, et al. A framework to assess clinical safety and hallucination rates of LLMs for medical text summarisation. npj Digital Medicine 2025;8:274. doi:10.1038/s41746-025-01670-7
- Chapman WW, et al. A simple algorithm for identifying negated findings and diseases in discharge summaries. J Biomed Inform 2001;34(5):301–310. doi:10.1006/jbin.2001.1029
Educational content only. This material teaches methods; it is not clinical advice. Code, models and examples here are not validated medical devices and must not be used to make decisions about real patients without proper clinical validation, governance and any required regulatory approval. Examples use synthetic or openly licensed de-identified data only. Never paste identifiable patient information into public AI tools. Mentions of models, tools or vendors are for identification, not endorsement.