What you'll learn: how to assemble retrieved chunks into a token-budgeted context with citation ids, how code (not the model) validates every citation, and how the capstone refuses a question the documents cannot answer, including the real q36 case where that is wrong.
At a glance
Figure 1. From retrieved chunks to a validated, cited answer
How to read this: three places can stop or change the answer, the gate before retrieval, the budget during assembly and the validator after the model, and all three are code.
- The context is a ranked list of chunks labelled
[doc_id#n], cut to a token budget; the best chunk is always kept. - The model proposes citations; code keeps only those that exist in the context and mention the product.
- "No citation, no claim": a recommendation without a valid citation is dropped.
- Refusal is a cheap word-coverage heuristic, and sometimes wrong. q36 is the capstone's own miss.
Scenario
Composite scenario, not a real company. A Lumen sales engineer pastes the assistant's answer into a customer email. The customer replies that the source ds-gw-200#9 does not exist, and that a question about a white-label version got no clear answer.
Nothing checked the model's sources, and nothing said the documents were silent. The sales director wants no invented sources and a clear refusal when the documents say nothing. Figures are illustrative.
What you will be able to do
- Fit ranked chunks into a token budget and read the cost of each budget.
- Explain why citations are checked in code and what the check adds beyond "the id exists".
- Compute a coverage score by hand and show where the refusal rule fails.
- Find the smallest budget that keeps an answer unchanged, and say why production should not copy it.
Prerequisites
Metadata filters and tenant-aware access control for the filter that decides which chunks can enter the context, and Structured output for why model JSON is validated.
Core ideas
The context is a ranked list with a budget
The Lumen request ("monitor vibration on 40 pumps in a humid plant, budget around 30k, must work with our existing OPC UA setup") retrieves 10 public chunks, at most 3 per document (the capstone's default is 6; 10 makes the budget easier to see). Tokens are the capstone's estimate: words times 1.3, rounded up.
| Rank | Chunk | Tokens | Running total | |---|---|---|---|---| | 1 | compat-opcua#5 | 54 | 54 | | 2 | ds-gw-200#5 | 29 | 83 | | 3 | faq-licensing#2 | 34 | 117 | | 4 | ds-gw-200#2 | 47 | 164 | | 5 | ds-gw-500#2 | 64 | 228 | | 6 | ds-gw-200#3 | 43 | 271 | | 7 | ds-gw-300#2 | 62 | 333 | | 8 | faq-vs-200#4 | 37 | 370 | | 9 | ds-gw-410#3 | 54 | 424 | | 10 | ds-ct-400#2 | 51 | 475 |
Predict first. The default budget is 700 tokens (context_budget_tokens in Settings). How many of these 10 chunks does it drop?
Answer
None. The total is 475 tokens, under 700, so all 10 fit. The budget did not bind here, nor on any of the 36 golden questions asked as a public user. It matters with long chunks, a larger k or a small model window.
The assembler:
- walks the hits in rank order;
- keeps each that fits and counts the rest as dropped;
- always keeps the best hit.
Verified: capstone/fde-product-discovery-copilot/app/retrieval/context.py
def assemble_context(hits: list[Hit], budget_tokens: int | None, words_to_tokens: float = 1.3) -> Context:
"""Take hits in rank order and keep each one that still fits. The best hit is always kept.
budget_tokens=None means no budget (every hit goes in)."""
ctx = Context()
for hit in hits:
cost = estimate_tokens(hit.chunk.text, words_to_tokens)
fits = budget_tokens is None or ctx.tokens + cost <= budget_tokens
if fits or not ctx.chunks:
ctx.chunks.append(hit.chunk)
ctx.tokens += cost
else:
ctx.dropped += 1
return ctx
budget_table in the lesson code runs it for several budgets (pinned by a test). At budget 10 the best hit is still kept:
| Budget (tokens) | Chunks kept | Dropped | Tokens used |
|---|---|---|---|
| none | 10 | 0 | 475 |
| 700 | 10 | 0 | 475 |
| 400 | 8 | 2 | 370 |
| 200 | 4 | 6 | 164 |
| 100 | 2 | 8 | 83 |
| 10 | 1 | 9 | 54 |
Order may matter too: "Lost in the Middle" (linked below) reports that models use the start and end of the input best. The capstone keeps rank order; edges_first in the lesson code tries best first, second best last. Its effect is not measured here.
Citations: the model proposes, code disposes
Each chunk is rendered as [doc_id#n] text. The model must cite only those ids; the pipeline checks what comes back.