RAGEngineering Hub
Chapter 2 · concept 2 of 2 · show all
  1. 1What RAG is, and why
    1. 1.1Why models need outside knowledge, and what hallucination means
    2. 1.2What RAG is: anatomy of a request
    3. 1.3RAG vs fine tuning vs long context and prompt caching
    4. 1.4Use cases and the quality, latency and cost triangle
    5. Chapter overview
  2. 2Tokens, cost and your first RAG
    1. 2.1Tokens and context windows for RAG
    2. 2.2The RAG lifecycle and your first RAG in miniature
    3. Chapter overview
  3. 3Getting documents in
    1. 3.1Content sources and file formats
    2. 3.2Parsing, layout and OCR
    3. 3.3Tables, images and mixed content
    4. 3.4Cleaning, normalization and quality gates
    5. 3.5Metadata, document identity and versioning
    6. 3.6Updates, deletes, reindexing and ingestion pipelines
    7. Chapter overview
  4. 4Chunking
    1. 4.1Why we chunk, and the main chunking strategies
    2. 4.2Structure aware chunking and tables
    3. 4.3Chunk size, overlap and token based sizing
    4. 4.4Contextual chunking and late chunking
    5. 4.5Parent child and multi representation indexing
    6. 4.6Chunk metadata, provenance and the chunking debugging playbook
    7. Chapter overview
  5. 5Embeddings
    1. 5.1Embeddings and similarity from first principles
    2. 5.2What embeddings cannot do
    3. 5.3Choosing an embedding model: size, cost, language
    4. 5.4Embedding model migration and index rebuilds
    5. Chapter overview
  6. 6Vector stores and scores
    1. 6.1Vector databases, ANN indexes and choosing a store
    2. 6.2Score calibration and thresholds
    3. 6.3Late interaction and multivector retrieval
    4. Chapter overview
  7. 7Finding the right chunks
    1. 7.1Query understanding, rewriting and expansion
    2. 7.2Dense retrieval and keyword search with BM25
    3. 7.3Hybrid retrieval and rank fusion
    4. 7.4topK, no good match, and metadata filtering
    5. 7.5Routing, diversity, deduplication and caching
    6. 7.6Reranking: two stage, cross encoder and LLM rerankers
    7. Chapter overview
  8. 8When retrieval fails
    1. 8.1Retrieval failure analysis playbook
    2. Chapter overview
  9. 9Building the answer
    1. 9.1Context selection, packing and citations
    2. 9.2Grounding prompts and abstention
    3. 9.3Lost in the middle and context management
    4. 9.4Conflicts, staleness and freshness rules
    5. 9.5Hallucination checks and answer verification
    6. 9.6Conversational RAG, answer UX and RAG APIs
    7. Chapter overview
  10. 10Measuring
    1. 10.1Why measure, and what to measure
    2. 10.2Building a golden dataset
    3. 10.3Retrieval metrics: precision, recall, MRR, nDCG
    4. 10.4Faithfulness and answer correctness
    5. 10.5LLM as judge, and judging the judge
    6. 10.6Offline and online evals, regression tests and slices
    7. Chapter overview
  11. 11Safety and operations
    1. 11.1Prompt injection and poisoned documents
    2. 11.2Access control, privacy and governance
    3. 11.3Index lifecycle operations
    4. 11.4Observability and tracing
    5. 11.5Latency, cost, caching and model routing
    6. 11.6Reliability, degraded modes and launch readiness
    7. Chapter overview
  12. 12Scale and advanced RAG
    1. 12.1Scaling, multi tenancy and enterprise architecture
    2. 12.2Choosing an architecture: naive, advanced, agentic, graph
    3. 12.3Agentic RAG and RAG as an MCP tool
    4. 12.4GraphRAG, structured data and multimodal RAG
    5. Chapter overview

The RAG lifecycle and your first RAG in miniature

