Vibe Engines
YouTube
AI System Design

Design a RAG Pipeline

Learn AI system design by building a retrieval-augmented generation (RAG) pipeline step by step.

The numbers to beat~300–800tokens / chunk1 vectorper chunkofflineruns ahead of time

The whole design, in writing

Learn AI system design by building a retrieval-augmented generation (RAG) pipeline step by step. An interactive guide covering document chunking, embeddings, the vector store, top-k retrieval, reranking, grounded prompt assembly, and keeping the index fresh — so an LLM answers from your knowledge, not its training data.

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

Why retrieval at all?

An LLM only knows what it was trained on. Ask it about your internal docs, last week’s release, or a private wiki and it confidently makes something up. How do you make it answer from knowledge it never saw?

Userasks a question
New in this step: User.

Retrieval-Augmented Generation: before generating, fetch the most relevant passages from your knowledge and put them in the prompt. The model reasons over real, current text instead of fuzzy memory — and can cite its sources.

What the new pieces do

Userclient
A person asking a question that should be answered from your documents, not the model’s training data.

Step 1 · The skeleton

Retrieve, then generate

A user asks a question. We can’t just forward it to the LLM — it would answer from training data. What has to happen between the question and the answer?

Userasks a questionRAG Gatewayorchestrator
New in this step: RAG Gateway.

The user asks a question about your private docs. What’s the flow?

  1. It has never seen your documents, so it answers from training data — confidently and often wrong. This is exactly what RAG exists to fix.

  2. A gateway orchestrates retrieve-then-generate: find the chunks that matter, hand them to the model as context, and let it answer from real text.

  3. Fine-tuning bakes knowledge in slowly and expensively, can’t cite sources, and goes stale the moment a doc changes. Retrieval keeps knowledge live and external.

A RAG Gateway orchestrates two phases for every question: retrieve the most relevant passages, then generate an answer grounded in them. Knowledge lives outside the model, so it’s always current and citable.

Why this piece earns its place

The gateway looks like plumbing, and skipping it is the most common shortcut: retrieval is three function calls, so the app makes them itself. What you lose is the only place that can degrade. Retrieval has parts that fail independently — the vector store times out, the embedding API rate-limits, the reranker is slow today — and each failure has a different right answer: serve a cached response, answer ungrounded with an explicit warning, or refuse outright. That policy has to live somewhere, and if it does not live in one component then every caller invents its own version of it, inconsistently. The gateway is also where the per-request budget lives: how many milliseconds retrieval gets before generation starts without it, how many tokens the context may consume, which tenant's documents this request is allowed to see. None of those are model decisions and none of them are UI decisions. Build it as a seam from the start, even when it is thin, because the second consumer arrives sooner than you expect.

What the new pieces do

RAG Gatewaybackend
Coordinates the retrieve-then-generate flow: embeds the query, fetches chunks, builds the grounded prompt, calls the model.

Step 2 · Prepare the knowledge

Chunk and embed your documents

Your knowledge is a pile of long documents. You can’t hand a whole 80-page PDF to the model per query. How do you make documents searchable by meaning?

UserRAG GatewayVector StoreDocument StoreIngestionKnowledgeEmbedding Model
New in this step: Vector Store, Document Store, Ingestion, Knowledge, Embedding Model. · swipe to pan the diagram

How do you store documents so you can find the relevant bits by meaning?

  1. Keyword search misses paraphrase — "how do I cancel" won’t match "terminating your subscription." Meaning needs semantic vectors, not exact words.

  2. Whole docs are too big for the context window and too coarse to pinpoint the relevant passage. You need to split first.

  3. An offline pipeline chunks documents, embeds each chunk into a vector capturing its meaning, and upserts vectors + text into the stores. Now "find by meaning" is a nearest-neighbour search.

An offline Ingestion pipeline loads each document, splits it into chunks, runs every chunk through an Embedding Model to get a vector, and upserts the vectors into a Vector Store (plus the raw text into a Document Store). This is the prep work that makes retrieval possible.

Why this piece earns its place

