Skip to module content
Module 02 · ~9 min

RAG — When It's the Right Answer

Retrieval and generation are two systems. Evaluate them separately, or debug them never.

Reading progress
0/5 · 0%

The big idea

💡Key idea
A wrong answer with correct chunks in context is a generation problem. A wrong answer with wrong chunks is a retrieval problem — and no amount of prompt surgery fixes it. Always test retrieval alone, first.
Quick check
1 question · instant feedback
0/1
  1. Your RAG bot returns a wrong answer. Retrieved chunks contain the correct information. What's broken?

Deep dive

4/4 open

RAG earns its complexity when all three of these are true — if any one is missing, a simpler shape probably wins.

**1. The knowledge is too large or too volatile** to fit in context or bake into a fine-tune. A 300-page policy guide updated quarterly clears this bar. A 40-paragraph FAQ does not.

**2. Answers must come from your specific documents** — client SOPs, policy pages, product catalogues — not from the model's general training. If the model already knows the answer reliably, retrieval adds latency for no gain.

**3. You need traceable provenance.** 'This answer came from these three passages' is increasingly a hard requirement for regulated and brand-sensitive clients. If you can't point to a source, you're generating, not retrieving.

A chunk is the unit of retrieval — it's what gets pulled out of storage and handed to the model. Getting this wrong cascades into every downstream component.

**Fixed-size chunking** (~500 tokens, 10–15% overlap) is the robust default. It degrades gracefully across messy, inconsistently formatted documents.

**Semantic chunking** (splitting on headings and topic boundaries) pays off on well-structured docs like policy pages and API references. It tends to hurt on unstructured or messy source material.

**The rule that doesn't change:** a chunk read in isolation must still make sense. A chunk that opens with 'and therefore the limit is doubled' will retrieve fine and answer badly — there's no context for what doubled, or why.

Store generous metadata alongside each chunk — source URL, section title, effective date, client, access tier. It costs nothing at write time and enables filtering you'll definitely want later.

**Choosing an embedding model** comes down to three axes: retrieval quality on your specific domain (published leaderboards are directional, not decisive — measure on your own eval set), cost per million tokens (you embed the whole corpus, and if you switch models you re-embed everything — that migration cost is the real lock-in to think about), and dimensionality (storage and search latency scale with vector size).

Start with a solid mid-tier model, measure it against your eval set, and only move up when measurement says to.

**Retrieval strategy:** hybrid search — combining dense vector search with BM25 keyword search — is the default, not an optimisation. Pure vector search misses exact identifiers like SKUs and error codes. Pure keyword search misses paraphrase. You need both.

**Rerankers** sit on top of retrieval: they take the top ~50 candidates and reorder them, returning the best ~5. They are the highest ROI-per-line-of-code addition to a RAG stack. Add one before tuning anything else.

**Metadata filters** run before vector search and double as your access-control seam. Retrieval that ignores permission metadata is a data breach with extra steps.

This is an infrastructure decision with real tradeoffs — not just a performance one.

**pgvector** (Postgres-native) is the default for most Supabase deployments. Row-level security applies directly to chunks, your backup story is already sorted, and your team already knows Postgres. Use it until you have a measured reason not to.

**Managed vector stores** (Pinecone-style) trade ops burden for a new vendor dependency and data-residency questions that need to go on your client's risk register.

**Self-hosted engines** (Qdrant, Weaviate) give you full performance control — and full ownership of whatever breaks at 2 a.m.

For most client deployments: pgvector until you feel actual, measured pain.

Quick check
1 question · instant feedback
0/1
  1. Client corpus is ~40 paragraphs of FAQ. What ships?

In the field

🔬Worked example
Task: clients ask 'what does LinkedIn allow in ad copy for financial services?' — answers must cite LinkedIn's actual policy pages, current versions. Shape check: ~300 pages, updated quarterly, provenance required → RAG justified (had it been 20 pages: context-stuffing). Chunking: semantic on policy headings; metadata {source_url, section, effective_date, jurisdiction}. Retrieval: hybrid (paraphrase mixed with exact terms like 'APR disclosure') + reranker; filter effective_date = current. Generation: answer only from retrieved chunks; every claim carries a [source: section] marker; if top rerank score is below threshold, say 'not found in policy corpus' rather than guess. Refusal-on-low-confidence is a feature you spec, not model politeness. Wrap it as a search_ad_policy tool on your MCP.
🚫When not to reach for it
Four cheaper shapes beat RAG: (1) Corpus fits in context — a few hundred pages often just fits; stuffing is simpler with no retrieval failure mode. Do the arithmetic before building infrastructure. (2) Questions are structured — 'spend by campaign last week' is a SQL query, not semantic search; text-to-SQL beats embeddings for anything tabular. (3) Freshness is the real need — competitor pricing, news; a live search tool beats a stale index. (4) The corpus is ten FAQs — put them in the prompt; a vector database for 40 paragraphs is résumé-driven engineering. Put a half-page 'why RAG / why not RAG' note on every engagement — it's IP and it protects you when a prior vendor pitched something bigger.
Quick check
1 question · instant feedback
0/1
  1. Which retrieval config typically wins on domain queries that mix paraphrase and exact identifiers?

Pitfalls & takeaways

Failure modes

  • Building RAG when context-stuffing or a live search tool would answer the question — infrastructure that produces no measurable win over prompt-only.
  • Chunks that don't stand alone. A chunk beginning 'and therefore the limit is doubled' retrieves well and answers badly.
  • Pure-vector retrieval that misses exact identifiers (SKUs, error codes) or pure-keyword that misses paraphrase. Hybrid is the default, not an optimisation.
  • No reranker. Rerankers are the highest ROI-per-line-of-code component in the stack; add one before tuning anything else.
  • Metadata filters ignored — 'retrieval that ignores permissions is a breach with extra steps.'
  • Generation that hallucinates a source when confidence is low, instead of refusing.

Durable takeaways

  • Evaluate retrieval alone before touching generation.
  • Hybrid + reranker + metadata filters is the default stack.
  • Refusal-on-low-confidence is a spec'd feature, not model politeness.
  • Every RAG engagement gets a half-page 'why RAG / why not RAG' note.
Quick check
1 question · instant feedback
0/1
  1. The top rerank score is below your threshold. The right generation behaviour is…

Do the work

🏋️Prove you learned it

Build a policy-corpus retriever with pgvector + hybrid + rerank on any real corpus. Before touching generation, write a 30-question retrieval eval mapping each question to the doc/section that should be retrieved. Score recall@5 for each config and put the three numbers in a table — plus one paragraph on which config you'd ship and why.

0 chars
📦Artifact to produce
Retrieval eval table: recall@5 for vector-only vs hybrid vs hybrid+rerank across 30 real questions.
Quick check
1 question · instant feedback
0/1
  1. Your chunks are 500-token fixed-size but the model keeps quoting fragments like 'and therefore the limit is doubled' with no context. Fix?