Skip to the lesson
FixTheRobot Explore Robotics / LLM / LM11 Building LLM Applications / LM11-T3

LLM · Theory · LM11

LM11-T3 — Retrieval-Augmented Generation

Look up the passages that probably contain the answer, give only those to the model, and make it answer from them. The model can be only as right as the lookup.

  • Lesson 3 of 6
  • Module 11: Building LLM Applications
  • Theory
  • Difficulty: Advanced
  • About 8 minutes to read
  • Prerequisite: LM11-T2

A language model knows what its training data contained, up to its cutoff date, imperfectly. It does not know your manuals, your robot logs or yesterday’s map update, and it will produce fluent text about them anyway. Retrieval-augmented generation (RAG) supplies the relevant text at question time. This lesson builds the pipeline from documents to a grounded answer, defines the metrics that tell you which stage is failing, and explains why the first number to look at is the recall of the retriever. Lab LM11-L1 and Notebook LM11-L3 implement the pipeline.

Learning objectives

After this lesson, you should be able to:

  • Describe the offline indexing and online query stages of a RAG system.
  • Choose chunk size and overlap and count the chunks.
  • Compute recall@k and mean reciprocal rank, and decompose answer accuracy into retrieval and generation.
  • Assemble a grounded prompt and design for the case in which the context has no answer.

The idea: open-book answering

Closed-book answering asks the model to rely on whatever its weights remember. That fails for private, recent or rare facts, and the failures are fluent (hallucination, Module 9). Open-book answering gives the model the relevant pages with the question. The model then has an easier job: read and extract, instead of remember.

A retrieval-augmented generation pipelineTwo rows. Top row, indexing done ahead of time: documents are split into chunks, each chunk is embedded or turned into a TF-IDF vector, and the vectors are stored in an index. Bottom row, at question time: the question is embedded, the index returns the top k chunks, a prompt is assembled from the instruction, the delimited chunks and the question, the model generates an answer that cites the chunk numbers, and a check compares the answer with the chunks.Offline: build the index DocumentsChunkssize · overlapVectorsTF-IDF or denseIndexwith ids and sources Online: answer a question QuestionRetrievetop k chunksPromptdelimited dataModelAnswercites chunks index consulted Evaluate each stage separately: recall@k first, then answer quality given the passages, then the whole.The model sees only what retrieval gives it: a miss here cannot be repaired later.
Indexing happens once, ahead of time. At question time retrieval selects the passages, which are put into a delimited prompt for the model. Each stage can fail on its own and should be measured on its own.

Stage 1: split documents into chunks

Models and retrievers work on pieces of limited size, and a whole manual embeds poorly: its vector is an average of many topics. Documents are therefore cut into chunks of a few hundred words, with some overlap so that a fact on a boundary appears whole in at least one chunk. Small chunks are precise but lose context and multiply the index; large chunks keep context but blur the match and use up the prompt budget (Lab LM11-L1 shows this trade-off). Respecting structure (sections, paragraphs, table rows) usually beats cutting at a fixed number of words. Keep with every chunk its source, title and position so answers can cite it.

Stage 2: retrieve

The question is turned into a vector the same way as the chunks (Lesson LM11-T2) and the top k chunks by similarity are returned. A common improvement is two steps: retrieve 20 to 50 candidates cheaply, then re-rank them with a more expensive model that reads question and passage together, and keep the best few.

Stage 3: assemble the prompt and generate

The prompt contains the instruction (answer only from the passages; say “I don’t know” otherwise; cite passage numbers), the passages in clearly delimited blocks, and the question. The delimiters and the statement that the passages are untrusted data are the first line of defence against injected instructions (Lesson LM11-T6). The model generates; the application can check that the cited passage numbers exist and that quoted text really occurs in the passages.

What RAG fixes and what it does not

  • Fixes: missing or stale knowledge, private data, traceability (you can show the source), updates without retraining (re-index the changed documents).
  • Does not fix: the model ignoring or misreading the passage, retrieval returning the wrong passage, questions that need combining many passages, or the prompt budget. It adds failure modes of its own: stale indexes, hostile documents, leaked access rights.

Fine-tuning versus retrieval

The mathematics: chunk counts, metrics and error composition

Number of chunks

A text of n words cut into chunks of s words with overlap o advances by s − o words per chunk:

chunks needed to cover the text
Nchunks=⌈(n−s)(s−o) ⌉+1N_{\text{chunks}}=\frac{\lceil (n-s)}{(s-o)\,\rceil }+1
(n > s; one chunk if n ≤ s)

For n = 1,000, s = 200, o = 50: ⌈800/150⌉ + 1 = 7 chunks; with no overlap 5; for a 150-word text 1. Overlap increases the index size by the factor s/(s − o) (here 1.33).

Retrieval metrics

For each question the gold passage is the one that contains the answer. Let rankq be its position in the retrieved list (or none if absent):

