Day 04 — Phase 1 — 90 minutes

AnLLMcannotknowyourdata.Retrievalishowyouhanditover.

Day 3 showed you how to direct the model. Day 4 shows you how to feed it — chunking, embeddings, vector search, re-ranking. The plumbing behind every AI product that knows something private.

Reading time
18 minutes
— then build one yourself
scroll
§ 01 — why rag exists
§ 01 / why rag exists

A model only knows what it was trained on. Your data was not in that pile.

Your internal wiki, last night's support tickets, the contract signed this morning — none of it exists inside the weights. There are exactly three ways to change that, and only one of them keeps up with a corpus that changes.

Long context did not kill retrieval. Gemini 1.5 Pro shipped a one-million-token window in 2024 and frontier windows have kept growing since. The window stopped being the constraint. The bill did not.

the arithmetic
200,000-token corpus, every call$0.60 / query
5 retrieved chunks (~2,000 tokens)$0.006 / query
At $3 per million input tokens — the same assumption we used on Day 1. A 100× gap on input cost alone, before latency. Prompt caching narrows it for a corpus that never changes. It does not close it.
pick an approach
Fetch the few pieces that matter.

Index your documents once. At query time, find the handful of passages that actually relate to the question, and put only those in the prompt.

Setup effortA pipeline: chunk, embed, index, retrieve.
FreshnessRe-index one document, it is live.
Cost per queryYou pay for a few passages, not the corpus.
CitationsYou know which chunks you sent. Cite them.
Access controlFilter by permission before retrieval.
use it when

Large or changing corpora, per-user permissions, anything that needs a source link. Which is most real products.

← not rivals — mature products use all three

"Fine-tuning changes how the model behaves. Retrieval changes what it knows. Confusing the two is the most expensive mistake in this field."

§ 02 / chunking

You do not retrieve documents. You retrieve pieces. Where you cut decides what you can find.

A chunk is the unit of retrieval — the smallest thing your system can hand to the model. Cut a sentence in half and neither half is findable: one has the subject, the other has the number. Nothing downstream can repair that. Not a better embedding model, not a re-ranker, not a cleverer prompt.

Chunks too small
The embedding is sharp — it means one thing
The answer gets cut in half at the boundary
Pronouns lose their subject: "those follow the terms"
Chunks too large
Context survives — the whole clause stays together
One vector now averages four unrelated topics
You burn prompt tokens on text nobody asked about
the honest answer

There is no research-blessed chunk size. It depends on your documents and the questions people actually ask. Common practice sits somewhere around 200–800 tokens with 10–20% overlap — a starting point, not a finding.

→ the only way to know is to measure it on your own queries
the question someone will ask
How long do enterprise customers have to request a refund?
refund-policy.md → 3 chunks160 chars, no overlap
chunk 01 · 160 chars
Refund Policy v3. All refund requests must be submitted within 30 days of purchase. Requests are reviewed by the billing team within two business days. Enterpri
chunk 02 · 160 chars
se contracts are the exception: those follow the terms in the signed agreement, which typically allow a 90-day window. To submit a request, email billing@exampl
chunk 03 · 25 chars
e.com with your order ID.
answer severed"Enterprise" sits in one chunk, "90-day window" in the next. Neither chunk answers the question.

Cheapest to implement. Cuts wherever the character count lands — mid-word, mid-sentence, mid-table.

↑ chunk 01 and 02 are cut by real code — switch tabs and watch the seam move
§ 03 / embedding & retrieval

Search stopped being about words. It is geometry now.

Day 2 showed you embeddings — text turned into a list of numbers, a coordinate in a space where meaning is direction. Retrieval is the payoff. Embed every chunk once. Embed the question at query time. The chunks pointing the same way are your answer.

Cosine similarity
The angle between two vectors, not the distance. Runs −1 to 1 — but real embedding models bunch everything into a narrow positive band, so 0.82 on its own means nothing. Only the ranking matters.
Exact kNN
Compare the query against every vector you have. Perfectly accurate, and linear in corpus size. Fine for 10,000 chunks. Not fine for 10 million.
ANN index
HNSW and friends walk a graph instead of scanning. Milliseconds over millions of vectors — and approximate, by design. The true nearest neighbour can be missed.
Metadata filter
Restrict the search to this tenant, this workspace, this date range — before scoring. This is where access control lives.
the fusion formula
score(d) = Σ 1 / (k + rank(d))

Reciprocal Rank Fusion — Cormack et al. (2009), with k = 60. It reads positions, never scores, so a BM25 score and a cosine score never have to be made comparable. No training, no tuning, roughly ten lines of code.

