learn-rag-with-phoebe / Builder session 7 of 10
Learn RAG with Phoebe · Builder track · Session 7 of 10

Grounding and citations: answer from the sources, or not at all

Retrieval hands you the right passages. Now the model has to use them - and only them. This session is about the discipline that turns a fluent guesser into a trustworthy support bot: answer strictly from the retrieved context, attach a citation to every claim, and refuse when the answer simply is not there. You will write the grounding prompt, wire Anthropic's Citations on a document block, and treat "I do not have that in our documents" as a feature, not a bug. Recall is now reading Corpus C, a real company knowledge base.

🟠 Builder track Practitioners · some Python Live playground included Session 7 of 10
0-3 · Welcome 3-18 · Concepts 18-40 · Build-along 40-45 · Q&A
Part 0

The other half of RAG

For six sessions we made retrieval good: embeddings, chunking, vector stores, metadata, hybrid search, reranking. But a perfect top-k is worthless if the model then ignores it and answers from memory. Grounding is the contract that the answer must come from the retrieved context - and only from it. Add a citation to every claim so a reader can check it, and a refusal path for when the context does not contain the answer, and you have the difference between a demo and a support bot you can put in front of customers.

Live - presented in session Self-study - full depth after class ★ Try it now Sources covered at the end
★ What you build today Recall's grounding-and-refusal system prompt, an understanding of how Anthropic Citations pins each claim to a source location, and a felt sense - in the live playground - of the exact moment retrieval is too weak to answer, where a good bot refuses instead of guessing.
Part 1 · covers grounded generation + the prompt

Grounded generation: use only what you were handed 8 min live

The generation step of RAG takes the retrieved passages, the user's question, and a system instruction, and asks the model to write an answer. Grounding is what you put in that instruction: answer using only the provided context; if it is not there, say you do not know; cite the source of every claim. Get those three sentences right and most hallucinations disappear.

[C-refund] Refunds in 5-7 business days, 30-day window [C-ship] Standard 3-5 days, express next business day top-k retrieved passages "how long for a refund?" the user's question Grounding prompt Answer using ONLY these passages. If the answer is not present, say you do not know. Cite the source id. Answer Refunds arrive within 5-7 business days to your original payment method. [C-refund] Context + question + "use only this" instruction in, cited answer out. The citation is not decoration - it is the audit trail.
🔍 Click to zoom - grounded generation: retrieved context plus a strict instruction produces a cited answer
LiveThe three moves of a grounding prompt3 min

Anthropic's "Reduce hallucinations" guidance reads almost like a checklist for a RAG system prompt. Three moves do most of the work:

  • Restrict to the provided context. Tell the model explicitly that the answer must come from the passages you supplied, not its training data. This is the single highest-leverage sentence in the whole system.
  • Allow "I do not know." Give the model permission to say it cannot find the answer. Models hallucinate partly because they feel obliged to produce something; remove that obligation and the guessing drops.
  • Quote first, then answer. Ask the model to pull the supporting sentence out of the context before it writes the answer. If it cannot find a supporting quote, it should not make the claim - and it should retract anything it cannot back with a quote.
LiveWriting the grounding prompt for Recall3 min

Here is the pattern applied to Recall over Corpus C. Notice it does three things: sets the rule, formats each passage with a visible source id, and asks for a citation on every claim.

Python · a grounded-answer prompt templateSYSTEM = """You are Recall, a support assistant. Answer the user's question using ONLY the context passages below. If the answer is not in the context, reply exactly: "I do not have that in our documents." Do not use outside knowledge. Cite the source id in [brackets] after each claim it supports.""" def build_prompt(question, passages): # each passage carries its source id so the model can cite it context = "\n\n".join( f"[{p['id']}] {p['text']}" for p in passages ) user = f"Context:\n{context}\n\nQuestion: {question}" return SYSTEM, user passages = [ {"id": "C-refund", "text": "Refunds are issued to the original " "payment method within 5 to 7 business days..."}, {"id": "C-ship", "text": "Standard shipping takes 3 to 5 business days..."}, ] system, user = build_prompt("how long for a refund?", passages)

The model now has everything it needs to answer and nothing it needs to make up. The source ids in the context are what make citations possible - the model can point back to exactly the passage it used.

Restrict-to-context is a spectrum "Stuffing" every retrieved passage into the prompt and hoping the model picks the right one is the weakest form. Restricting to context ("use only these") is stronger. Quote-first-then-answer is strongest, because it forces the model to locate evidence before it writes - the closest a prompt can get to a proof.
Self-studyThree ways to hand context to the model3 min read