Ingestion reads like a script you run once, and that framing is what breaks RAG three months in. It is a stateful, versioned system, and the state is the hard part. Chunk ids have to be deterministic — derived from source id plus position, not a fresh UUID per run — or re-ingesting a lightly edited document appends a second copy instead of updating the first, and retrieval starts returning both the old and the new wording of the same policy. Deletes are worse: remove a document at the source and nothing removes its vectors, so the pipeline keeps confidently citing a page that no longer exists. Tombstones or a reconciliation sweep are not optional. This is also where the money is. Embedding is charged per token across the whole corpus, not per query, so the expensive operation is the backfill — a re-chunk, an embedder upgrade, a new document set — while steady-state ingestion is cheap. The upside: it is offline work, so unlike query latency you can schedule it, batch it, and run it on spare capacity.

  • ~300–800tokens / chunk
  • 1 vectorper chunk
  • offlineruns ahead of time

What the new pieces do

Vector Storeindex
Holds chunk embeddings and answers nearest-neighbour search in milliseconds via an ANN index.
Document Storestore
The original chunk text and metadata, returned alongside vectors so the prompt and citations use real content.
Ingestionbus
The offline pipeline: load documents, split into chunks, embed each, and upsert into the vector + document stores.
Knowledgestore
The source of truth: PDFs, docs, wikis, tickets — whatever the system must answer from.
Embedding Modelbus
Maps text to vectors. The SAME model must embed both stored chunks and incoming queries so they share a space.

Back of the envelope

split on structure
paragraphs/sections, not arbitrary character counts
add overlap
~10–15% so ideas spanning a boundary aren’t cut in half
same embedding model
for chunks AND queries — they must share a space
store metadata
source, title, URL — for filtering and citations

Step 3 · Find the right chunks

Embed the query, search by similarity

A question comes in. The chunks are vectors in a store. How do you find the handful that actually answer this question — out of possibly millions?

RAG GatewayRetrieverVector StoreDocument StoreIngestionEmbedding Model
New in this step: Retriever. · swipe to pan the diagram

You have millions of chunk vectors. How do you find the closest to the query?

  1. Exact nearest-neighbour over millions of high-dim vectors is too slow per query. At scale you trade a little accuracy for huge speed.

  2. Embed the query with the SAME model, then let an ANN index (HNSW/IVF) return the closest chunks in milliseconds — near-exact recall, sub-linear cost.

  3. That’s back to lexical search and its paraphrase blind spot. Semantic retrieval is the whole point. (Hybrid keyword+vector is a refinement, not a replacement.)

The Retriever embeds the query with the same model, then asks the Vector Store for the top-k nearest chunks via an ANN index. The Document Store returns their text. Semantic match means "cancel my plan" finds "terminating your subscription."

Why this piece earns its place

Approximate search is the one component whose failure you cannot see from production traffic. A missing chunk raises no error, adds no latency and fills no dashboard — the answer just quietly draws on the second-best passage. So recall has to be measured off traffic, against a held-out set of queries with known-correct chunks, re-run whenever the index, the embedder or the ingest rules change. Without that set, "we tuned the index" is a claim nobody can check. The knobs are worth knowing by name. HNSW holds its graph in memory, so the index is sized in RAM per million vectors rather than on disk, and that is usually the real cost ceiling. Build and query cost are asymmetric: the graph is expensive to construct and cheap to search, which pushes you toward rebuilding on a schedule rather than continuously. And the search-effort knob (ef_search in HNSW, nprobe in IVF) is set per query, not per index — so a request that fails a confidence check can be retried wider before you give up on it.

What the new pieces do

Retrieverservice
Turns the query into a vector and searches the store for the nearest chunks (semantic, not keyword, match).

Step 4 · How much to fetch

Top-k and the recall/precision dial

Retrieve too few chunks and you miss the answer. Retrieve too many and you bury the model in noise, blow the token budget, and slow it down. Where’s the line?

Retrieverembed + searchVector Storetop-k ANNDocument Storesource chunks
New in this step: Retriever → Vector Store, Retriever → Document Store.

How many chunks should you stuff into the prompt?

  1. More context isn’t better — irrelevant chunks distract the model ("lost in the middle"), raise cost, and slow generation. Precision beats volume.

  2. Too brittle: the answer often spans two or three passages, and the very top hit isn’t always the right one. You need a small set, not a single bet.

  3. Retrieve a modest candidate set (e.g. k≈20), then narrow to the best few. Enough recall to catch the answer, enough precision to keep the prompt clean.

Retrieve a modest top-k candidate set from the Vector + Document stores — wide enough that the answer is almost certainly in there (recall), but not so wide it drowns the prompt. The next step tightens it to the best few (precision).

Why this piece earns its place