fraction of questions with the gold passage in the top k; mean reciprocal rank
recall@k=(1Q)⋅#{q: rank⁡q≤k}\text{recall}\text{@}k=(\frac{1}{Q})\cdot \#\{q:\,\operatorname{rank} _{q}\le k\}
MRR=(1Q)⋅∑q 1rank⁡q(0 if absent)\mathrm{MRR}=\frac{(\frac{1}{Q})\cdot \sum _{q}\,1}{\operatorname{rank} _{q}(0\,\text{if absent})}

With ranks (1, 2, 1, 5, absent) for five questions: recall@1 = 0.4, recall@3 = 0.6, recall@5 = 0.8, MRR = (1 + 0.5 + 1 + 0.2 + 0)/5 = 0.54. Recall@k is the quantity that bounds the system: if the gold passage is not among the k passages in the prompt, the model has to guess.

Composition of errors

Let r be the recall@k, a the accuracy of the generator when the gold passage is in the prompt and b its accuracy when it is not (including correct refusals). By the law of total probability:

end-to-end accuracy from its parts
accuracy=r⋅a+(1−r)⋅b\text{accuracy}=r\cdot a+(1-r)\cdot b

With r = 0.9, a = 0.85, b = 0.2: 0.9 · 0.85 + 0.1 · 0.2 = 0.785. Improving retrieval to r = 0.95 gives 0.8175; improving the generator to a = 0.95 gives 0.875. The decomposition tells you where the next hour of work should go, and it needs the two measurements separately.

Prompt budget

The context costs about 1.3 tokens per English word. Four chunks of 200 words are about 1,040 tokens per question; at long prompts the prefill time and the price grow in proportion (Lesson LM10-T5). More passages are not free and can distract the model: accuracy often falls when many irrelevant chunks are added.

A worked example: a tiny end-to-end retriever

The code counts chunks, builds a TF-IDF index over four chunks, retrieves for two questions, and computes recall and MRR from ranks and the error composition. Notebook LM11-L3 extends this to a full prototype with prompt assembly and an injection test.

import math

def nchunks(nwords, size, overlap):
    if nwords <= size:
        return 1
    return math.ceil((nwords - size) / (size - overlap)) + 1

print(nchunks(1000, 200, 50), nchunks(1000, 200, 0), nchunks(150, 200, 50))   # 7 5 1

def tokens(text):
    return ''.join(ch if ch.isalnum() else ' ' for ch in text.lower()).split()

def build(chunks):
    n, df = len(chunks), {}
    toks = [tokens(c) for c in chunks]
    for t in toks:
        for w in set(t):
            df[w] = df.get(w, 0) + 1
    idf = {w: math.log((1 + n) / (1 + d)) + 1 for w, d in df.items()}
    vecs = []
    for t in toks:
        v = {}
        for w in t:
            v[w] = v.get(w, 0) + idf[w]
        vecs.append(v)
    return idf, vecs

def search(idf, vecs, question, k):
    q = {}
    for w in tokens(question):
        if w in idf:
            q[w] = q.get(w, 0) + idf[w]
    qn = math.sqrt(sum(x * x for x in q.values()))
    out = []
    for i, v in enumerate(vecs):
        vn = math.sqrt(sum(x * x for x in v.values()))
        s = sum(x * v.get(w, 0.0) for w, x in q.items()) / (qn * vn) if qn and vn else 0.0
        out.append((s, i))
    out.sort(key=lambda p: (-p[0], p[1]))
    return [(i, round(s, 3)) for s, i in out[:k]]

chunks = ["The dock is in bay four.", "Robots dock when the battery is low.", "A full charge takes ninety minutes.", "Visitors wear vests."]
idf, vecs = build(chunks)
print(search(idf, vecs, "Where is the dock?", 2))              # [(0, 0.619), (1, 0.564)]
print(search(idf, vecs, "How long does a full charge take?", 2))   # [(2, 0.707), (0, 0.0)]

def recall_at_k(ranks, k):
    return sum(1 for r in ranks if r is not None and r <= k) / len(ranks)

def mrr(ranks):
    return sum(1 / r for r in ranks if r is not None) / len(ranks)

ranks = [1, 2, 1, 5, None]                  # rank of the gold passage per question; None = not retrieved
print(recall_at_k(ranks, 1), recall_at_k(ranks, 3), recall_at_k(ranks, 5), round(mrr(ranks), 2))   # 0.4 0.6 0.8 0.54

r, a, b = 0.9, 0.85, 0.2                    # recall, accuracy with the gold passage, accuracy without it
print(round(r * a + (1 - r) * b, 3))      # 0.785

The second question has a score of exactly 0 for the runner-up because that chunk shares no word with the question. The ranking is still well defined; a threshold on the best score is how a system decides it has nothing to offer.

Debugging a RAG system