The same retrieved passages can be presented to the model in ways that range from careless to rigorous. Know all three, and reach for the strongest your latency budget allows.

TechniqueWhat it doesGrounding strength
Stuffing contextDrop all top-k passages into the prompt, no instruction to stick to themWeak - model may still answer from memory
Restrict-to-contextAdd "answer using only these passages; say you do not know otherwise"Strong - cuts most hallucinations
Quote-first-then-answerModel extracts the supporting sentence, then answers, and retracts any claim with no quoteStrongest - evidence before assertion
Real world

The confident wrong answer. A support team (anonymized) shipped a bot that stuffed context but never told the model to stay inside it. Asked about a policy the KB did not cover, it invented a plausible-sounding 90-day return window from its training data. One sentence - "if it is not in the context, say you do not know" - turned that into an honest "I do not have that in our documents." Same retrieval, one line of prompt.

Part 2 · covers citations + the refusal guardrail

Citations and refusal: the two trust primitives 8 min live

Grounding tells the model to use the context. Citations let a reader verify it used the context. Refusal handles the case the context does not cover. Together they are what make a RAG answer auditable - a claim you can click through to its source, and an honest blank where there is no source.

Retrieve top-k best cosine = score score above threshold? Answer, grounded "Refunds take 5-7 business days. [C-refund]" Refuse, honestly "I do not have that in our documents." YES NO - do not guess A weak best-match is a signal, not a failure. Refusing beats a confident wrong answer every time in support.
🔍 Click to zoom - the refusal decision: answer with a citation, or refuse when nothing scores high enough
LiveWhy refusal is a feature, not a failure2 min

New builders treat "I do not know" as a bug to be minimized. In a support bot it is the opposite - it is the safety rail. The cost of a confident wrong answer ("yes, we offer a 90-day refund") is a broken promise, a support ticket, and lost trust. The cost of an honest refusal is one more click for the user. Refusal is cheaper, every time.

  • The threshold is your dial. If the best retrieval score is below some bar, the answer probably is not in your KB - so refuse. Tune the bar on real questions (that is session b9).
  • Refusal protects the citation contract. A model that must cite a source cannot cite one that does not exist. So "no good source" naturally becomes "no answer".
  • It is testable. You can write out-of-KB questions and assert the bot refuses. A bot that never refuses is a bot that hallucinates on the edges.
LiveAnthropic Citations on a document block3 min

You can ground and cite with pure prompting, but Anthropic's Citations feature does it structurally: pass each retrieved chunk as a document content block with citations enabled, and the API returns the exact cited_text and its location for every claim.

Python · Citations enabled on a document blockimport anthropic client = anthropic.Anthropic() resp = client.messages.create( model="claude-sonnet-5", max_tokens=1024, messages=[{ "role": "user", "content": [ { "type": "document", "source": { "type": "text", "media_type": "text/plain", "data": "Refunds are issued to the original payment " "method within 5 to 7 business days.", }, "title": "C-refund", "citations": {"enabled": True}, }, {"type": "text", "text": "How long does a refund take?"}, ], }], ) print(resp.content) # answer blocks carry cited_text + location
One chunk, one document block Put each RAG chunk in its own plain-text document block. Plain text is split into sentence chunks and cited by character-index location; PDFs cite by page; custom content cites by block index. And cited_text does not count toward your output tokens - the quoting is effectively free, so there is no cost reason to skip citations.
Self-studyCitation location types3 min read

The location Anthropic returns with each citation depends on how you supplied the document. Matching the document type to how your users read the source makes the citations clickable in your UI.

Document typeHow it is chunkedLocation returned
Plain textSplit into sentencesCharacter-index range (start, end)
PDFBy pagePage location
Custom contentYour own blocksBlock-index location

For a RAG pipeline, plain-text document blocks - one per chunk - give the finest, sentence-level citations, which is usually what you want in a support answer. Custom content blocks are the move when you have already chunked deliberately and want the citation to point back to your exact chunk boundaries.

Self-studyPrompt-only citations vs the Citations feature2 min read

You can get citations two ways, and it is worth knowing when each earns its keep. Asking the model to write [C-refund] in its prose costs nothing to set up but is only as reliable as the prompt. Enabling the Citations feature returns the exact supporting sentence and its location as structured data you can render as a clickable link.

ApproachWhat you getWhen to reach for it
Prompt-onlySource ids written into the answer textQuick prototypes, logs, internal tools
Citations featureStructured cited_text + location per claimCustomer-facing UIs where users click through to the source