k is the only dial on this page that moves latency, cost and answer quality at the same time, which is exactly why it belongs in the request as a parameter, not a constant. Query classes differ: a lookup with one right answer ("what is the refund window") is served by a handful of chunks, while a synthesis question ("how has this policy changed") genuinely needs a wide net, and one hardcoded k serves one of them badly. Route by query type, or start narrow and widen when confidence comes back low. The other thing worth saying out loud is that there are two budgets here and they get confused constantly. The candidate set — k≈20 — is read only by the reranker, a small model that does not much care about volume. The prompt budget is what the generator reads, and it is the one that costs real money per request and degrades with noise. Growing k spends reranker time; growing the post-rerank shortlist spends generation quality. Naming which budget you are spending is most of the argument.

Back of the envelope

k ≈ 20 candidates × ~500 tok/chunk (mid-range of Step 2’s ~300–800) ≈ 10,000 tok
the retrieval-stage set — only the reranker reads this, not the generator
→ rerank → top 3–5 × ~500 tok ≈ 1,500–2,500 tok
what actually lands in the prompt
on an 8K-context model, worst case that leaves ≈ 5,500+ tok
room for the system prompt, conversation history, and the answer
optional metadata filter
restrict by source/recency before ranking

Step 5 · Sharpen the shortlist

Rerank for precision

The vector search is fast but coarse — it ranks by embedding similarity, which isn’t the same as "actually answers the question." The true best chunk might sit at position 8. How do you fix the order?

Retrieversearch + rerankRerankerprecision
New in this step: Reranker.

ANN similarity ≠ true relevance. How do you get the best chunks to the top?

  1. Bi-encoder similarity is a fast approximation. The genuinely most relevant passage often isn’t the nearest vector — order needs a second, sharper pass.

  2. A cross-encoder reads the query and each chunk together and scores true relevance — far more accurate. Run it on the ~20 candidates (cheap), keep the best 3–5.

  3. More candidates raises recall but not precision — you still feed the model noise. Reranking is what turns a long candidate list into a clean shortlist.

A Reranker (a cross-encoder) re-scores the top-k candidates by reading the query and each chunk together, then keeps only the best 3–5. It’s too slow to run over the whole store — but perfect over a 20-candidate shortlist.

Why this piece earns its place

The reranker buys two things and the second one is the one people miss. The obvious one is precision. The quiet one is a usable score. Bi-encoder cosine similarity is not calibrated and not comparable across queries — 0.82 is high for one question and mediocre for another, because it depends on where that query happens to land in the embedding space. You cannot threshold on it. A cross-encoder reads the pair together and produces a relevance score you can calibrate against labelled data, which is what makes Step 7's "say I don't know" mechanically possible at all; without it the decline rule has nothing trustworthy to fire on. The reranker is also the cheapest place to buy quality. Swapping it is a config change that takes effect on the next request, whereas swapping the embedding model invalidates every stored vector and means a full reindex of the corpus. So when retrieval quality is not good enough, reach for the reranker first and treat the embedder as the change you make deliberately and rarely.

What the new pieces do

Rerankerservice
Re-scores the top candidates with a heavier cross-encoder so the few chunks that reach the prompt are the most relevant.

Step 6 · Ground the answer

Build the prompt and generate

You have the 3–5 best chunks. Now the model has to answer — but you need it to use those chunks and not slip back into making things up. How do you assemble the prompt?

RAG GatewayorchestratorRetrieversearch + rerankLLMgrounded gen
New in this step: LLM.

You have the best chunks. How do you get a grounded, citable answer?

  1. If the chunks aren’t in the prompt, the model can’t use them. Grounding only works when the retrieved text is actually in the context.

  2. The gateway builds a grounded prompt: "answer ONLY from these passages, cite them, say you don’t know if they don’t cover it" — then generates.

  3. Back to noise and cost. You reranked for a reason — feed the clean shortlist, not the raw candidate pile.

The Gateway assembles a grounded prompt — system instructions + the reranked chunks + the question — and tells the LLM to answer only from the passages, cite them, and admit when they don’t cover the question. The chunks carry their source metadata, so citations point at real documents.

Why this piece earns its place