The two halves of every RAG system, building the index and answering a question, shown end to end on a small corpus you can run in the browser.

A real question

What are the parts of a RAG system, and which one do I check when the answer is wrong?

Two halves. Offline you build an index of your documents; online you search it and answer. Check each half on its own.

The problem

Why this matters

RAG is one library call: give it documents and a question, and the answer comes out.
The belief

RAG is one library call: give it documents and a question, and the answer comes out.

It is a pipeline of steps. When you can see each one, you can tell whether a wrong answer came from search or from the model.
What really happens

It is a pipeline of steps. When you can see each one, you can tell whether a wrong answer came from search or from the model.

Why not the simpler way

The simple way to try RAG is to install a framework, call one function and get an answer. It works, and you learn nothing about why it fails. When the answer is wrong you cannot tell if the document was never indexed, the right chunk was not retrieved, or the model ignored it. Seeing each step on its own is what makes RAG debuggable.

The idea

The idea in plain words

Every RAG system has two halves. Offline, you build an index: load the documents, cut them into chunks, turn each chunk into an embedding, and store both the vectors and a keyword index. Online, for each question, you search that index, put the best chunks into a prompt, and let the model answer from them.

It is a library with a good card catalogue. The catalogue is built before anyone walks in. When someone asks a question, the librarian looks it up, pulls three books, and answers from those pages.

How it works

Step by step

  1. Load the documents, cut them into chunks, turn each chunk into numbers, and store both the numbers and a keyword index.
    1

    Offline: build the index

    Load the documents, cut them into chunks, turn each chunk into numbers, and store both the numbers and a keyword index.

  2. Keyword search matches the exact words. Vector search matches the meaning. Merging the two lists covers both.
    2

    Online: search two ways

    Keyword search matches the exact words. Vector search matches the meaning. Merging the two lists covers both.

  3. The best chunks go into the prompt, and the model answers from them with citations.
    3

    Answer from the top chunks

    The best chunks go into the prompt, and the model answers from them with citations.

In detail

RAG pipeline: documents are loaded, chunked, embedded and indexed offline; at question time the index is searched, the best chunks are packed into the prompt, and the model writes a cited answer.OFFLINE · BUILD THE INDEXLoadPDF, HTML, MDChunk256–512 tokensEmbedtext → vectorIndexvectors + BM25ONLINE · ANSWER A QUESTIONQuestionuser asksRetrievetop k chunksPack promptwithin budgetGeneratecited answersearch the index
Quality is decided offline more often than online: a chunk that splits the answer cannot be rescued by a better prompt.

Retrieval in the lab is hybrid. BM25 scores chunks by shared words, so exact codes like E4012 match. Vector search scores chunks by meaning, so "my internet keeps dropping" can match a section titled "unstable connection". Reciprocal rank fusion merges the two lists by position, and the top 3 go into the prompt.

Hybrid retrieval: the question is searched by keyword (BM25) and by meaning (vectors); reciprocal rank fusion merges the two ranked lists by position; the top 3 chunks go into the prompt.Question“E4012?”Keyword · BM25exact words, codesVector searchsimilar meaningFuse · RRF1 / (60 + rank)Top 3into the promptranked list Aranked list B
Fusion uses only each chunk's position in the two lists, so a BM25 score of 9.7 and a cosine of 0.67 never need to be compared.

The prompt tells the model to answer only from the sources, cite them as [1], [2], and say it does not know when the sources do not cover the question.

The code

Try it in Python

# pip install rank_bm25 sentence-transformers
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer

chunks = ["E4012: mesh pairing failed. Update the extender to 2.8.1.", "..."]
bm25 = BM25Okapi([c.lower().split() for c in chunks])
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
vecs = model.encode(chunks, normalize_embeddings=True)
PREFIX = "Represent this sentence for searching relevant passages: "