Where to look when a RAG answer is wrong
SymptomLikely stageCheckRemedy
Right facts exist in the documents but answers are wrongretrievalis the gold chunk in the top k?chunking, hybrid search, re-ranking, query rewriting
Gold chunk is retrieved but the answer ignores itgeneration / promptanswer with the gold chunk onlyclearer instruction, fewer or better-ordered passages, a stronger model
Answers contain facts not in any passagegenerationquote check against the passagesrequire citations; verify quotes; refuse when unsupported
Answers are outdatedindexingindex timestamp versus document changeincremental re-indexing; show dates
Users see content they should notaccess controldoes the index respect permissions?filter by permission before retrieval, not after generation
Cost or latency too highprompt budgettokens per requestsmaller k, shorter chunks, re-ranking to choose fewer

Engineering implications and limitations

Evaluate retrieval on its own. Build a set of real questions with known gold passages (30 to 200) and report recall@k and MRR with intervals (Module 9). Do this before choosing the generator.

Include unanswerable questions. A system that always answers will hallucinate when retrieval has nothing. Measure the rate of correct refusals separately and choose a similarity threshold or an explicit “not found” behaviour from that data.

Ask for citations and check them. Require passage numbers in the answer; verify programmatically that the numbers exist and, where feasible, that quoted strings occur in the cited passage. Show sources to the user.

Filter by permission before retrieval. Access control must be part of the search, not a clean-up after generation, or the model will have seen content the user may not.

Keep the index fresh. Store document versions and timestamps, re-index on change, and delete removed documents. A stale index is a silent source of wrong answers.

Treat retrieved text as untrusted. Documents can contain instructions aimed at the model. Delimit them, instruct the model to treat them as data, and never let their content trigger actions (Lesson LM11-T6).

For robots. A robot assistant that answers from manuals should cite the section and show it to the operator, and it must refuse rather than guess for safety-relevant questions (limits, procedures). Answers from retrieval can inform a human; they should not directly set parameters without validation.

Common misconceptions

“RAG removes hallucination.”

It reduces it when retrieval succeeds; the model can still misread or ignore the passages, and retrieval can fail.

“More retrieved chunks always help.”

They add cost and distraction; measure accuracy against k.

“Chunk size is a detail.”

It is one of the largest levers on recall; tune it on your questions.

“If the final answer is wrong, the model is bad.”

First check whether the gold passage was in the prompt; often retrieval is the problem.

“The index is a one-time job.”

Documents change; an index without a refresh process becomes wrong.

Engineering takeaways

  1. RAG supplies relevant passages at question time: chunk and index offline; retrieve, assemble a delimited prompt and generate online.
  2. Chunks needed: ⌈(n − s)/(s − o)⌉ + 1; overlap keeps boundary facts whole at the price of a larger index.
  3. Recall@k and MRR evaluate retrieval; end-to-end accuracy = r·a + (1 − r)·b separates retrieval from generation.
  4. Design for unanswerable questions: threshold or explicit refusal, citations that are verified, and quote checks.
  5. Index freshness, permission filtering and hostile documents are operational risks that need owners and tests.

References and further reading

The numbers were computed by the code in this lesson. The cost estimate of 1.3 tokens per word is a rule of thumb for English text and depends on the tokenizer (Module 2).

  • Lewis, P. et al. (2020). Retrieval-augmented generation for knowledge-intensive NLP tasks. arXiv:2005.11401.
  • Karpukhin, V. et al. (2020). Dense passage retrieval for open-domain question answering. arXiv:2004.04906.
  • Greshake, K. et al. (2023). Not what you have signed up for: Compromising real-world LLM-integrated applications with indirect prompt injection. arXiv:2302.12173.
  • Lewis et al. (2020), RAG (research paper)

Knowledge check

Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

A 600-word document is cut into chunks of 150 words with an overlap of 30. How many chunks are there?

Explanation

⌈(600 − 150)/(150 − 30)⌉ + 1 = ⌈450/120⌉ + 1 = ⌈3.75⌉ + 1 = 5 chunks.

Question 2

Recall@3 of a retriever is 0.8, the generator is correct 90% of the time when the gold passage is present and 10% when it is absent. What is the end-to-end accuracy?

Explanation

0.8 × 0.9 + 0.2 × 0.1 = 0.72 + 0.02 = 0.74.

Question 3

Users report that answers are wrong. How do you find out whether the retriever or the generator is responsible?

Explanation

On a set of questions with known gold passages, check whether the gold passage is in the top k (retrieval), and separately give the generator the gold passage directly and measure its accuracy (generation). Low recall points to retrieval, low accuracy with the gold passage points to the prompt or the model.

What’s next

Next: tool calling and schema validation: letting a model request actions while the application stays in control.

Practice this lesson

Chunking and Retrieval

Chunk documents, build TF-IDF vectors and retrieve the best passages for a question.

Next lesson

LM11-T4 — Tool Calling and Schema Validation

Let a model request tools through a validated schema and keep the application in control.

Nothing below is required to finish this lesson.