The two are not exclusive - a production support bot often uses the Citations feature for the clickable evidence and still keeps the grounding instruction in the system prompt, because the instruction is what stops the model answering off-context in the first place. Citations prove the answer; grounding causes it.

Build-along · covers the refusal threshold in the wild

★ Feel the refuse case: Recall over Corpus C ★ 8 min · live playground

This is Recall reading Corpus C, a company help-center knowledge base - the same shape of documents a support bot answers from. Ask a real support question and watch the right chunk rise. Then ask something the KB does not cover and watch the tool hit its refuse case - the exact decision your grounding prompt has to make.

Honesty note - what this playground really does To run offline with zero network calls, this tool uses a simplified lexical embedding - term frequency over a small vocabulary with a synonym map - not a neural model. But the ranking mechanic is the real one: cosine similarity over vectors, exactly as production RAG does it. When nothing scores above zero, the tool shows an explicit refuse panel - that is a stand-in for the score threshold your real bot uses to decide "answer" versus "I do not have that in our documents."

Ask "how do I reset my password?" or "how long for a refund?" and confirm the right KB chunk ranks first - that is the answer your grounding prompt would cite. Then ask "what is your stock price?" or "do you offer student discounts?" - things Corpus C simply does not contain - and watch the refuse panel appear. That panel is session b7 in one screen: when the best match is too weak, refuse instead of guessing.

Build-along · take it further

Write Recall's grounding-and-refusal prompt ★ 12 min

A grounding prompt is only trustworthy once you have tried to break it. Write Recall's system prompt, then test it against questions it should answer and questions it must refuse.

Draft the prompt. Write a system prompt that (1) restricts Recall to the provided context, (2) permits and specifies the exact refusal string, and (3) requires a source id citation after every claim. Reuse the Part 1 template as your skeleton.

Three in-KB questions. Test against questions Corpus C answers: refund timing, password reset, shipping options. Confirm each answer is correct, stays inside the context, and carries the right citation like [C-refund].

Two out-of-KB questions. Test "what is your stock price?" and "do you sell gift cards?". Confirm Recall returns your exact refusal string and does not invent a plausible policy.

Tune the tone. A bare refusal feels curt. Add one helpful line ("You might try contacting support Monday to Friday") - but only from context. Notice how the citation rule stops you from padding the refusal with invented specifics.

★ Recall's status after b7 Recall now answers strictly from the retrieved context, cites the source id for every claim, and refuses honestly when Corpus C does not hold the answer. It has a conscience. In b8 we wire the whole loop together - retrieve, augment, generate - and ship Recall v1 end to end with the Claude API.
Homework

Before session b8 ◐ 40 min total

Source material

Official sources covered

Taught from Anthropic's official guidance on hallucination control and citations. This page covers the RAG-relevant core of both; the rest (full API reference, tool-use nuances) stays with the source.

Anthropic · Reduce hallucinationsPart 1-2 · allow "I do not know" · restrict to provided context · quote-first then answer · retract claims with no supporting quote
Anthropic · CitationsPart 2 · enable per document block · plain-text sentence + char-index location · PDF page · custom block-index · cited_text is free of output tokens
Anthropic · Messages API (document blocks)Self-study · content-block shapes for documents; full end-to-end wiring lands in b8
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What is the single highest-leverage sentence in a RAG grounding prompt?

Restricting the model to the provided context and permitting "I do not know" is what removes most hallucinations - it is Anthropic's core reduce-hallucinations move.

2 · When the best retrieved passage scores far below your threshold, the right behavior is to...

A weak best-match signals the answer is not in the KB. Refusing beats a confident wrong answer; refusal is a feature, not a failure.

3 · With Anthropic Citations enabled on a plain-text document block, what do you get back and at what token cost?

Plain text is chunked into sentences and cited by character-index location; the returned cited_text is free of output-token cost, so there is no cost reason to skip citations.

Builder session 7 cheat sheet · pin this

GroundingAnswer using ONLY the retrieved context. The model must not fall back on training knowledge.
The three movesRestrict to context · allow "I do not know" · quote-first then answer. Retract any claim with no quote.
Cite the source idFormat each passage with its id, ask for a [bracket] citation per claim. The audit trail.
Refusal is a featureWeak best-match → "I do not have that in our documents." Beats a confident wrong answer.
The thresholdBelow the bar = probably not in the KB = refuse. Tune the bar on real questions (b9).
Anthropic CitationsSet "citations": {"enabled": true} on a document block. API returns cited_text + location.
Location typesPlain text → sentence + char-index · PDF → page · custom → block-index. One chunk per block.
Free quotingcited_text does not count toward output tokens. No cost reason to skip citations.