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.

The most important sentence on this page: a RAG system's answer quality is upper-bounded by its retrieval quality. If the right passage isn't in context, no model, prompt, or temperature setting can save you. Most "the LLM is bad" complaints in RAG systems are retrieval bugs.

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

1 Β· Parse & clean

Turn PDFs/HTML/scans into clean text with structure kept.

Fails when: tables flattened row-by-row, boilerplate kept, OCR garbage β†’ garbage everything downstream.

β–Έ
2 Β· Chunk

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…").

β–Έ
3 Β· Embed

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.

β–Έ
4 Β· Index

ANN index + keyword index + metadata (source, date, permissions).

Fails when: index goes stale vs source; permission metadata missing β†’ data-leak machine.

Query β€” online

5 Β· Rewrite

Make conversational follow-ups self-contained before embedding.

Fails when: raw "what about the EU?" is embedded context-free β†’ retrieves garbage.

β–Έ
6 Β· Retrieve

Dense + BM25, merged with RRF; permission-filtered per user.

Fails when: pure dense whiffs on identifiers; pure BM25 whiffs on paraphrase; filters over-aggressive.

β–Έ
7 Β· Rerank

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.

β–Έ
8 Β· Assemble

Dedupe, order deliberately, attach citations, allow abstention.

Fails when: evidence buried mid-context (lost-in-the-middle); no "say so if insufficient" out.

β–Έ
9 Β· Generate

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).

StrategyHowFailure mode
Fixed-sizeSplit every N tokens, 10–20% overlapSplits 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-awareSplit on paragraphs β†’ sentences β†’ tokens as fallbackBetter, but blind to semantic structure; a "chunk" may fuse the end of one topic and the start of another β†’ muddy embedding
Structure-awareChunk 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 chunkingSplit where embedding similarity between consecutive sentences dropsCostlier at ingest; thresholds are fiddly; helps most on unstructured prose
Code chunkingSplit 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.
The honest answer on chunk size: there is no universal best (typical starting point: 200–500 tokens + structure-awareness). "Chunking is a hyperparameter; I tune it against a retrieval eval set, not by vibes."

πŸ”ͺ Chunking playground

A ~600-char slice of fake API docs that mixes two topics (auth-token rotation and payment errors). Drag the sliders and watch the tradeoff appear.

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.

Landscape in one line: dedicated engines (Pinecone, Qdrant, Weaviate, Milvus) vs pgvector in the Postgres you already run. The senior take: at < a few million vectors, pgvector avoids a whole new stateful system; dedicated engines earn their keep at scale or with heavy filtering. Redis/Elasticsearch/OpenSearch also do vector search now β€” "vector search is becoming a feature, not a product" is a defensible, current position.

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."

βš”οΈ Retrieval duel β€” BM25 vs dense

One corpus, two query styles. Toggle and watch which retriever finds the right chunk (the payment-decline one).

      β†’ query:

C1 Β· "Error code 4012 is returned when the card issuer declines the capture authorization."

BM25
dense

C2 Β· "Declined transactions: when the issuing bank refuses a charge, the purchase does not go through and the decline reason is shown."

BM25
dense

C3 Β· "Rotate refresh tokens every 15 minutes via the /v2/auth/token endpoint."

BM25
dense

Each retriever wins a query class the other loses β€” that asymmetry is WHY hybrid + RRF exists.

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).

Rule of thumb: add a reranker when retrieval recall@50 is good but precision@5 is bad β€” i.e. the right doc is found but not surfaced.

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#refunds and 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):

Faithfulness / groundedness

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.

Answer relevance

Does it actually address the question β€” vs a faithful-but-off-target summary?

Context relevance

Was the retrieved context itself on-topic? Retrieval quality, judged post-hoc.

Practice: build a golden set from real user queries (even 50–100 is transformative) including known-hard cases and unanswerable questions β€” the system should abstain; test it. Run the suite on every change to chunking, embedding model, prompts, or k β€” RAG has many coupled knobs, and without regression evals you're doing archaeology on prod complaints. LLM-judge caveats (bias, drift): see LLM ops & evals.

9Common failure modes β†’ fixes (the cheat table)

SymptomLikely causeFix
Right doc exists, never retrievedVocabulary mismatch; bad chunking; embedding weak on domain termsHybrid search; re-chunk with structure/context; better embedding model; query rewriting
Retrieved but wrong answerLost-in-the-middle; conflicting chunks; context-stripped chunk ambiguityRerank + tighter k; deliberate ordering; enriched chunks; resolve version conflicts at ingest
Hallucinates beyond contextNo abstention path; retrieval gap silently filled from weightsExplicit "insufficient context" instruction; faithfulness eval as regression gate; citations required
Confidently stale answersIndex not synced with sourceIncremental re-embedding pipeline; recency metadata + filters; deletion propagation
Great on factoids, bad on "summarize/compare"Chunk granularity mismatch β€” top-k snippets can't support synthesisSmall-to-big retrieval; hierarchical summaries; route summary-type queries differently
Multi-hop questions failSingle retrieval can't chain evidenceQuery decomposition; iterative/agentic retrieval (retrieve→read→retrieve again)
Chat follow-ups retrieve garbageRaw follow-up ("what about the EU?") is context-freeQuery rewriting from conversation history before embedding
Users see docs they shouldn'tPermissions not enforced at retrievalPermission metadata filtered in the query, per requesting user β€” non-negotiable
Meta-point that lands well: most RAG failures are retrieval failures, and most retrieval failures are ingestion failures. Debug back-to-front β€” look at what was retrieved before blaming the model, and look at the chunks before blaming the retriever.

10When NOT to use RAG

Strong candidates know the boundaries of the pattern. RAG is a tool, not a default: bounded corpus β†’ long context; behavior/style problems β†’ fine-tuning; information already in the request β†’ nothing at all; investigative queries β†’ an agent with a search tool.

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.