How-to
How to ground an LLM answer in your own sources
Grounding is a prompt, a validation step, and a refusal path. All three are required, and the third is the one people skip.
9 min read
Grounding means the answer only contains claims your sources support. It is made of three parts — a prompt that constrains, a validation step that checks, and a refusal path that is allowed to say no — and almost every implementation ships the first, sometimes the second, and rarely the third.
Why the prompt alone is not enough
The usual instruction is some variant of "answer using only the provided context". This helps, measurably. It does not hold, for a structural reason: the model has no way to distinguish between something it read in the context and something it already knew. Both are just activations. When the context is nearly sufficient, filling the last gap from parametric knowledge is the locally reasonable move, and the result is a fluent answer with one unsupported sentence in it.
That one sentence is the whole problem. It is the sentence a reader will act on, it is indistinguishable in tone from the supported ones, and it will carry a citation marker because the model was asked to cite.
Part one: the prompt
Write it to make refusal an explicitly acceptable output, not just an absence of an answer. The difference in phrasing matters more than it should.
Answer only from the numbered sources below.
If the sources do not contain the answer, say so
plainly rather than filling the gap from your own
knowledge. Saying "the sources provided do not
cover this" is a correct and complete answer.
Cite every claim with the number of the source
that supports it, like [2]. Do not cite a source
that does not support the specific claim.
Sources:
[1] {title} — {chunk}
[2] {title} — {chunk}Two details in there are doing real work. Numbering the sources gives the model a concrete referent, which makes markers far more likely to be correct than asking it to cite by title. And stating that a refusal is "correct and complete" removes the implicit pressure to produce something — models are trained to be helpful, and an unhelpful-looking output needs explicit permission.
Part two: validation
After generation, check every citation marker against the sources you actually supplied. This is a short function and it catches a class of error the prompt cannot.
const supplied = new Set(sources.map((s, i) => i + 1));
const cited = [...answer.matchAll(/\[(\d+)\]/g)]
.map((m) => Number(m[1]));
const invented = cited.filter((n) => !supplied.has(n));
// Drop the markers rather than the answer: the prose
// is usually fine and the marker is the lie.
let clean = answer;
for (const n of invented) {
clean = clean.replaceAll(`[${n}]`, "");
}
// Worth logging. A rising invented-marker rate is an
// early signal that retrieval is returning too little.
if (invented.length) metrics.increment("citation.invented");Dropping the marker rather than rejecting the answer is the right default. An invented marker usually attaches to a sentence that is itself fine — the model summarised correctly and mis-numbered. Rejecting the whole answer for that would be a worse experience than removing a bad footnote.
Part three: the refusal path
This is the part that gets skipped, and skipping it silently invalidates the other two.
A refusal path means the system is architecturally allowed to return "your sources do not cover this", and that this outcome is measured rather than treated as a failure. If refusal is not measured, nobody notices when the rate drops to zero — and a zero refusal rate does not mean your corpus is comprehensive, it means your system stopped refusing.
The practical way to keep it honest is to build refusal controls into your evaluation set: questions your corpus genuinely cannot answer, where the correct output is a refusal. Score those. A system that answers them has not become more capable; it has become less trustworthy.
Keep derived context uncitable
If you supply the model anything beyond raw passages — a summary, a knowledge-graph fact, an inferred relationship — it must not be citable, and the prompt has to say so.
The reason is that derived context is your reading of the sources rather than something a source says. If a reader follows a citation and lands on a document that does not contain the cited claim, you have produced something worse than an uncited answer: a claim that looks checkable and is not.
Context — how these sources relate. This is
background only. Do NOT cite it.
- Onboarding completion reduces churn
(supported by 3 of the sources below)
Sources — cite these by number.
[1] Q3 Retention Review — ...
[2] Support Themes 2026 — ...What grounding cannot fix
- A corpus that does not contain the answer. Grounding converts a confident wrong answer into an honest refusal, which is a large improvement and is not the same as an answer.
- Retrieval that returned the wrong passages. The model will faithfully ground itself in irrelevant sources and produce a well-cited answer to a question nobody asked.
- Chunk boundaries that cut a claim in half. A half-claim is grounded in exactly the way you asked for.
Which is why an invented-marker rate and a refusal rate are worth watching as retrieval diagnostics rather than as generation diagnostics. Both usually rise because retrieval is returning too little, not because the model got worse.
A checklist
- Number the sources in the prompt and require numeric markers.
- State explicitly that a refusal is a correct and complete answer.
- Validate markers after generation; drop the invented ones and count them.
- Mark any derived context as non-citable.
- Put refusal controls in your evaluation set and score them.
- Watch the refusal rate. A zero is a bug, not a success.