def retrieve(q: str, k: int = 3) -> list[str]:
    by_kw = bm25.get_scores(q.lower().split()).argsort()[::-1][:10]
    by_vec = (vecs @ model.encode(PREFIX + q, normalize_embeddings=True)).argsort()[::-1][:10]
    score = {}
    for ranked in (by_kw, by_vec):            # reciprocal rank fusion, k = 60
        for rank, i in enumerate(ranked):
            score[i] = score.get(i, 0) + 1 / (60 + rank + 1)
    return [chunks[i] for i in sorted(score, key=score.get, reverse=True)[:k]]

See it

What we measured

Measured · evidence recall@3

Polder corpus (25 documents), 47 golden questions, recursive 512 token chunks, bge-small-en-v1.5 vectors. A hit means a top 3 chunk holds the whole answer passage.

Vector search onlybaseline0.936 (44 of 47)
BM25 only0.9361.000 (47 of 47)
Fused with RRF0.9360.979 (46 of 47)

Measured 2026-11-03 with the Mini RAG Lab logic. On this corpus the questions reuse exact codes and names, so keyword search alone is best. Fusion still beats vector search alone. Expect a different order on paraphrased questions; measure on your own data.

Try it on a real example

A real question: “How much notice is given for a price change during a fixed term?”

Search with
1.Tariff sheet 2026.1 · 15.5…Price changes during a fixed term are announced at least 30 days in advance by e-mail.✓
2.Tariff sheet 2025.2 · 9.2An older tariff sheet.–
3.Tariff sheet 2025.1 · 8.9An even older one.–

The words “price change” and “fixed term” match exactly, so keyword search puts the answer first.

Choosing

Use it when, avoid it when

Use it when
  • Any question answering over your own documents that change or are too many to paste
  • Hybrid retrieval as the default: keyword for exact codes, vectors for paraphrases
Avoid it when
  • A framework one-liner as your only view of the system: you cannot see which step failed
  • Vector search alone when users type ids, codes or product names

What it costs · Offline: one embedding per chunk, repeated only for changed chunks. Online: one query embedding, two searches and one model call with about 1,100 input tokens per question here.

In production

Measure each half on its own. A golden set of questions with a known answer passage tells you whether retrieval found that passage, before any model is involved. Only then judge the answers. Most bad answers in RAG are retrieval misses, and you cannot fix those with a better prompt.

Keep the two halves loosely coupled. The index is rebuilt when documents change; the online path should not need a redeploy for that.

Watch out

Common mistakes

  • Vector search is the modern way, so keyword search is obsolete. On this corpus BM25 alone found every answer passage.
  • Fusion always wins. It was one question behind BM25 here. It is the safe default when you do not know your query mix.
  • A wrong answer means the model is bad. Check retrieval first: was the answer passage in the prompt?

Check yourself

Remember this

Remember these

  • Offline: load, chunk, embed and index. Online: search, pack the prompt, answer.
  • Keyword search is strong on exact codes and terms; vector search on meaning.
  • Merging both lists (hybrid search) is the safe default.
  • Check retrieval on its own before you blame the model.

Say it in an interview

  1. 01RAG has an offline half that builds the index and an online half that answers.
  2. 02I measure retrieval on its own with evidence recall before judging answers.
  3. 03Hybrid retrieval: BM25 for exact codes, vectors for meaning, RRF to merge by rank.
  4. 04On my corpus BM25 found 47 of 47, vectors 44 and fusion 46, so I measure, not assume.
  5. 05Most bad RAG answers are retrieval misses, which a better prompt cannot fix.
Try it yourself: Mini RAG LabAsk a question and see keyword and vector search, the prompt, and the answer.

Quick quiz

Three questions

Question 1

Q1

On the Polder corpus, which retriever found the answer passage in its top 3 most often?

Question 2

Q2

Which work happens offline, before any user asks?

Question 3

Q3 · from RAG-03

The prompt has 3 retrieved chunks. Which part takes most of its tokens?

Saved on this device. No account needed.