05 Β· AI & LLM Systems
RAG, deeply β retrieval quality is the ceiling
RAG is the most common LLM system in production, so it's the most common deep-dive question. The differentiator isn't naming the pipeline stages β everyone can β it's knowing where each stage fails and what you'd do about it. Structure every answer as: stage β purpose β failure mode β fix.
1First principles β why RAG exists
An LLM's knowledge is (a) frozen at training time, (b) lossily compressed into weights, and (c) missing everything private to you. Three fixes exist: retrain/fine-tune (slow, expensive, still lossy), stuff everything into context (costly, capped, degrades β Β§10), or fetch just the relevant knowledge at query time and put it in context. RAG is option three.
The reframe that shows understanding: RAG converts a recall problem into a reading-comprehension problem. Models are unreliable at recalling tail facts from their weights, but very good at answering over text sitting right in front of them.
2The pipeline β every stage has a failure mode
# INGESTION (offline)
documents β parse/clean β chunk β embed β index (vector DB + keyword)
# QUERY (online)
query β [rewrite] β embed β retrieve (dense + BM25) β rerank
β assemble context β generate β [cite/verify]
Ingestion β offline
Turn PDFs/HTML/scans into clean text with structure kept.
Fails when: tables flattened row-by-row, boilerplate kept, OCR garbage β garbage everything downstream.
Split into retrievable units that each hold one thought.
Fails when: mid-sentence splits orphan facts; context-stripped chunks read as ambiguous ("it must be rotatedβ¦").
Map each chunk to a vector; semantic similarity β geometric proximity.
Fails when: model weak on domain jargon; query/passage prefixes ignored; model version changed without re-embedding.
ANN index + keyword index + metadata (source, date, permissions).
Fails when: index goes stale vs source; permission metadata missing β data-leak machine.
Query β online
Make conversational follow-ups self-contained before embedding.
Fails when: raw "what about the EU?" is embedded context-free β retrieves garbage.
Dense + BM25, merged with RRF; permission-filtered per user.
Fails when: pure dense whiffs on identifiers; pure BM25 whiffs on paraphrase; filters over-aggressive.
Cross-encoder re-scores top-50 β keep top-5β10.
Fails when: skipped β right doc is found at rank 30 but never surfaced into context.
Dedupe, order deliberately, attach citations, allow abstention.
Fails when: evidence buried mid-context (lost-in-the-middle); no "say so if insufficient" out.
Answer grounded in context, with citations.
Fails when: retrieval gaps silently filled from weights β the primary hallucination vector in RAG.
3Ingestion β the unglamorous 80%
Real-world RAG quality is mostly determined here, before any AI happens:
- Parsing: PDFs with multi-column layouts, tables, headers/footers; HTML boilerplate; scanned docs needing OCR. Garbage parsing β garbage everything. Tables are the classic killer β flattened row-by-row into text, they lose the rowβcolumn association the answer depends on.
- Cleaning & metadata: strip nav/boilerplate, keep structure (headings, section paths); attach metadata per chunk β source, title, section, date, access permissions. Metadata powers filtering ("only docs from this repo", "only this user's tenant") and citations later.
- Access control is metadata too β retrieval must respect document permissions, or RAG becomes a data-leak machine. A genuinely senior point to raise unprompted.
- Freshness: documents change. You need incremental re-indexing (hash content, re-embed changed chunks) and deletion propagation β a stale index confidently serves last quarter's policy.
4Chunking strategies β and their failure modes
Why chunk at all? (1) Embeddings compress a passage into one vector β embed a whole document and the vector becomes a blurry average, matching nothing well. (2) Context budgets force selectivity. (3) Precise chunks make citations meaningful. Chunking trades retrieval precision (small chunks) against answer completeness (large chunks).
| Strategy | How | Failure mode |
|---|---|---|
| Fixed-size | Split every N tokens, 10β20% overlap | Splits mid-sentence/mid-thought; a fact's setup lands in chunk 12, its conclusion in chunk 13 β neither retrieves well. Overlap papers over boundaries, duplicates content, and can dilute ranking |
| Recursive / separator-aware | Split on paragraphs β sentences β tokens as fallback | Better, but blind to semantic structure; a "chunk" may fuse the end of one topic and the start of another β muddy embedding |
| Structure-aware | Chunk along headings/markdown/code structure; prepend section path ("Payments > Refunds > EU") | Needs clean structure; sections vary wildly in size β usually the right default anyway |
| Semantic chunking | Split where embedding similarity between consecutive sentences drops | Costlier at ingest; thresholds are fiddly; helps most on unstructured prose |
| Code chunking | Split on function/class boundaries (AST-aware) | Naive line-splitting orphans a function body from its signature/imports β for code, structure-aware is nearly mandatory |
Cross-cutting failure modes to name
- Context-stripping: a chunk saying "it must be rotated every 90 days" retrieves poorly and reads ambiguously β what must? Fixes: prepend title/section-path to every chunk; or contextual enrichment β an LLM writes 1β2 situating sentences per chunk at ingest (Anthropic's "contextual retrieval" idea).
- Chunk-size mismatch with question type: factoid questions want small precise chunks; summary/"how does X work" questions want large ones. Fix: small-to-big retrieval β index small chunks for matching precision, but hand the LLM the parent section for completeness. This one technique resolves the central tension β a strong thing to volunteer.
- Tables/lists split across chunks β silently wrong numeric answers.
5Embeddings β what they are, precisely
An embedding model maps text β a fixed-length vector (typically 384β3072 dims) such that semantic similarity β geometric proximity. Trained contrastively: pull related pairs (questionβanswer, paraphrases) together, push unrelated apart. The result: "How do I revoke a token?" lands near "Invalidating API credentials" despite zero shared words β the thing keyword search can't do.
Operational facts an engineer is expected to know
- Similarity metrics: cosine similarity (angle between vectors β the default; magnitude-invariant), dot product (unnormalized; magnitude matters), Euclidean distance. Key detail: on normalized vectors all three give identical rankings β so the practical rule is "normalize embeddings, use cosine/dot, move on." Use the metric the embedding model was trained for.
- Asymmetric search: queries and documents are different kinds of text ("how do I reset my password" vs a policy paragraph). Good retrieval embedding models are trained for this; some require prefixes (
query: β¦/passage: β¦) β ignoring that visibly degrades retrieval. - The embedding model is a versioned dependency: vectors from different models/versions are incompatible β changing embedding models means re-embedding the entire corpus. Plan for it: store raw text + metadata so re-embedding is a batch job, not an archaeology project.
- Limitations that cause real failures: embeddings blur negation ("eligible" vs "not eligible" sit close); exact identifiers β error codes, SKUs, function names, version numbers β are where dense retrieval is weakest and keyword search is strongest. This is the setup for hybrid search.
6Vector DBs, hybrid search, and BM25
Vector DB = approximate nearest-neighbor (ANN) search + database features. Exact k-NN over millions of vectors is too slow; ANN indexes trade a little recall for orders-of-magnitude speed: HNSW β a navigable graph of neighbors, the dominant one β and IVF β cluster-then-search. The "database features" matter as much as ANN: metadata filtering combined with vector search (filter + search interact non-trivially), CRUD/upserts, namespaces/multi-tenancy.
BM25 β the classic keyword ranker (Elasticsearch/OpenSearch default): score by term overlap, weighting rare terms high (IDF), diminishing returns on repetition, normalized by doc length. Its strengths are exactly dense retrieval's weaknesses: exact matches on identifiers, error strings, names, jargon. Its weakness is exactly dense's strength: zero synonym/paraphrase understanding.
Hybrid search = run both, merge. Standard merge: Reciprocal Rank Fusion (RRF) β score each doc by Ξ£ 1/(k + rank) across result lists; rank-based, so you dodge incomparable score scales. Hybrid is the production default worth asserting: "For a corpus with error codes and API names β like production logs or docs β I'd start hybrid; pure dense search whiffs on ERR_CONN_RESET-type queries, pure BM25 whiffs on paraphrases."
7Reranking and context assembly
Reranking β the precision stage
First-stage retrieval uses a bi-encoder: query and doc embedded separately, compared geometrically β fast but coarse. A cross-encoder reranker reads query+document together through one model and scores actual relevance β far more accurate, far slower. So: retrieve top-50 cheaply β rerank β keep top-5β10. This two-stage retrieve-then-rerank design is the standard IR architecture; typical gains are large, cost is +100β300ms and a per-query model call (Cohere Rerank, BGE-reranker, or an LLM-as-reranker).
Context assembly β the neglected stage
- Dedupe and diversify β near-identical chunks waste budget (MMR if needed); order deliberately β lost-in-the-middle: put the strongest evidence near the start or end, not buried mid-context.
- Attach citation handles β label chunks
[1] source: runbook/payments.md#refundsand instruct answers to cite. Citations enable verification and discipline the model toward grounded claims. - Prompt the abstention path: "Answer only from the provided context; if it's insufficient, say so." Without an explicit out, the model fills retrieval gaps from its weights β the primary hallucination vector in RAG.
- Query-side transforms (before retrieval, worth one breath): query rewriting for conversational follow-ups ("what about staging?" β self-contained query from chat history β table stakes for chat-RAG); HyDE (embed a hypothetical answer); decomposition for multi-hop questions.
8RAG evaluation β the triad
The key move: evaluate retrieval and generation separately β end-to-end scores can't tell you which stage to fix.
Retrieval metrics (needs a golden set of query β relevant-chunk labels): recall@k β is the answer's evidence in the top-k? β the single most important number; precision@k; MRR/nDCG β is it ranked high? Cheap to compute, no LLM judge needed, and directly actionable: low recall β fix chunking/embeddings/hybrid; low precision with good recall β add reranking.
Generation metrics β the RAG triad (RAGAS/TruLens vocabulary):
Is every claim in the answer supported by the retrieved context? Catches hallucination-over-context. Usually scored by an LLM judge decomposing the answer into claims and checking each against context.
Does it actually address the question β vs a faithful-but-off-target summary?
Was the retrieved context itself on-topic? Retrieval quality, judged post-hoc.
9Common failure modes β fixes (the cheat table)
| Symptom | Likely cause | Fix |
|---|---|---|
| Right doc exists, never retrieved | Vocabulary mismatch; bad chunking; embedding weak on domain terms | Hybrid search; re-chunk with structure/context; better embedding model; query rewriting |
| Retrieved but wrong answer | Lost-in-the-middle; conflicting chunks; context-stripped chunk ambiguity | Rerank + tighter k; deliberate ordering; enriched chunks; resolve version conflicts at ingest |
| Hallucinates beyond context | No abstention path; retrieval gap silently filled from weights | Explicit "insufficient context" instruction; faithfulness eval as regression gate; citations required |
| Confidently stale answers | Index not synced with source | Incremental re-embedding pipeline; recency metadata + filters; deletion propagation |
| Great on factoids, bad on "summarize/compare" | Chunk granularity mismatch β top-k snippets can't support synthesis | Small-to-big retrieval; hierarchical summaries; route summary-type queries differently |
| Multi-hop questions fail | Single retrieval can't chain evidence | Query decomposition; iterative/agentic retrieval (retrieveβreadβretrieve again) |
| Chat follow-ups retrieve garbage | Raw follow-up ("what about the EU?") is context-free | Query rewriting from conversation history before embedding |
| Users see docs they shouldn't | Permissions not enforced at retrieval | Permission metadata filtered in the query, per requesting user β non-negotiable |
10When NOT to use RAG
Long context instead
When the working set is bounded and fits comfortably β one contract, one codebase module, one incident's logs β just put it in context: zero infra, no retrieval-recall risk, the model sees everything (no "the relevant chunk wasn't retrieved" failure class). Costs: per-request token cost and latency, and long-context degradation on complex reasoning β but prefix caching makes repeated queries over the same corpus dramatically cheaper, eroding RAG's cost argument for small corpora. Rule of thumb: bounded working set that fits β long context; large/unbounded/multi-tenant corpus, strict latency/cost budgets, or citation requirements β RAG. (Hybrid: retrieval to select documents, long context to read them whole.)
Fine-tuning instead (or alongside)
The crisp separation: RAG injects knowledge; fine-tuning shapes behavior. Fine-tune for consistent style/format/domain dialect, reliable tool-use patterns, or distilling a big model's behavior into a small cheap one at volume. Fine-tuning is a bad knowledge store: facts don't reliably stick, updates require retraining, no citations, and you now own model versioning + eval + serving. They compose β a fine-tuned model inside a RAG pipeline: behavior from weights, facts from retrieval.
No augmentation at all
General-knowledge tasks the base model already does well; transformation tasks where all needed information is in the request itself (summarize this, refactor this). Retrieval bolted onto those adds latency, cost, and a new way to inject irrelevant context.
When agentic retrieval beats classic RAG
If queries need investigation β follow references, search iteratively, decide what to look for next β a tool-using agent with a search tool outperforms one-shot retrieve-then-generate. That's the next page β and closer to how Sentinel pulls logs via MCP: targeted, tool-driven fetching rather than a pre-embedded corpus. Being able to say "my production system deliberately uses tool-based retrieval rather than embedding a corpus, because incident context is fresh, structured, and queryable" turns this from theory into your story.
11Rapid-fire interview Q&A
Q: Design a RAG system for our internal docs.
Ingestion: parse with structure, chunk by headings (~300β500 tokens) with section-path prefixes, attach source/date/permission metadata, embed + BM25-index, incremental re-index on doc changes. Query: rewrite conversational queries, hybrid retrieval (RRF) top-50, cross-encoder rerank to top-5, permission-filtered per user, assemble with citations and an abstention instruction. Eval: golden query set β recall@k for retrieval, faithfulness/relevance judges for generation β run as a regression suite on every pipeline change.
Q: Retrieval quality is bad β debug it.
Back-to-front: sample failing queries, inspect what was retrieved. Right doc absent from top-50 β recall problem: check chunking (is the answer split or context-stripped?), vocabulary mismatch (add BM25), embedding fit. Present-but-buried β precision problem: add reranking. Also check the boring suspects first: parsing garbage, stale index, over-aggressive metadata filters.
Q: Cosine vs dot product?
Cosine measures angle only; dot product also rewards magnitude. On normalized vectors they rank identically β so normalize and stop worrying, but use whatever the embedding model was trained with.
Q: Why hybrid search?
Dense and lexical fail on disjoint query classes β embeddings handle paraphrase but blur exact identifiers (error codes, function names); BM25 nails identifiers but not synonyms. RRF-merged hybrid covers both; production corpora (logs, code, docs) are full of identifiers, so hybrid is my default.
Q: RAG vs fine-tuning vs long context?
Knowledge that's large/fresh/multi-tenant β RAG. Behavior/style/format consistency, or making a small model cheap at volume β fine-tuning. Bounded corpus that fits, especially with prefix caching β long context. They compose; the eval suite decides.
12Self-test
State the retrieval-upper-bound thesis and its corollary for debugging.
A RAG system's answer quality is upper-bounded by its retrieval quality β if the right passage isn't in context, no model/prompt/temperature saves you. Corollary: debug back-to-front. Most "the LLM is bad" complaints are retrieval bugs; most retrieval bugs are ingestion bugs. Look at what was retrieved before blaming the model, and at the chunks before blaming the retriever.
What is small-to-big retrieval and which tension does it resolve?
Index small chunks (matching precision) but hand the LLM the parent section (answer completeness). It resolves the central chunking tension: small chunks retrieve precisely but can't support synthesis; large chunks embed muddily but read completely.
Why does hybrid search exist? Name the merge algorithm and its formula.
Dense retrieval blurs exact identifiers (error codes, SKUs, function names) β BM25's strength; BM25 has zero paraphrase understanding β dense's strength. The failures are disjoint, so run both and merge with Reciprocal Rank Fusion: score = Ξ£ 1/(k + rank) across result lists β rank-based, dodging incomparable score scales.
Name the RAG triad and what each metric catches.
Faithfulness/groundedness β every claim supported by retrieved context (catches hallucination-over-context). Answer relevance β actually addresses the question (catches faithful-but-off-target). Context relevance β retrieved context was on-topic (retrieval quality, post-hoc). Plus, separately: recall@k on a golden set is the single most important retrieval number.
Give three situations where you would NOT use RAG, with the replacement.
(1) Bounded working set that fits β long context (+ prefix caching for repeated queries). (2) Style/format/behavior problems β fine-tuning (RAG injects knowledge; fine-tuning shapes behavior). (3) All needed info is in the request (summarize this) β no augmentation. Bonus: investigative queries β agentic retrieval with a search tool instead of a pre-embedded corpus.
Why is access control a retrieval concern, not just an app concern?
Chunks carry permission metadata, and the retrieval query must filter on it per requesting user β otherwise semantic search happily surfaces documents the user should never see, turning RAG into a data-leak machine. Enforcing it after generation is too late: the content already reached the model and possibly the answer.