Prompt assembly is the step most likely to be written as string concatenation and never revisited, and it carries a real contract. Chunks need unambiguous delimiters and a stable id printed beside each one, because "cite your sources" only works if the model's citation can be parsed back to a chunk and verified — an answer naming a plausible document title is not a citation, it is a hallucination wearing one. Order matters too: models attend unevenly across a long context, so put the highest-ranked chunk where it will actually be read, and decide deliberately whether that is first or last for your model. Truncation needs a policy — drop whole chunks by rank, never tail-cut the last one, or you ship half a sentence as evidence. And log the assembled prompt alongside the answer. Without it a bad answer is undebuggable: you cannot tell whether retrieval never found the right passage or the model had it and ignored it, and those two have completely different fixes.

What the new pieces do

LLMservice
Generates the answer using only the retrieved chunks as context, with instructions to cite and not invent.

Step 7 · Trust the answer

Citations, "I don’t know", and evals

Even grounded, the model can over-claim or cite the wrong chunk. Users need to trust the answer — and you need to know when retrieval is failing. How do you keep RAG honest?

UserRAG GatewayRetrieverLLM
New in this step: LLM → RAG Gateway, RAG Gateway → User. · swipe to pan the diagram

How do you keep a RAG system trustworthy over time?

  1. Grounding reduces hallucination but doesn’t eliminate it — the model can still misread or over-generalize from a chunk. Trust must be verifiable.

  2. Citations are the trust mechanism — they let a user verify the claim against the source. Hiding them removes the one thing that makes RAG auditable.

  3. Surface the source chunks as citations, let the model decline when context is thin, and run evals (retrieval recall, answer faithfulness) to catch drift.

The answer ships with citations back to the source chunks, the model is allowed to say "I don’t know" when retrieval comes up thin, and an eval harness tracks retrieval quality (did we fetch the right chunks?) and answer faithfulness (did the answer stick to them?). That’s how you catch a silently drifting index before users do.

Why this piece earns its place

Two metrics, not one, and the reason is that the halves fail independently. A single end-to-end "was that a good answer" score tells you something regressed and nothing about what to do, because retrieval recall and answer faithfulness point at opposite repairs. Recall dropped: reindex, revisit chunking, widen k. Faithfulness dropped while recall held: the model had the right passage and drifted, so the fix is in the prompt or the model, and touching the index would waste a week. The eval set itself has to be frozen and versioned separately from the corpus, or you cannot distinguish a real regression from the ground truth moving underneath you — which is the failure mode of golden sets that get "refreshed" whenever they start failing. Offline evals are slow, so pair them with online signals that move daily: the rate at which the system declines to answer, whether users open the citations, thumbs-down clustering by source. None of those are ground truth. All of them tell you where to point the offline set next.

The payoff

You built a RAG pipeline

From a forgetful model to a grounded, citable system: an ingestion pipeline that chunks and embeds, a vector store for semantic search, two-stage retrieval with reranking, grounded generation, and evals to keep it honest.

UserRAG GatewayRetrieverRerankerLLMVector StoreDocument StoreIngestionKnowledgeEmbedding Model
The finished design, end to end. · swipe to pan the diagram

Now stale the index and watch RAG’s signature failure — confident answers built on outdated chunks — and see why ingestion is a first-class, ongoing system, not a one-time script.

Everything you assembled, in order

  • RAG Gateway — orchestrates retrieve → augment → generate
  • Ingestion — chunk + embed documents, offline and ongoing
  • Embeddings — meaning as coordinates — same model for chunks + queries
  • Vector Store — ANN search returns top-k by similarity in ms
  • Top-k — wide for recall, then narrowed for precision
  • Reranker — cross-encoder sharpens the shortlist to the best few
  • Grounded prompt — answer only from context, cite, decline if thin
  • Evals + citations — make a quiet failure mode visible and auditable

Deep cut · 12:19

Right drawer. Wrong page.