query
how much time do big corporate clients get to ask for their money back?
Dense
embeddings · cosine
1C1
2C2
3C5
✓ right chunk at #1
Keyword
BM25 · lexical
— no results —
✗ nothing returned
Not one query word appears in the right chunk. "Corporate" is not "enterprise". "Money back" is not "refund". BM25 has nothing to match on.
Hybrid
RRF · k=60
1C10.0164
2C20.0161
3C50.0159
✓ right chunk at #1
what just happened

Dense retrieval reads meaning. This is the query type it was built for.

the indexed chunks
C1Refund Policy v3 — Enterprise contracts follow the signed agreement, which typically allows a 90-day refund window.
C2Refund Policy v3 — Standard plans: refund requests must be submitted within 30 days of purchase.
C3Error ERR_2041 is returned when a webhook signature fails verification. Rotate the signing secret and retry.
C4Troubleshooting — if your callback endpoint returns 5xx, we retry with exponential backoff for up to 24 hours.
C5Billing FAQ — invoices are issued on the first of each month and charged to the card on file.
Dense and keyword orderings above are illustrative — they show how each ranker behaves on each query shape. The hybrid column is not: it is computed from those two lists with the real RRF formula, k = 60.
← neither ranker wins all three. that is the whole argument for hybrid.
§ 04 / re-ranking

Retrieval finds fifty maybes. Re-ranking finds the five that answer the question.

Vector search is optimised for recall — get the right chunk somewhere in the top fifty, fast. That is a different job from putting it at number one. So you run a second, more expensive model over the shortlist, and only the shortlist.

Bi-encoder
Stage 1 — recall

Query and chunk are encoded separately. Chunk vectors are computed once, at index time, and never again.

+One vector lookup per query. Scales to millions.
The two never meet. The model scores a chunk without ever having seen the question next to it.
Cross-encoder
Stage 2 — precision

Query and chunk go through the model together, attending to each other token by token, and one relevance score comes out.

+Far more accurate. It can tell "30 days" from "90 days" in context.
Nothing can be pre-computed. Every candidate is a forward pass at query time.
why not cross-encode everything

Reimers & Gurevych (2019) measured it: finding the most similar pair among 10,000 sentences takes roughly 65 hours with a BERT cross-encoder and about 5 seconds once the sentences are pre-encoded as vectors.

→ that gap is the entire reason retrieval has two stages
the funnel
Corpus
1,240,000
every chunk you have indexed
ANN search
50
bi-encoder + HNSW graph walk
milliseconds
Re-rank
5
cross-encoder, one pass per candidate
the expensive stage
Prompt
~2,000
tokens of context, not a corpus
≈ $0.006 per query
query
can I get a refund after 45 days on an enterprise contract?
1K7Refunds are processed to the original payment method within 5–7 business days.
2K2Standard plans: refund requests must be submitted within 30 days of purchase.
3K9How to request a refund: email billing with your order ID and invoice number.
4K4Refund Policy v3 changelog — v3 clarifies the enterprise exception wording.
top 4 → into the prompt
5K5Partial refunds are available for annual plans cancelled mid-term.
6K1Enterprise contracts follow the signed agreement, which typically allows a 90-day refund window.
7K8Refunds are not available for one-time setup or onboarding fees.
8K3Chargebacks and disputes are handled directly by the payments team.
answer droppedK1 is at position 6, below the cut. The model never sees the 90-day clause — and will answer confidently from the 30-day chunk instead.
Relevance scores here are illustrative. What is not illustrative is the failure shape: the retriever did its job — the right chunk was in the shortlist — and the system still answered wrongly, because nothing put it near the top.
← and once you have your five, order them: Day 3's lost-in-the-middle applies here too
§ 05 / the whole pipeline

Three stages run once. Three run on every single query.

That split is the whole architecture. Parsing, chunking and indexing are a batch job you run when documents change. Retrieving, re-ranking and generating happen while someone waits — and every millisecond and every token there is on your bill.

stage 02 · chunkonce, at ingest

Split on the document's own seams, pack to a budget, overlap the boundaries, and prefix each chunk with the title and section it came from.

chunks = split(text, on=["\n## ", "\n\n", ". "], budget=500, overlap=60)
how it breaks

A clause is severed across two chunks. A chunk full of "it" and "those" with the subject three chunks back.

what you will see

The answer is definitely in the corpus, and it never appears in the top-k.

the debugging rule

When a RAG system answers badly, log the retrieved chunks before you touch the prompt. If the answer was not in what you retrieved, it is a retrieval bug, and no amount of prompt engineering will fix it. If it was in there and the model still got it wrong, now you have a generation problem.

Measure it properly: write down thirty real questions and the chunk that should answer each one, then track recall@k — how often the right chunk is in the top k. It is the one number that tells you whether the rest of the system even has a chance.
say this out loud

RAG reduces hallucination. It does not end it. A model handed the right passage can still misread it, blend it with something it half-remembers from training, or answer a question the passage never addressed. Grounding is an instruction plus an evaluation loop — never a guarantee.

