M07 · Retrieval-augmented generation (RAG)07-1033 min read
Lesson 50 of 106 · Module 8 of 14 · Week 4
Threads:The measurement threadThe weights threadThe infrastructure threadThe control thread
Debugging RAG: retrieval failure versus generation failure
When a RAG answer is wrong, the first and only question worth asking is whether the correct passage was in the context the model received. If it was not, you have a retrieval failure and the fix is upstream in chunking, embedding, or ranking. If it was, you have a generation failure and the fix is in assembly, prompting, or grounding. Asking this before anything else resolves roughly half of all RAG debugging time, because it eliminates the entire wrong half of the pipeline in a single step.
What the retrieval-versus-generation split is
It is a binary partition of every possible RAG failure, drawn at the context boundary:
a wrong answer
│
┌─────────────────┴─────────────────┐
│ │
Was the correct passage IN THE CONTEXT the model received?
│ │
NO YES
│ │
RETRIEVAL FAILURE GENERATION FAILURE
the model never had it the model had it and misused it
│ │
stages 1–7: stages 8–9:
parse, chunk, embed, assemble, prompt,
index, query-prep, ground, cite
retrieve, rerank │
│ │
fix: 06-01, 06-02, 07-02, fix: 07-08, 07-11,
07-04, 07-06, 07-07 05-02, 09-12
Why the boundary is drawn at the context and not somewhere else. The context window is the only place in the pipeline where you can make a clean, verifiable statement about what the model knew. Everything before it is about getting evidence; everything after is about using it. That makes the boundary a genuine information barrier: no upstream fix can change how the model used a passage it received, and no downstream fix can conjure a passage that was never retrieved.
The two failure classes have disjoint fixes, and that is the whole payoff. This is not a taxonomy for its own sake. Consider the wasted work each misdiagnosis causes:
| Misdiagnosis | What the team does | Why it cannot work |
|---|---|---|
| Retrieval failure diagnosed as generation failure | Rewrites prompts, switches to a bigger LLM, adds chain-of-thought | The passage is not in the context. No prompt can make the model read text it was not given |
| Generation failure diagnosed as retrieval failure | Deploys a vector database, changes the embedding model, adds hybrid search | The passage was already at rank 1. Better retrieval returns the same passage |
Both are common. The second is more expensive, because changing the embedding model means re-embedding the corpus (12-12).
A third class exists and must be named to keep the binary honest: the answer may be unavailable because the information is not in the corpus at all. That is neither a retrieval nor a generation failure — it is an ingestion or curation failure (06-01, 08-01), and in some cases the correct response is that the question is unanswerable and the system should say so (07-11). Folding it into "retrieval failure" is a mistake, because it sends you tuning a retriever over a corpus that does not contain the answer.
How to run the diagnosis, step by step
L1 — The intuition you can carry into an exam
Read the prompt the model actually received. Is the answer in there? If no, fix retrieval. If yes, fix generation.
Three facts to carry:
- The first question is always "was the correct passage in the context?"
- Retrieval and generation failures have disjoint fixes. Diagnose before intervening.
- You cannot run this diagnosis without logging the assembled context. That log is the prerequisite for all of it.
L2 — The four-question decision tree
The full procedure is four questions, each one narrowing further. Run them in order; do not skip ahead.
Question 1: Is the answer in the corpus at all?
Search the corpus directly — by keyword, by grep, by whatever crude means you have — for the fact the answer needed.
| Finding | Class | Where to look |
|---|---|---|
| The fact is nowhere in the indexed text | Ingestion failure | 06-01 parsing, 08-01 curation. The document may exist but have parsed to nothing |
| The fact is in the corpus but mangled — a table turned into interleaved words, a scanned page with no text layer | Ingestion failure | 06-01. This is the silent-failure case |
| The fact is in the corpus, intact | continue to Q2 |
This question first, because it is the cheapest and because everything downstream is meaningless if it fails. It is also the question people never ask, which is why a parsing defect can survive months of retrieval tuning.
Question 2: Was the correct chunk in the retrieved candidate pool?
Retrieve the top-50 for the failing query — deeper than production k — and search the list for the chunk containing the answer.
| Finding | Class | Where to look |
|---|---|---|
| Absent from the top-50 | Retrieval / recall failure | See the sub-tree below |
| Present at rank 4–50 | Ranking / precision failure | 07-07 reranking, or fusion depth (07-06) |
| Present at rank 1–3 | continue to Q3 |
If it is absent from the top-50, drill one level:
| Sub-check | Likely cause | Fix |
|---|---|---|
| Does the chunk contain the whole answer, or was it split across a boundary? | chunking | 06-02 |
| Is the chunk enormous, so its vector averages many topics? | chunking | 06-02 |
| Does self-retrieval on that chunk's own text return it at rank 1, similarity ~1.0? | encoder mismatch | 07-02 — the single highest-value check |
| Does BM25 alone find it? | dense-only blind spot — identifiers, unseen vocabulary | add the sparse leg (07-06) |
| Does dense alone find it? | sparse-only blind spot — synonyms, paraphrase | add the dense leg (07-06) |
| Is it excluded by a metadata filter? | over-restrictive filter — status, date, or ACL | 07-03, 07-05 |
| Is it in the index at all? | index staleness or a failed insert | 12-12 |
| Does exact brute-force search find it when ANN does not? | index recall too low | raise ef_search / nprobe (07-04) |
Question 3: Was the correct chunk in the assembled context?
Present in the top-3 does not mean present in the prompt. Read the logged context.
| Finding | Class | Where to look |
|---|---|---|
| Absent — dropped by a relevance floor | threshold too strict | 07-08 |
| Absent — dropped by a token budget | context overflow or truncation | 07-08, 04-06 |
| Absent — dropped by post-retrieval filtering | filter applied in the wrong place | 07-05 |
| Present, but buried mid-context among many chunks | assembly failure | 07-08 — lost-in-the-middle |
| Present, near the start or end | continue to Q4 |
Question 4: The passage was in the context and the answer is still wrong. Which generation failure is it?
| Symptom | Failure | Fix |
|---|---|---|
| The answer contradicts the context | unfaithful generation | grounding instruction, citation requirement (07-11) |
| The answer adds facts not in the context | hallucination on top of grounding | require quoted support; constrain (09-12, 07-11) |
| The answer uses the model's parametric knowledge instead of the context | instruction failure | explicit "use only the provided sources" (05-02) |
| The answer is right but omits part of a multi-part question | partial use — often a mid-context chunk | edge-load (07-08) |
| The answer picks the wrong one of two contradictory chunks | precedence not specified | render dates, instruct precedence (07-03, 07-08) |
| The answer inverts polarity — says supported when the context says not supported | negation | reranking helps; require quoted support (07-03, 07-11) |
| The answer says it does not know when the context contains the answer | over-conservative instruction | soften the decline threshold (07-11) |
| The citation points at a source that does not contain the claim | citation fabrication | validate citations programmatically (07-11) |
| The answer is truncated mid-sentence | no output budget reserved | 07-08, 02-03 |
L3 — What makes the diagnosis possible, and what makes it impossible
The procedure above is trivial to run if you have one artefact and impossible without it: the logged assembled context. Not the retrieved ids, not the scores — the actual text the model received, or at minimum the ordered list of chunk ids with their scores plus a way to reconstruct the text.
The minimum viable trace, per request:
request_id
query_raw the user's text
query_prepared after rewriting (12-11)
filter_predicate the entitlement + status filter applied (07-05)
sparse_top_n [(chunk_id, bm25_score, rank), ...]
dense_top_n [(chunk_id, cosine, rank), ...]
fused [(chunk_id, rrf_score, rank), ...]
reranked [(chunk_id, rerank_score, rank), ...]
assembled [chunk_id in prompt order]
prompt_tokens the count actually sent
answer the generated text
citations [claimed source ids]
Every field earns its place. Without sparse_top_n and dense_top_n separately you cannot tell which leg failed. Without filter_predicate you cannot see that an over-restrictive status filter excluded the answer. Without assembled in prompt order you cannot diagnose lost-in-the-middle. Without prompt_tokens you cannot see truncation.
One caution that is not optional. 07-05 established that retrieved chunk bodies may be restricted content, and that logs are a leak site. The resolution: log ids, scores, ranks, and predicates always; log chunk bodies only in environments whose corpus contains nothing restricted. In production, reconstruct the text from ids at debug time, under the same authorisation as any other read.
Why the order of questions is not arbitrary. Each question is chosen to eliminate the largest amount of pipeline per unit of effort, and to run before any question whose answer it could invalidate:
| Question | Cost to answer | Pipeline eliminated if it fails |
|---|---|---|
| Q1 in corpus? | seconds — grep | stages 3–9 all become irrelevant |
| Q2 in top-50? | one query | either stages 1–6 or stages 7–9 |
| Q3 in context? | read the log | assembly versus everything upstream |
| Q4 which generation failure? | read the answer against the context | narrows to one prompt fix |
Asking Q4 first — "let me improve the prompt" — is the intuitive move and the wasteful one, because it is the question that eliminates the least.
Retrieval failure vs generation failure vs ingestion failure vs an unanswerable question
| Ingestion failure | Retrieval failure | Ranking failure | Assembly failure | Generation failure | Unanswerable | |
|---|---|---|---|---|---|---|
| Correct passage exists in corpus | no, or mangled | yes | yes | yes | yes | no |
| In the top-50 candidates | n/a | no | yes, ranked low | yes | yes | n/a |
| In the assembled context | no | no | usually no | present but poorly placed, or dropped | yes | no |
| Model could have answered | no | no | no | maybe | yes | no |
| Typical symptom | "we know that document exists but the system never uses it" | "it never finds this" | "it finds it eventually but not in the top 3" | "sometimes right, sometimes not" | "it had the answer and said something else" | "it makes something up" |
| Fix lives in | 06-01, 08-01 | 06-02, 07-02, 07-04, 07-06 | 07-07, 07-06 | 07-08 | 07-11, 05-02 | 07-11 — decline |
| Silent? | yes — completely | no, if you look at the pool | no, if you look | partly | no — visible in the answer | no |
| Cost of misdiagnosing it | months of retrieval tuning over a corpus missing the data | prompt engineering that cannot work | a vector-database migration that changes nothing | same | re-embedding the corpus for nothing | a hallucination shipped as an answer |
Three readings that exam items probe.
Ranking failure is a sub-case of retrieval failure with a completely different fix. Both mean "the passage was not in the context", but "absent from the top-50" and "at rank 12 of 50" point at opposite ends of the retrieval stack — recall interventions versus precision interventions (07-06 versus 07-07). Collapsing them is the most common way this diagnosis goes half-right.
Ingestion failure is the only fully silent class, and it is the one whose misdiagnosis is most expensive. A parser that dropped every table produces a system that retrieves fluently, ranks well, assembles cleanly, and answers wrongly on every table-derived question — and every metric downstream of parsing looks healthy.
"Unanswerable" is a correct outcome, not a failure. If the corpus genuinely does not contain the answer, the right behaviour is a decline (07-11). A system that hallucinates instead has a generation failure on top of an unanswerable question, and the fix is the decline path, not better retrieval.
Worked example: five failing queries, five different diagnoses
This is a constructed diagnostic walkthrough with invented values, built so each decision point is inspectable. The scores and counts are illustrative, not measured.
The system is the nine-stage pipeline from 07-09: 12,000 chunks, hybrid retrieval, cross-encoder reranking with a 0.5 floor, edge-loaded assembly of up to 3 chunks. Five user complaints arrive. Same symptom class — "the answer was wrong" — five different stages.
Case 1 — "It never finds anything about the licence renewal process"
Q1: is it in the corpus? Grep the indexed text for "licence renewal". Zero hits. The source document exists — a scanned PDF of the licensing agreement.
Parse log for licence-agreement.pdf:
extracted characters: 41
source estimate: ~38,000
assertion (0.8×–1.2×): FAILED — flagged, but nobody read the flag
Diagnosis: ingestion failure. The PDF is a scan with no text layer, so parsing yielded 41 characters of metadata and nothing else. Fix: OCR at the parse stage (06-01). Diagnosis time: ninety seconds, entirely because the assertion existed.
Note the counterfactual. Without that parse assertion the team would have seen a retrieval symptom, added hybrid search, changed the embedding model, and deployed a vector database — none of which can retrieve text that was never extracted.
Case 2 — "Asking about the 60-day international travel window returns nothing useful"
Q1: in the corpus? Yes — the sentence is present, intact.
Q2: in the top-50? No. Absent entirely.
Drill down:
Self-retrieval on that chunk's own text → rank 1, similarity 0.997 ✓ encoder fine
BM25 alone on "international travel expense window" → rank 34 ← found, barely
Dense alone → not in top-50
Chunk length → 1,940 tokens ← the problem
The chunk is 1,940 tokens covering the entire expenses policy — domestic windows, international windows, receipt rules, currency conversion, approval chains. Its single vector is the average of six topics, so it is moderately similar to everything and strongly similar to nothing. BM25 found it weakly on term overlap; the dense leg could not, because its vector does not point at "international travel" specifically.
Diagnosis: retrieval failure caused by chunking (06-02). Fix: re-chunk at ~400 tokens with overlap. After re-chunking, the international-travel paragraph becomes its own chunk and appears at dense rank 2.
The instructive part: this looked like an embedding-model problem and was a chunking problem. The self-retrieval check exonerated the encoder in one call, which is what made the next hypothesis cheap to reach.
Case 3 — "It knows about certificate rotation but the restart step comes out fourth"
Q1: in the corpus. Q2: in the top-50 — at fused rank 4.
Fused ranking: c8801 (0.0325) > c5512 (0.0323) > c2170 (0.0161) > c6033 (0.0159)
↑ the answer
Assembled (k=3): c8801, c5512, c2170
→ c6033 never entered the context
Q3: in the assembled context? No — cut by k=3.
Diagnosis: ranking failure (07-07). The chunk was retrieved and then discarded by the context cutoff. It fused to rank 4 because only the dense leg found it, and RRF rewards mutual presence over single-leg conviction (07-06).
Fix: add the cross-encoder reranker. It scores c6033 at 0.93 — highest of the pool — promoting it to rank 1. No retrieval change was needed at all; the passage was always there.
This is precisely the case where a team without the diagnosis reaches for the embedding model, re-embeds 12,000 chunks, and finds the ranking unchanged — because the retrieval was never the problem.
Case 4 — "Sometimes it gets the international travel exception, sometimes it doesn't"
Q1: in the corpus. Q2: top-50, rank 2. Q3: in the assembled context? Yes — but look at where.
Assembled context, naive rank order, k=8:
position 1 e1 30-day rule ← strong position
position 2 e2 60-day intl exception ← being pushed toward the middle
position 3 e3 review timeline
position 4 e4 portal location
position 5 e6 currency conversion
position 6 e7 receipt requirements
position 7 e8 approval chain
position 8 e5 revision history ← the WEAKEST chunk in the strongest end position
Diagnosis: assembly failure (07-08). Both answer-bearing chunks were present. e1 at position 1 is used reliably, which is why the 30-day answer is always right. e2 at position 2 of 8 sits in the region the model attends to least reliably — the lost-in-the-middle U-curve — which is why the exception appears intermittently. Meanwhile a revision-history line occupies the strong final position.
The intermittency is the tell. A failure that appears and disappears for the same question, with the same retrieval, is a positional or attention effect, not a retrieval one.
Fix, in two lines of assembly code: apply the 0.5 relevance floor (dropping e4, e5, and others) and edge-load — e1 first, e2 last. Chunk tokens fall, and both answer-bearing chunks land in strong positions.
Case 5 — "It told a customer to disable certificate verification"
Q1: in the corpus — an archived forum post says exactly that. Q2: top-50, rank 1. Q3: in the assembled context, position 1.
Q4: which generation failure?
Context position 1: archived forum post — "just turned off cert verification, worked for me"
Context position 3: official spec — "the server MUST abort the handshake …"
Answer: "Disable certificate verification on the client."
Citations: none — no citation instruction was in the prompt
Diagnosis: this is not primarily a generation failure. The model faithfully used its highest-ranked source. It did exactly what it was told. The failure is upstream, in two places:
- Authority blindness (
07-03). The forum post ranked first because it matches the query's register — first-person, symptomatic, informal — while the specification is written like a specification. Fix: trust-tier metadata at ingestion and a filter or boost on it; ideally, do not index content you would not cite. - No citation requirement (
07-11). With citations, a reader would see the answer came from an archived forum post and could reject it. Without them, a dangerous answer is indistinguishable from a good one.
There is a genuine generation-side fix too — instruct the model to prefer authoritative sources and to flag when its best source is community content — but the primary defect is that the corpus offered a bad answer at rank 1.
This case is why the diagnosis has four questions rather than two. A crude retrieval-versus-generation split would land on "generation failure: the model said something dangerous", and the prompt fix would be a patch over a corpus problem.
Reading the five cases together
| Case | Symptom | Q1 in corpus | Q2 in top-50 | Q3 in context | Diagnosis | Fix | Lesson |
|---|---|---|---|---|---|---|---|
| 1 | never found | no | — | — | ingestion | OCR at parse | 06-01 |
| 2 | never found | yes | no | — | retrieval / chunking | re-chunk to ~400 tokens | 06-02 |
| 3 | found but not used | yes | rank 4 | no | ranking | add reranking | 07-07 |
| 4 | intermittent | yes | rank 2 | mid-context | assembly | floor + edge-load | 07-08 |
| 5 | dangerous answer | yes | rank 1 | position 1 | authority + no citations | trust tiers + citations | 07-03, 07-11 |
Five identical-sounding complaints, five stages, five disjoint fixes. A team that responds to all five by changing the embedding model fixes none of them and re-embeds the corpus five times. The diagnosis costs minutes per case; the misdiagnosis costs weeks.
Decision table: symptom to stage
Use this as a lookup once you have run the four questions — it maps observations onto the stage to inspect.
| Observation | Most likely stage | Fix | Lesson |
|---|---|---|---|
| A document you know exists is never used, for any phrasing | parse | check extracted length; OCR; structured extraction | 06-01 |
| A table's contents are never retrievable | parse | table-aware extraction | 06-01 |
| Answers exist in long documents but are never found specifically | chunk | reduce chunk size | 06-02 |
| An answer spanning two paragraphs is never retrieved whole | chunk | increase overlap; semantic chunking | 06-02 |
| A footer or nav block is returned for every query | dedup | strip boilerplate before indexing | 06-04 |
| All similarities cluster in a narrow band; nothing is distinctive | dedup / chunk | boilerplate dominating chunk text | 06-04 |
| Confident, fluent, irrelevant results with normal-looking scores | embed | self-retrieval assertion; pin model, version, prefix, pooling | 07-02 |
| Exact error codes and part numbers are never found | retrieve | add the sparse leg | 07-01, 07-06 |
| Paraphrased questions are never matched | retrieve | add the dense leg | 07-02, 07-06 |
| Brute-force search finds it but the index does not | index | raise ef_search / nprobe; measure index recall | 07-04 |
Filtered queries return fewer results than k | index / filter | pre-filter instead of post-filter | 07-04, 07-05 |
| The correct chunk is at rank 4–50 | rerank | add or deepen reranking | 07-07 |
| The best single-leg hit is buried mid-ranking | fuse | deeper fusion pool, then rerank | 07-06, 07-07 |
| Superseded documents are cited | query prep / metadata | status filter; do not index superseded content | 07-03 |
| Community content outranks official docs | metadata | trust tiers; filter or boost | 07-03 |
| Users see documents they cannot access | query prep | pre-filter in every leg | 07-05 |
| Intermittent correctness on the same question | assemble | edge-load; reduce k | 07-08 |
| Multi-part questions answered partially | assemble | edge-load; ensure one chunk per part | 07-08 |
| Answers truncated mid-sentence | assemble | reserve output budget | 07-08, 02-03 |
| Answer contradicts the provided context | generate | grounding instruction; require quoted support | 07-11 |
| Answer adds facts not in the context | generate | citation requirement; constrain | 07-11, 09-12 |
| Citations point at sources lacking the claim | generate | validate citations programmatically | 07-11 |
| Model declines when the answer is present | generate | soften the decline threshold | 07-11 |
| Cost or latency far above forecast | assemble | count tokens; k is a multiplier | 07-08, 12-09, 12-10 |
Two entries in that table are worth memorising as signatures because they identify their stage almost uniquely:
"Confident, fluent, irrelevant, normal-looking scores, no errors" is the embedding-mismatch signature. Nothing else in the pipeline produces exactly that combination, and the self-retrieval assertion confirms or excludes it in one call.
"Intermittently correct for the same question with the same retrieval" is the assembly signature. Retrieval failures are consistent — the chunk is either found or it is not. Positional and attention effects are probabilistic, so intermittency points at assembly rather than upstream.
Why debugging RAG by failure attribution is on the NCA-GENL exam
The official job-role frame describes an associate who implements testing and debugging, performs system analysis against specifications, and assesses and resolves performance issues [OFFICIAL]. That is a description of this lesson. The objectives it serves:
- 1.3 / 4.2 — Build LLM use cases such as RAG, chatbots, and summarizers. Building includes debugging.
- 4.5 — Monitor functioning of data collection, experiments, and other software processes
[OFFICIAL]. The per-request trace is that monitoring. - 4.1 / 1.1 — Assist in deployment and evaluation of model scalability, performance, and reliability under supervision of senior team members
[OFFICIAL]. Attribution is what makes an evaluation actionable. - 1.4 — Curate and embed content datasets for RAGs. Half of all diagnoses land in curation.
- 1.9 — Use prompt engineering principles to create prompts to achieve desired results. Generation-side fixes are prompt fixes.
- Derived Experimentation scope — separating retrieval quality from generation quality is exactly the RAG evaluation decomposition the course index names, and
09-07owns the metrics.
The exam is scenario-heavy and calibrated to correct practice and tool choice rather than deep technical derivation [FIELD]. Debugging scenarios are the ideal vehicle for that: a symptom is described, four plausible interventions are offered, and the keyed answer is the one matching the stage the symptom implicates. These are among the most answerable questions on the exam if you know the symptom-to-stage map, and among the most guessable-wrong if you do not.
Question phrasings you should recognise:
| Phrasing | Testing | Answer shape |
|---|---|---|
| "A RAG answer is incorrect. What should be determined first?" | the primary split | whether the correct passage was retrieved into the context |
| "The retrieved context contains the answer but the response contradicts it. What kind of failure?" | generation | generation / faithfulness failure — fix grounding and citations |
| "The correct document is never retrieved regardless of phrasing. Where should the team look?" | retrieval / ingestion | parsing, chunking, embedding consistency, index freshness |
| "Results are fluent and confidently irrelevant with no errors. What is the likely cause?" | the embedding signature | inconsistent query/passage encoding |
| "The right passage is retrieved at rank 9 but only 3 chunks are used. What should be added?" | ranking | a reranking stage |
| "The same question is answered correctly some of the time. What does this suggest?" | the assembly signature | positional effects — the passage is mid-context |
| "Why measure retrieval quality separately from generation quality?" | decomposition | so a failure can be attributed to a stage and the right fix chosen |
| "A known document produces no retrievable text. Which stage failed?" | ingestion | parsing |
| "What single artefact makes RAG debugging possible?" | the trace | the logged assembled context |
Distractor families:
- "Use a larger LLM." The default wrong answer for retrieval failures. A larger model cannot read a passage it was not given.
- "Change the embedding model." The default wrong answer for generation and ranking failures, and the most expensive one because it forces a full re-embed (
12-12). - "Increase
k." Superficially addresses recall, frequently worsens assembly (07-08). - "Add hybrid search" offered for recency, authority, permissions, or ranking problems. It addresses lexical/semantic coverage only (
07-06). - "Fine-tune the model on the documents." Does not add retrievable facts reliably, removes provenance, and cannot be filtered per user (
11-08,13-05). - "Lower the similarity threshold." The wrong passage often scored high (
07-03). - Prompt engineering offered for a retrieval failure, and retrieval changes offered for a generation failure. The two canonical inversions.
- "Check the logs" where no context trace is logged. The distractor is plausible precisely because most systems cannot do it.
Common mistakes when debugging RAG
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Weeks of tuning with no improvement | No attribution — the team fixed the interesting stage, not the broken one | Run the four questions before changing anything |
| 2 | The diagnosis cannot be run at all | The assembled context is not logged | Log query, filter, per-leg candidates, fused, reranked, assembled order, token count, answer, citations |
| 3 | Retrieval judged "fine" because end-to-end scores are acceptable | Aggregate metrics hide stage-level failures | Measure recall and faithfulness separately (09-07) |
| 4 | An embedding-mismatch failure survives for months | No self-retrieval assertion | One embed, one search, in CI (07-02) |
| 5 | A parsing failure diagnosed as a retrieval failure | Parsing is silent and nobody checked the corpus first | Q1 always comes first: is it in the corpus? |
| 6 | "Absent from top-50" and "rank 12 of 50" treated as one problem | Recall and precision conflated | They have opposite fixes — 07-06 versus 07-07 |
| 7 | Four fixes shipped together; nobody knows which worked | No isolation | One change at a time against a frozen eval set (03-04) |
| 8 | An intermittent failure dismissed as model non-determinism | Positional effects mistaken for sampling randomness | Intermittency with fixed retrieval points at assembly (07-08, 09-11) |
| 9 | A single anecdote drives an architecture change | One query is not evidence | Add the failing query to the eval set, then measure (01-08, 09-09) |
| 10 | Restricted chunk text ends up in debug logs | Bodies logged for debuggability | Log ids and scores; reconstruct text under authorisation (07-05) |
| 11 | The fix works for the reported query and breaks two others | No regression suite | Every diagnosed failure becomes a permanent eval item (10-04) |
| 12 | A hallucination treated as a generation bug when the corpus lacked the answer | The unanswerable case was not distinguished | Add a decline path and count declines as correct when appropriate (07-11) |
Mistake 11 is the one that converts debugging from firefighting into progress. Every diagnosed failure should end as a new item in the evaluation set, labelled with the correct chunk id. Over a few months that turns a 20-item hand-written eval set (01-08) into a hundred-item suite grown from real failures (09-01) — and it means a fix that regresses an old failure fails the build instead of a customer (10-04).
Mistake 9 deserves a note too. A single wrong answer is a hypothesis generator, not evidence. Users report individual failures; the honest response is to reproduce it, diagnose it, add it to the eval set, and then measure whether the proposed fix helps the set rather than the anecdote. A change that fixes one query and breaks three is a regression that felt like a success.
Why does asking "was the passage retrieved?" first save so much time?
Because it eliminates roughly half the pipeline with one cheap observation, and because the two halves have no fixes in common.
Count the stages. The nine-stage pipeline (07-09) splits into stages 1–7 (getting evidence) and stages 8–9 (using it). A single look at the logged context tells you which half to ignore. No other question in RAG debugging has that yield:
| Question asked first | Stages eliminated by the answer | Cost |
|---|---|---|
| Was the passage in the context? | ~7 of 9, either way | read one log line |
| Is the prompt good enough? | 0 | hours of iteration |
| Is the embedding model good enough? | 0 | a full re-embed to find out |
| Is the vector database fast enough? | 0 | a migration |
| Is the LLM strong enough? | 0 | a model swap and re-evaluation |
The asymmetry is not subtle. And the disjointness of the fixes is what makes it consequential rather than merely efficient: work done on the wrong half is not partial credit, it is zero. Prompt engineering applied to a retrieval failure yields nothing, however good the prompt gets, because the text is not there. A new embedding model applied to a generation failure yields nothing, because the same passage arrives at rank 1 and the model misuses it the same way.
There is a second-order benefit worth naming: the question is cheap to answer correctly, which means it is hard to get wrong. Judging whether a prompt is good enough is a matter of taste; judging whether a specific sentence appears in a logged context is a matter of fact. Diagnostic steps that produce facts should always precede ones that produce opinions.
And a third: it makes the conversation concrete. "RAG isn't working well" cannot be assigned to anyone. "For query X, chunk c6033 was retrieved at fused rank 4 and cut by k=3" has an owner, a fix, and a test. That translation is most of what makes a RAG system operable.
How do I tell an unfaithful answer from a genuine retrieval miss?
By reading the context and the answer side by side and asking whether every claim in the answer is supported by something in the context. This is the faithfulness (or groundedness) check, and it is the generation-side counterpart to retrieval recall.
| What you observe | Class | Fix |
|---|---|---|
| The answer's claims are all in the context, but the context lacks what the user asked | retrieval miss — the model was faithful to insufficient evidence | fix retrieval; the generation was correct behaviour |
| The answer's claims are not in the context | unfaithful generation | grounding instruction, quoted support, citation validation (07-11) |
| Some claims are supported, others are not | partial hallucination — usually the model filling a gap from parametric knowledge | require per-claim citation; instruct explicitly to use only provided sources |
| The answer says it does not know, and the context truly lacks the answer | correct behaviour | this is a success, and should be scored as one |
| The answer says it does not know, and the context contains the answer | over-conservative generation | soften the decline threshold; check whether the passage was mid-context (07-08) |
The distinction that catches people: a model that faithfully answers from bad evidence is not hallucinating. It did its job. Scoring it as a generation failure sends you to fix the prompt when the corpus or the retriever is at fault. This is one reason RAG evaluation must report retrieval and generation metrics separately rather than as one quality number (09-07).
Three checks that make this mechanical rather than impressionistic:
Per-claim citation. Instruct the model to attach a source id to every factual claim (07-11). Then an unsupported claim is visibly unsupported — it has no citation, or a citation you can check.
Programmatic citation validation. For each cited id, verify the claim's key terms actually appear in that chunk. This is crude and catches the worst cases: citations pointing at chunks that do not contain the claim. It is cheap enough to run on every response in a test suite.
LLM-as-a-judge for faithfulness, with its biases understood (09-10). Give a judge model the context and the answer and ask whether every claim is supported. Useful at scale, unreliable enough that you should not trust it alone — and note this is a different use of an LLM than reranking (07-07).
Grounding via retrieval and citation of sources are named hallucination controls [NVIDIA-DOC], and NeMo Guardrails is the NVIDIA instrument for keeping applications accurate, appropriate, on-topic, and secure — 13-02 covers it and 09-12 covers hallucination types. The point for this lesson is narrower: without citations you cannot run this diagnosis at all, because you have no way to check which claim came from which source.
What should a RAG debugging session actually look like?
Thirty minutes, five steps, one artefact at the end. Concretely:
Step 1 — Reproduce with the trace (5 min). Re-run the failing query and capture the full trace: prepared query, filter predicate, per-leg candidates with scores, fused ranking, reranked ranking, assembled order, token count, answer, citations. If you cannot capture this, stop and build the logging — everything else is speculation.
Step 2 — Run the four questions (5 min). In corpus? In top-50? In context? Which generation failure? Write down the answer to each; do not skip forward because you have a hunch. The hunch is usually the interesting stage, not the broken one.
Step 3 — Name the stage (1 min). One stage, by name, from the nine. If you cannot name one, you have not finished step 2. "Retrieval is bad" is not a stage; "chunking produced a 1,940-token chunk whose vector averages six topics" is.
Step 4 — Add the query to the eval set (5 min). With the correct chunk id labelled. This is the step that makes the session cumulative instead of disposable. Do it before the fix, so you have a failing test.
Step 5 — Fix one thing and re-measure (10 min plus). The single intervention that matches the named stage. Re-run the whole eval set, not just the failing query, because a fix that helps one query and breaks three is a regression (03-04).
What you have at the end, and this is the point: a named stage, a permanent test, a measured before-and-after, and a one-line explanation someone else can read. Compare that to the alternative session, which ends with "I changed some things and it seems better."
Two habits that compound over a project:
Keep the four-configuration table current. Sparse alone, dense alone, hybrid, hybrid plus rerank, each with recall@3, recall@10, and MRR on the frozen set (07-01, 07-06, 07-07). It answers most architecture questions before they are asked, and it stops the recurring proposal to remove a component nobody can defend.
Put the assertions in CI (10-04). Self-retrieval similarity, parse-length ratios, maximum chunk length, retrieval recall thresholds, and faithfulness on the eval set. A CI gate that fails the build catches the silent stages; a dashboard does not, because nobody reads a dashboard on a Tuesday. That gate is the week-5 deliverable of this course, and it is the difference between a system you operate and one you hope about.
Glossary recap: the terms this lesson introduced
| Term | Definition |
|---|---|
| Retrieval failure | The correct passage was not in the context the model received; the fix is upstream of generation |
| Generation failure | The correct passage was in the context and the model produced a wrong answer anyway |
| Ingestion failure | The information is absent or mangled in the indexed corpus — the only fully silent failure class |
| Ranking failure | The correct passage was retrieved but ordered below the context cutoff — a retrieval failure with a precision fix |
| Assembly failure | The correct passage entered the context but was placed or budgeted so the model under-used it |
| Failure attribution | Assigning an observed failure to one named pipeline stage before choosing a fix |
| The four-question tree | In corpus? → In top-50? → In context? → Which generation failure? |
| The trace | The per-request log of query, filter, per-leg candidates, fused and reranked rankings, assembled order, tokens, answer, and citations |
| Self-retrieval assertion | Embedding a known chunk's own text as a query and requiring it back at rank 1 near 1.0 — the embedding-stage check |
| Embedding-mismatch signature | Confident, fluent, irrelevant results with normal-looking scores and no errors |
| Assembly signature | Intermittent correctness on the same query with unchanged retrieval |
| Faithfulness / groundedness | Whether every claim in the answer is supported by the provided context |
| Faithful to insufficient evidence | A correct generation over a bad retrieval — not a hallucination, and misdiagnosing it sends you to the wrong stage |
| Regression suite growth | Turning every diagnosed failure into a permanent labelled eval item |
Key takeaways on debugging RAG
- Ask one question first: was the correct passage in the context the model received? It eliminates roughly seven of nine stages either way, and it costs one log line.
- Retrieval and generation failures have disjoint fixes. Work on the wrong half is not partial credit; it is zero.
- Run four questions in order: is it in the corpus, is it in the top-50, is it in the assembled context, and which generation failure is it. Each eliminates more than the next.
- The worked example's headline result: five identical-sounding complaints diagnosed to five different stages — parse, chunk, rank, assemble, and corpus authority — with five disjoint fixes. Changing the embedding model would have fixed none of them.
- Two signatures worth memorising. Confident-fluent-irrelevant with normal scores and no errors is embedding mismatch. Intermittent correctness with unchanged retrieval is assembly.
- Ingestion failure is the only fully silent class, and Q1 exists to catch it before you tune anything downstream.
- "Absent from the top-50" and "rank 12 of 50" are different problems with opposite fixes — recall interventions versus reranking.
- A model that faithfully answers from bad evidence is not hallucinating. Scoring it as a generation failure sends you to the wrong stage.
- You cannot run this diagnosis without the trace. Log ids, scores, ranks, filter predicates, assembled order, and token counts — bodies only where nothing is restricted (
07-05). - Every diagnosed failure becomes a permanent eval item. That is what makes debugging cumulative rather than disposable.
Next: grounding, citations, and letting a model say "I don't know"
Several diagnoses in this lesson ended at the same place: the model produced a claim, and there was no mechanism to check whether the context supported it. Citations are what make that checkable, and a decline path is what makes an empty retrieval produce an honest answer instead of an invented one. Next: 07-11 covers grounding and citations as hallucination controls — how to instruct a model to answer only from provided sources, how to render source ids so citations are verifiable rather than decorative, how to validate them programmatically, and why letting a model say "I don't know" is a feature that has to be deliberately designed rather than a failure to be prompted away.