The interactive build above lays out a RAG pipeline: documents chunked and embedded offline, an ANN index over the vectors, a top-k pull, a cross-encoder reranker, a grounded prompt that cites its sources, and evals watching for drift. The film builds the same pipeline out of paper at a shop’s returns counter — the policy cut into one-rule pages that overlap at the edges, a label machine that turns any page into a card of marks, six drawers wearing those cards on the outside, the question put through the same machine, twenty pages fanned across the counter with the answer sitting fourth, fifteen swept aside, and the page number tapped twice and said out loud. Then it runs the drill a second time, after a hand has come through the back hatch.

  • See the wreck: someone swapped a page inside a drawer. The label card on the front never moved, because nothing connects them. Bo ran the identical drill, reached the identical drawer, and cited a page that was no longer true — with the same knuckle-tap that means “I checked”. Nothing errored, nothing was slow, and the citation we added so answers could be checked is what made the wrong answer sound trustworthy.
  • Take it into the interview: a citation proves where an answer came from, never that the page is still true. Re-print the label card in the same transaction that writes the chunk, date what the card was printed from, and treat an embedder change as a full reindex — two vector spaces that aren’t comparable return near-random chunks with real-looking scores.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why ~10-15% chunk overlap specifically — what actually breaks at 0% overlap, and what breaks at 50%?

    At 0% overlap, an idea that spans a chunk boundary gets split so neither chunk fully contains it — a sentence explaining "the deadline is X, UNLESS condition Y" could have the condition cut into the next chunk, and retrieval might fetch only the half with the deadline. At 50%+, you are storing and embedding nearly the same text twice, roughly doubling index size and compute for a marginal completeness gain past a certain point. 10-15% is the empirical sweet spot: enough overlap that boundary-spanning ideas usually survive in at least one chunk, without meaningfully bloating the index.

  2. A cross-encoder reranker scores query-document pairs one at a time, not in a batch like the bi-encoder embedder. How do you keep reranking 20 candidates fast enough?

    Cross-encoders ARE more expensive per comparison than the embedding-based ANN search, which is exactly why they only ever run on the narrow shortlist (k≈20) instead of the whole store — the two-stage design isn’t just about precision, it’s what makes the expensive stage affordable at all. Within that budget, reranking latency is managed by running comparisons in parallel (batched inference) and picking a reranker model sized for the latency budget, sometimes a smaller distilled cross-encoder that trades a little precision for meaningfully lower latency.

  3. A user should only see documents they have permission to access. Does that filtering happen before or after the ANN search?

    Before, ideally — as a metadata filter applied AT search time (most vector databases support filtered ANN search), not as a post-hoc filter on the results. Filtering after search is a real bug waiting to happen: if the top-k ANN results are entirely documents the user cannot see, filtering afterward can return an empty or thin result set even though relevant, permitted documents exist further down the unfiltered ranking — the search itself needs to know the access boundary, not just the display layer.

  4. How does the system decide, programmatically, that retrieval "came up thin" enough to say "I don’t know"?

    Typically a similarity-score threshold on the top result (or the reranker’s relevance score) — if even the best-ranked chunk falls below a calibrated confidence cutoff, that’s a signal the knowledge base likely doesn’t cover this question, and the prompt instructs the model to decline rather than stretch a weak match into a confident-sounding answer. The threshold itself needs the same calibration discipline as any classifier cutoff: set too high and it declines answerable questions; too low and it still grounds on near-misses.

  5. How does chunking and embedding change for documents with images and tables, not just prose?

    Tables usually need structure-aware chunking (keep a table intact or chunk it row-group by row-group rather than splitting mid-table, since a table fragment loses its header context) and images need either a multimodal embedding model that can embed image+text jointly, or a captioning step that converts the image to a text description an ordinary text embedder can index — either way, "just chunk every 500 tokens" silently breaks on both, treating a table as prose and destroying its meaning.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. RAG exists mainly to…

    Retrieval puts real, current passages in the prompt so the model answers from external knowledge instead of training-data memory.

  2. The same embedding model must be used for chunks and queries because…

    Nearest-neighbour search only means anything if query and chunk vectors come from the same embedding space.

  3. Two-stage retrieval (ANN then rerank) is used because…

    ANN gives cheap recall over millions of vectors; a cross-encoder reranker gives precision over the small candidate set.

  4. RAG’s most dangerous failure mode is…

    RAG fails quietly: the pipeline runs fine but the index is stale, so the answer is subtly wrong. Citations and evals make it visible.

  5. Someone upgrades the query-time embedding model but doesn’t re-embed the stored chunks. What happens?

    Two different embedding spaces aren’t comparable — "nearest neighbor" across them is close to random, but still returns k ranked results with no error. An embedder change requires a full reindex.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Retrieve then generate: fetch the most relevant passages first, then have the model answer from them — not from training memory.
  • Ingest & embed: an offline pipeline chunks documents, embeds each chunk, and upserts vectors + text into the stores.
  • Semantic search: embed the query with the same model and pull the top-k nearest chunks via an ANN index.
  • Rerank for precision: a cross-encoder re-scores the candidates and keeps the best few for the prompt.
  • Ground & cite: answer only from the retrieved chunks, cite the sources, and say “I don’t know” when they’re thin.

