A chatbot that cites its sources makes a promise: every factual statement in the answer can be traced to a specific piece of text that the reader can open and check. Most implementations break that promise in quiet ways. The links point to whole pages instead of the passage that matters, a citation sits under a sentence it does not support, or the bot fills a gap from memory and decorates the result with a plausible link. This guide covers how to build citation into the pipeline rather than bolt it on at the end: grounding at passage level, presenting sources, deciding what happens to claims with no source, and testing that citations really support the answers they sit under.
What a citation has to do
A useful citation answers three questions for the reader: where did this come from, what exactly does the source say, and when was it read. A bare URL answers only the first, and only loosely. If the page is long, the reader has to hunt for the supporting sentence; if the page has changed since retrieval, they may never find it.
Set the bar at the claim, not the answer. "This answer used these five sources" is a bibliography. "This sentence is supported by this passage" is a citation. The second is harder to build, and it is the only version you can test.
Keep one principle in view throughout: a citation points to evidence; it does not prove anything by itself. Even when everything works, the reader still judges whether the passage means what the answer claims it means. Your job is to make that check fast. For the broader problem of grounding answers in current material, see chatbots that answer with current information; this page focuses on the citation itself.
Ground answers at passage level
Citation quality is decided before the model writes a word. The retrieval step has to hand the model text in units small enough to cite and stable enough to point back to.
- Chunk by meaning. Split documents and fetched pages into passages along headings and paragraphs, a few sentences each. Very long chunks make citations vague; very short ones strip away the context that makes a sentence true.
- Give every passage an identifier and provenance. Store the source URL or document path, the title, the passage text, its position in the source, the publication date if known and the retrieval date. For web pages, keep a copy of the text you retrieved; the live page may read differently next week.
- Show the model identifiers, not just text. Present each passage with its ID in the prompt and instruct the model to cite IDs after each factual sentence.
- Ask for structured output. Have the model return claims together with the passage IDs that support each one, then render the prose from that structure.
A response shape that keeps verification possible:
{
"answer": [
{"text": "The policy applies to contractors as well as staff.",
"supported_by": ["doc-14#p3"]},
{"text": "No source in the collection says when the policy took effect.",
"supported_by": [], "status": "unsupported"}
]
}Then validate before anything reaches the user. Every cited ID must exist in the set retrieved for this question; an ID the model invented is an error, not a citation. Where you ask the model to quote, check that the quote is an exact substring of the passage. Both checks are cheap, and they catch the crudest forms of fabricated support before a reader ever sees them.
Show sources the reader can check quickly
The interface decides whether anyone actually checks. A few patterns that work:
- Inline markers next to the sentence they support, not a list at the bottom. One marker at the end of a paragraph holding three claims tells the reader nothing about which claim it backs.
- The passage on demand. On hover or tap, show the quoted passage with its title, source and retrieval date. Most checks end right there, without opening the page.
- A deep link where possible. Point to the section anchor, the page number or a text fragment so the source opens at the right place.
- Consulted is not cited. If you show the sources the bot read, label them separately from the ones that support statements. Listing every retrieved page under "sources" makes the support look stronger than it is.
- Dates in view. A passage retrieved months ago may describe a situation that no longer holds. Showing the retrieval date lets the reader weigh that.
Decide what happens to claims with no source
The hardest design question is not how to cite, but what the bot does when nothing it retrieved supports what it wants to say. If you leave this open, the model settles it for you, usually by answering from its training data and attaching the nearest link.
Pick an explicit policy and write it into the prompt, the output schema and the tests:
- Drop. Remove unsupported sentences before rendering. Safe, though answers can turn choppy.
- Flag. Keep the sentence, mark it visibly as unsourced and never attach a citation to it.
- Say so. Answer with what was found and state plainly what was not: "The documents describe the approval steps but do not say who signs off on exceptions."
- Ask. Offer to search a wider set of sources, which the user or an administrator has to allow.
Treat an empty retrieval as a legitimate outcome, and keep it apart from a failed one. "I searched and found nothing relevant" and "the search service did not respond" lead to different next steps, and folding both into one vague apology hides a production problem. The note missing evidence is an open question makes the case for keeping these gaps visible instead of smoothing them over.
Test that citations support the answers
A citation that exists is not a citation that holds. Testing needs a fixed question set and checks at three levels.
Mechanical checks, on every response in production and in tests: cited IDs exist in the retrieved set, quotes match the passage text exactly, every factual sentence has either a citation or an unsupported flag, and no citation points at a passage from another question's context.
Support checks, on a test set: for each claim and its cited passage, does the passage actually say this? A second model call can act as a judge that sees only the claim and the passage and answers supported, partly supported or not supported. Calibrate that judge against human grades on a sample before you rely on it.
Human review, on a regular sample: a person reads the answer, opens the sources and grades both. This catches what automated checks miss, such as a citation that is technically accurate but lifted from a source that argues the opposite a few paragraphs later.
Build the test set with uncomfortable cases on purpose:
- Questions whose answer is not in the collection at all.
- Questions on which two sources disagree.
- Questions where the only relevant page is out of date.
- Questions that tempt the model to supply a well-known fact the sources do not contain.
Write acceptance criteria you can check, for example: "Every factual sentence carries a citation or an unsupported flag. Every cited passage, read on its own, supports its sentence. For questions with no answer in the collection, the bot says so and cites nothing." The note on citations that hold up goes further into what a reviewer should look for. Re-run the whole set whenever you change retrieval settings, the prompt or the model, because citation behaviour shifts with each of them.
When the sources come from the web
Web retrieval adds two problems. Pages change and disappear, so a citation to a live URL can stop supporting the sentence it backs. Store the retrieved text with its retrieval date, and cite your stored copy alongside the original address. And search results are not passages: a snippet from a search API is often too thin to ground a claim, so fetch the page, extract the relevant passages and cite those. The guide to reducing hallucinations with grounding covers the retrieval side in more depth.
Where GoCux fits
GoCux is not a chatbot framework and does not search the open web, but it applies the same discipline to the evidence a team works with. Knowledge search takes a question, looks only inside one project's documents and returns passages together with their citations; when nothing matches, it reports that as an answer rather than an error. In research work, a finding backed by a quotation is kept apart from a question that no source answered, so the gaps stay visible. When web material is needed, it comes in through an authorised browsing or export step and carries the address of the original page and the date it was retrieved. Responses prepares an answer so that it can be reviewed before it is sent outside the workspace, which suits answers meant for someone else.
Checklist before you ship
- Passages are small enough to cite, and each carries an ID, source, position and retrieval date.
- The model cites passage IDs per claim through structured output, and invented IDs are rejected.
- Quotes are checked as exact substrings of their passages.
- Markers sit next to the sentence they support, and the passage is one tap away.
- Sources consulted are labelled separately from sources cited.
- Unsupported claims follow a written policy: dropped, flagged, stated or escalated.
- Empty results and failed retrieval produce different messages.
- A fixed test set, including unanswerable and conflicting questions, is re-run after every change to retrieval, prompt or model.
Bring this workflow into your workspace.
Set the scope. Review the evidence. Approve the steps that need it.