§ 06 / wait, what?

Things that'll
change how you retrieve

All from published research. All directly applicable to the pipeline you ship next week.

01 / 06
The original RAG paper chunked Wikipedia into fixed 100-word blocks

Lewis et al. (2020), "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" (Facebook AI Research, NeurIPS 2020), is where the term comes from. Its memory was a Wikipedia dump split into 21 million 100-word passages, retrieved with DPR and passed to a BART generator — with the query encoder and generator trained jointly.

why it matters: The architecture the entire industry copied started with the simplest chunking rule imaginable. Ship the boring version, measure it, then get clever.
02 / 06
Dense retrieval beat keyword search by up to 19 points — in 2020

Karpukhin et al. (2020), "Dense Passage Retrieval for Open-Domain Question Answering", reported that their dense retriever outperformed a strong Lucene-BM25 system by 9–19% absolute in top-20 passage retrieval accuracy across open-domain QA benchmarks.

why it matters: This result is why every vector database exists. It is also only half the story — read the next card before you delete your keyword index.
03 / 06
A 1990s keyword algorithm still beats modern embeddings on unfamiliar data

Thakur et al. (2021) built BEIR to test retrievers across 18 datasets they were not trained on. BM25 held up as a remarkably strong baseline: several dense models that dominate in-domain fell behind it once the domain shifted. The strongest overall configuration was BM25 retrieval followed by a cross-encoder re-ranker.

why it matters: In-domain leaderboard position does not predict how a retriever behaves on your documents. That is an argument for hybrid search and for measuring on your own data.
04 / 06
The best way to combine two rankers is a formula with no training in it

Cormack et al. (2009), "Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods" (SIGIR), scores each document as the sum of 1/(k + rank) across every ranked list it appears in, with k = 60. It beat both the individual rankers and more sophisticated fusion methods.

why it matters: It uses positions, never scores — so a cosine similarity and a BM25 score never have to be calibrated against each other. Ten lines of code, no model to train.
05 / 06
65 hours versus 5 seconds — the measurement that shaped two-stage retrieval

Reimers & Gurevych (2019), "Sentence-BERT", noted that finding the most similar pair among 10,000 sentences requires about 50 million BERT cross-encoder inferences — roughly 65 hours on the GPU they used. Pre-encoding each sentence once and comparing vectors brings the same task down to about 5 seconds.

why it matters: Cross-encoders are more accurate and structurally cannot be your first stage. That single constraint is why retrieval is a funnel: cheap and wide first, expensive and narrow second.
06 / 06
Your vector database does not return the nearest neighbours

Production vector search runs on approximate nearest neighbour indexes — most commonly HNSW (Malkov & Yashunin), which walks a layered graph instead of scanning every vector. It is approximate by construction: the true best match can be missed, and how often that happens depends on parameters you set.

why it matters: Recall is a dial you chose, usually by accepting a default. If retrieval quality is unexplainably poor, the index is a suspect — not just the embedding model.
§ 07 / recap
01
the pipeline
Parsefiles → text, structure kept
Chunkcut on seams, overlap the joins
Indexvector + metadata + ACL
Retrievedense + lexical, fused
Re-rankcross-encoder over the shortlist
Generateanswer only from context, cite it
← first three run once
02
symptom → cause
Answer exists, never retrieved
Chunking cut it. Fix the seams.
Right chunk retrieved, wrong answer
Ranking or ordering. Add a re-ranker.
Exact codes never found
Dense-only. Add lexical search.
Confident, sourceless answer
No grounding instruction.
← log the chunks first
03
where to start
Chunks
~500 tokens, ~60 overlap, split on headings
Retrieve
top 50, dense + BM25, fused with RRF
Re-rank
cross-encoder down to 5
Measure
recall@k on 30 real questions
← defaults, not answers
§ 08 / homework

Two things before next session.

Day 5 treats retrieval as one tool an agent can reach for. You want to have felt it break first.

01

Build the smallest RAG that works, over ten of your own documents. Parse, chunk, embed, retrieve the top five, answer from them. No framework, no vector database.

At ten documents you do not need an ANN index — cosine similarity over an array is exact and instant. Skipping it removes a variable while you learn the rest.

02

Write twenty real questions and, for each one, the chunk that should answer it. Measure recall@5. Then add overlap, then add keyword search, then add a re-ranker — and re-measure after each.

Your first number will be worse than you expect. That is the point: you now have a dial you can turn instead of a vibe you can argue about.

next up
Day 5 → Agents & Tool Use.
From chatbot to agent. Function calling, orchestration, the agentic loop.
What makes something an agent
Function calling and tool schemas
The agentic loop, step by step
Retrieval as just another tool
Where agents fail in production