The qualities that shape everything

Each one names the mechanism that buys it.

Answer from knowledge the model never saw
A RAG Gateway orchestrates retrieve-then-generate, so knowledge lives outside the model and stays current and citable instead of baked into weights.
Find by meaning, not keywords
Chunk each document and embed it into a shared vector space — the same embedding model for chunks and queries so they’re comparable.
Search millions of vectors in milliseconds
An ANN index (HNSW/IVF) trades a sliver of recall for orders-of-magnitude speed over exact nearest-neighbour.
Enough recall without drowning the prompt
Retrieve a modest top-k candidate set (wide enough the answer is almost certainly in it), then narrow it for precision.
The best chunk actually reaches the model
A cross-encoder reranker re-scores the ~20-candidate shortlist by reading query and chunk together, keeping the best 3–5.
Keep answers honest and auditable
Citations back to source chunks, an allowed “I don’t know” when retrieval is thin, and evals on retrieval recall and answer faithfulness.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Retrieve then generate over sending the question straight to the LLM

The model never saw your documents, so it answers from training data — confidently and often wrong, which is exactly what RAG exists to fix.

Chunk + embed for semantic search over keyword-indexing whole documents

Keyword search misses paraphrase (“cancel” vs “terminating your subscription”) and whole docs are too big and coarse; embeddings put similar meanings close together.

Approximate nearest-neighbour (ANN) over exact comparison against every chunk

Exact search over millions of high-dimensional vectors is too slow per query; ANN gives near-exact recall at sub-linear cost.

A small top-k, then rerank over stuffing as many chunks as fit

Irrelevant chunks distract the model (“lost in the middle”), raise cost, and slow generation — precision beats volume.

A cross-encoder reranker over trusting the vector-similarity order

Bi-encoder similarity is a fast approximation, and the genuinely best passage often isn’t the nearest vector; a cross-encoder reads query and chunk together for true relevance.

The answer, out loud

What a strong answer to “Design a RAG Pipeline” sounds like, first question to last trade-off. It is about 6 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    Pin down what the model can’t know

    Before I draw anything, I want to pin down the problem. A model only knows what it was trained on, and our users are asking about internal docs, last week’s release, a private wiki — things it never saw. Ask anyway and it confidently makes something up. So the requirement is an answer drawn from our own documents, current, with sources a user can check. That changes the real question from “did the model memorize this?” to “did we retrieve the right context?” — and that one we can engineer.

  2. 3–7 min

    Retrieve first, then generate

    The skeleton is a user, a RAG gateway and an LLM. The gateway orchestrates every question: retrieve the most relevant passages, then have the model answer from them. Sending the question straight to the model is the failure we’re here to fix. Fine-tuning it on every document is the tempting alternative, but that’s slow and expensive, can’t cite a source, and goes stale the moment a doc changes. Retrieval keeps knowledge outside the model, where it stays current and citable.

    Built in step 1: Retrieve, then generate
  3. 7–14 min

    Prepare the knowledge offline

    Retrieval needs something to search, so an offline ingestion pipeline loads each document and splits it into chunks of roughly three to eight hundred tokens — on paragraphs and sections, with ten to fifteen percent overlap so an idea crossing a boundary isn’t cut in half. Each chunk goes through an embedding model, which maps text to a point in space where similar meanings sit close together. Vectors go into a vector store; the text and metadata — source, title, URL — into a document store for citations. A keyword index would miss paraphrase: “how do I cancel” never matches “terminating your subscription.” One rule I’d say out loud: the same embedding model embeds chunks and queries, or they aren’t comparable.

    Built in step 2: Chunk and embed your documents
  4. 14–19 min

    Search by meaning, approximately

    At query time the retriever embeds the question with that same model and asks for the nearest chunks. Comparing against millions of vectors exactly is too slow per query, so I’d use an approximate nearest-neighbor index — HNSW or IVF — that answers in milliseconds. The cost is a sliver of recall, meaning the chance the right chunk is anywhere in what we fetch. I’d tune the index, and k — how many chunks we fetch — against latency, and keep keyword search as a later hybrid refinement, not a replacement.

    Built in step 3: Embed the query, search by similarity
  5. 19–24 min

    Decide how much to fetch

    How many chunks? Stuffing in as many as fit sounds safe, but irrelevant chunks distract the model — the “lost in the middle” problem — and add cost and delay. One chunk is too brittle: answers often span two or three passages. So I split the job: fetch a modest candidate set, around twenty, tuned for recall, then narrow it for precision, so only the best few reach the model.

    Built in step 4: Top-k and the recall/precision dial
  6. 24–30 min

    Sharpen the shortlist

    Vector similarity is fast but coarse; the truly best chunk might sit at position eight. So a reranker re-scores those twenty. It’s a cross-encoder: rather than comparing two separately computed vectors, it reads the question and a chunk together and scores how well that chunk answers it. Far more accurate, and far too slow for the whole store — which is why it only sees the shortlist and keeps the best three to five. Just retrieving more wouldn’t help; that raises recall, not precision. The cost is an extra model on the critical path, which I’d keep fast with batching or a smaller distilled reranker.

    Built in step 5: Rerank for precision
  7. 30–36 min

    Ground it, and let it say no

    The gateway then builds the prompt: system instructions, the reranked chunks, the question. Two instructions do the heavy lifting — answer only from these passages, and if they don’t cover it, say so. Pasting all twenty candidates back in would undo the reranking. Chunks carry their source metadata, so the answer ships with citations a user can check. And “I don’t know” is a feature: when even the best relevance score falls below a calibrated threshold, the model declines rather than stretching a weak match.

    Built in step 6: Build the prompt and generate
  8. 36–42 min

    What I’d watch, and how it fails

    RAG fails quietly — nothing errors, the answer just goes subtly wrong — so the dashboard is evals: retrieval recall, did we fetch the right chunks, and faithfulness, did the answer stick to them. Two failures I’d plan for. The index goes stale: retrieval still returns chunks, but from last week’s documents, and the model grounds confidently on them — so ingestion is an ongoing, versioned system, not a one-off script. Or someone upgrades the query embedding model without re-embedding the stored chunks; the two spaces aren’t comparable, so search returns near-random chunks with real-looking scores. An embedder change is a full reindex.

  9. 42–45 min

    Close on the trade-off

    To close, in one breath: ingestion that chunks and embeds, one embedding model on both sides, approximate search for a wide candidate set, a cross-encoder to narrow it, a grounded prompt with citations, and evals watching for drift. Every knob — chunk size, k, how many survive reranking — trades recall against precision and cost. With more time I’d go to permissions, filtering to what a user may see during the search rather than after it, and to tables and images, where plain chunking quietly breaks.

