RAGEngineering Hub
Chapter 7 · concept 2 of 6 · 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

Dense retrieval and keyword search with BM25

How BM25 scores shared rare words, how dense retrieval scores meaning, and a head to head on our 47 questions where the 30 year old method won, with the reason why.

A real question

Vector search understands meaning. Is keyword search still worth having?

Yes. On our corpus BM25, a keyword method from the 1990s, found all 47 answers in the top 3 and vector search found 44. Each catches questions the other misses.

The problem

Why this matters

Vector search replaced keyword search.
The belief

Vector search replaced keyword search.

BM25 put the answer in the top 3 for 47 of 47 questions and first for 35. Vector search: 44 and 30. Rare exact words like “excess”, “RTO” or “E4012” are BM25's strength.
What really happens

BM25 put the answer in the top 3 for 47 of 47 questions and first for 35. Vector search: 44 and 30. Rare exact words like “excess”, “RTO” or “E4012” are BM25's strength.

Why not the simpler way

After learning embeddings it is tempting to drop keyword search. Then questions about a product code, a regulation number or an unusual term start failing, because the vector smooths them into a topic. Understanding both methods tells you what each one is for.

The idea

The idea in plain words

BM25 is a keyword ranking function. For every word in the question it asks: does this chunk contain it, how often, how rare is the word across all chunks, and how long is the chunk? It adds that up. Dense retrieval embeds question and chunks and ranks by cosine (RAG-19).

BM25 is a search for an exact name in a phone book. Dense retrieval is asking a friend "who was that plumber who fixed the leak?".

How it works

Step by step

  1. Each word the question and chunk share adds to the score, more if the word is rare in the corpus, less if the chunk is long.
    1

    BM25 counts rare shared words

    Each word the question and chunk share adds to the score, more if the word is rare in the corpus, less if the chunk is long.

  2. Dense retrieval compares embeddings, so “restart modem” finds “reboot the router” with no shared words.
    2

    Vectors match meaning

    Dense retrieval compares embeddings, so “restart modem” finds “reboot the router” with no shared words.

  3. BM25 misses paraphrases; vectors blur codes and rare terms. That is why most systems run both (RAG-28).
    3

    Each covers the other's gap

    BM25 misses paraphrases; vectors blur codes and rare terms. That is why most systems run both (RAG-28).

In detail

The BM25 formula, in words. For each query word: IDF (rarer words count more) times a saturating term frequency (the 5th mention adds less than the 1st), divided by a length factor (long chunks are penalized). Two settings: k1 (how fast frequency saturates, we use 1.2) and b (how strongly length matters, we use 0.75).

Tokenization matters. BM25 sees words as your tokenizer splits them. Our tokenizer splits on hyphens, so "PT-MOB-25GB" becomes three tokens; that still matched here, but a tokenizer that drops digits or short tokens would lose codes like "E4012" or "1012". Stemming and stop words change results; test them.

Dense retrieval needs an embedding model and an index (RAG-22); BM25 needs an inverted index, which every search engine has.

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 code

Try it in Python

import math, re
from collections import Counter

def tokens(text: str) -> list[str]:
    return re.findall(r"[a-z0-9][a-z0-9-]*", text.lower())   # keeps E4012 and PT-MOB-25GB whole

def bm25_index(chunks, k1=1.2, b=0.75):
    docs = [Counter(tokens(c)) for c in chunks]
    avg = sum(sum(d.values()) for d in docs) / len(docs)
    df = Counter(t for d in docs for t in d)
    idf = {t: math.log(1 + (len(docs) - n + 0.5) / (n + 0.5)) for t, n in df.items()}
    def score(query: str):
        q = tokens(query)
        out = []
        for d in docs:
            L = sum(d.values())
            out.append(sum(idf.get(t, 0) * d[t] * (k1 + 1) / (d[t] + k1 * (1 - b + b * L / avg)) for t in q))
        return out
    return score

See it

What we measured

Measured · retrieval on 47 golden questions

Polder corpus, 34 recursive 512 token chunks. BM25: k1 1.2, b 0.75, lowercase, split on anything that is not a letter or digit (so PT-MOB-25GB becomes pt, mob, 25gb). Vector: bge-small. A hit is a chunk holding the evidence quote.

Answer ranked firstvector 30BM25 35
Answer in top 3vector 44BM25 47
Mean reciprocal rankvector 0.784BM25 0.862
nDCG@5vector 0.831BM25 0.897
Questions where vector ranked the answer higher5 of 47

Measured 2026-11-04 with corpus/scripts/measure-parts-4-7.mts. The golden questions were written while reading the documents, which favours keyword overlap.

Try it on a real example

“Is there an excess for storm damage?”

Search with
1.General conditions HC-2026 #0 · 0.670Article 4, covered events.–
2.Endorsement E-12 · 0.639Flood cover.–
3.Product information document · 0.618What type of insurance this is.–
6.Claims handbook #1 · 0.598“There is no excess for damage caused by storm, lightning or fire.”✓

Vector search finds the insurance documents, but the one sentence with the answer is sixth.

Choosing

Use it when, avoid it when

Use it when
  • BM25 for codes, IDs, names and rare terms
  • Dense retrieval for paraphrases, synonyms and other languages
  • Both, fused, as the default (RAG-28)
Avoid it when
  • Dropping BM25 because vectors feel modern
  • Judging either on questions written from the documents only

What it costs · BM25: an inverted index, no model. Dense: an embedding model and a vector index.

In production

BM25 is cheap, explainable (you can show which words matched) and needs no model. It fails on synonyms, other languages and vague questions. Dense retrieval fails on exact identifiers and rare terms. In production, measure both on your golden set, split by slice: codes, paraphrases, languages. Add paraphrased questions to the golden set on purpose, because questions written from the documents flatter BM25.

Watch out

Common mistakes

  • BM25 is obsolete. It beat vectors on our corpus.
  • Vector search finds everything BM25 finds. It ranked the storm excess sentence sixth.
  • BM25 won, so drop vectors. Vectors ranked the answer higher on 5 questions, and real users paraphrase more than our golden set.

Check yourself

Remember this

Remember these

  • BM25 scores shared words, weighted by rarity and chunk length.
  • Dense retrieval scores meaning and handles paraphrases.
  • On our 47 questions: BM25 47 in the top 3, vectors 44.
  • Our questions share words with the documents; real users paraphrase more.

Say it in an interview

  1. 01BM25 scores shared words by rarity, frequency and chunk length; vectors score meaning.
  2. 02On my corpus BM25 put 47 of 47 answers in the top 3 and vectors 44.
  3. 03BM25 wins on rare exact terms like excess, RTO or product codes.
  4. 04My golden questions share words with the documents, so I add paraphrases to be fair.
  5. 05I run both and fuse them, because they fail on different questions.
Try it yourself: Retrieval LabKeyword, vector and hybrid search side by side, with the rank of the answer in each.

Quick quiz

Three questions

Question 1

Q1

Why did BM25 find "Is there an excess for storm damage?" at rank 1 while vector search had it at rank 6?

Question 2

Q2

What is the main caveat of our result that BM25 found 47 of 47?

Question 3

Q3 · from RAG-26

What is the safest way to try a query rewrite?

Saved on this device. No account needed.