What this teaches

Learn AI system design by building a retrieval-augmented generation (RAG) pipeline step by step. An interactive guide covering document chunking, embeddings, the vector store, top-k retrieval, reranking, grounded prompt assembly, and keeping the index fresh — so an LLM answers from your knowledge, not its training data.

Key takeaways

  • RAG Gateway — orchestrates retrieve → augment → generate
  • Ingestion — chunk + embed documents, offline and ongoing
  • Embeddings — meaning as coordinates — same model for chunks + queries
  • Vector Store — ANN search returns top-k by similarity in ms
  • Top-k — wide for recall, then narrowed for precision
  • Reranker — cross-encoder sharpens the shortlist to the best few
  • Grounded prompt — answer only from context, cite, decline if thin
  • Evals + citations — make a quiet failure mode visible and auditable

Concepts covered

  • Why retrieval at all?
  • Retrieve, then generate
  • Chunk and embed your documents
  • Embed the query, search by similarity
  • Top-k and the recall/precision dial
  • Rerank for precision
  • Build the prompt and generate
  • Citations, "I don’t know", and evals
RUN IT YOURSELF

The chunk boundary that eats the answer

The whole retrieval half of this page in one runnable file — chunk with overlap, embed, cosine, top-k — running for real in your browser. It chunks one policy document two ways and prints what each index can and cannot answer. Change overlap, hit Run, and watch a fact leave the index entirely.

HOW TO READ THE CODE — 4 IDEAS
  1. Chunking moves in a stride of size - overlap; at 0% overlap the windows are disjoint (step 2).
  2. The sale rule spans a boundary, so at 0% overlap no chunk holds it whole — a recall ceiling of zero.
  3. No k, no reranker and no better embedder retrieves a fact that was never stored intact.
  4. At 50% overlap it survives — and still only ranks 2nd. That is the case for fetching wide, then reranking (steps 4–5).
CPython · WebAssembly
built to be reasoned about, not memorized — make the calls, poison the index, run the quiz.
Finished this one? 0 / 61 AI System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs