BM25 vs Vector Search: When to Use Which in RAG
Retrieval-Augmented Generation (RAG) has become the standard architecture for grounding large language models in private or domain-specific data. But the quality of a RAG system is only as good as its retrieval layer. If the retriever surfaces the wrong chunks, the generator cannot produce a correct answer — no matter how capable the LLM. Two retrieval methods dominate the landscape: BM25, a classical lexical algorithm, and vector search, a modern embedding-based approach. Understanding when to use each — and how to combine them — is one of the most impactful decisions you can make when building a RAG pipeline.
What Is BM25?
BM25 (Best Matching 25) is a probabilistic ranking function that extends the classic TF-IDF approach. It scores documents based on how often query terms appear in a document, while normalizing for document length and term frequency saturation. Unlike vector embeddings, BM25 operates purely on exact token matching. If the user searches for "GPT-4o", BM25 will strongly favor documents that contain that literal string.
The BM25 scoring formula balances three signals: term frequency (how often a term appears in a document), inverse document frequency (how rare the term is across the corpus), and document length normalization (to prevent longer documents from being unfairly favored). This makes BM25 remarkably robust for keyword-heavy queries, product names, error codes, identifiers, and any scenario where exact terminology matters.
What Is Vector Search?
Vector search works by embedding both documents and queries into a high-dimensional semantic space using a model such as OpenAI's text-embedding-3-small, Cohere's embed-v3, or open-source alternatives like BGE and E5. Retrieval is performed by computing the similarity — typically cosine similarity — between the query vector and document vectors. The closest vectors in semantic space are returned as results.
The strength of vector search lies in its ability to match on meaning rather than wording. A query like "how do I reset my password" can retrieve a document titled "credential recovery procedure" even though no words overlap. This semantic matching is invaluable for natural language questions, paraphrased queries, and conceptual lookups.
Why the Choice Matters in RAG
The retrieval method you choose directly determines which chunks reach the LLM context window. Each method has characteristic failure modes:
- BM25 fails on synonyms and paraphrases. A user asking about "employee turnover" will miss documents that only mention "staff attrition".
- Vector search fails on rare terms and identifiers. Embedding models compress text into general semantic representations, which can wash out specific product codes, version numbers, or proper nouns.
- BM25 is transparent and debuggable. You can inspect exactly which terms drove a match.
- Vector search requires re-embedding when models change. Switching embedding models means re-indexing the entire corpus.
In real-world RAG systems, blindly choosing one method over the other leads to predictable blind spots. The most effective pipelines understand the tradeoffs and often combine both.
How to Use BM25 in a RAG Pipeline
BM25 is lightweight, requires no GPU, and can be implemented with libraries like rank_bm25 in Python or via full-text search engines like Elasticsearch, OpenSearch, and Postgres' built-in ts_vector capabilities. Below is a minimal implementation using rank_bm25.
from rank_bm25 import BM25Okapi
documents = [
"The GPT-4o model supports 128k context window.",
"Claude 3.5 Sonnet excels at coding tasks.",
"To reset your password, click the recovery link.",
"GPT-4o pricing is $5 per million input tokens.",
"Credential recovery requires email verification."
]
# Tokenize documents (simple whitespace split for demo)
tokenized_docs = [doc.lower().split() for doc in documents]
bm25 = BM25Okapi(tokenized_docs)
query = "GPT-4o pricing"
tokenized_query = query.lower().split()
scores = bm25.get_scores(tokenized_query)
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
for doc, score in ranked:
print(f"{score:.4f} {doc}")
Notice how BM25 cleanly surfaces the two documents mentioning "GPT-4o" and "pricing". A vector model might have ranked the Claude document higher due to semantic similarity around "models" and "tokens". For queries involving specific product names, BM25 is hard to beat.
How to Use Vector Search in a RAG Pipeline
Vector search requires an embedding model and a vector store. For production systems, dedicated vector databases like Pinecone, Weaviate, Qdrant, or Milvus are common. For smaller projects, pgvector or FAISS work well. Here is a self-contained example using sentence-transformers and FAISS.
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
documents = [
"The GPT-4o model supports 128k context window.",
"Claude 3.5 Sonnet excels at coding tasks.",
"To reset your password, click the recovery link.",
"GPT-4o pricing is $5 per million input tokens.",
"Credential recovery requires email verification."
]
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
embeddings = model.encode(documents, normalize_embeddings=True)
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension) # inner product = cosine on normalized vectors
index.add(embeddings.astype(np.float32))
query = "how do I get back into my account"
query_vec = model.encode([query], normalize_embeddings=True).astype(np.float32)
distances, indices = index.search(query_vec, k=3)
for rank, idx in enumerate(indices[0]):
print(f"{distances[0][rank]:.4f} {documents[idx]}")
Here the query "how do I get back into my account" has zero word overlap with "Credential recovery requires email verification", yet vector search retrieves it correctly because the embedding model captures the semantic intent. This is exactly the scenario where BM25 would fail.
Hybrid Search: The Best of Both Worlds
Modern RAG systems increasingly use hybrid retrieval, which combines BM25 and vector scores into a single ranked list. The most common fusion algorithm is Reciprocal Rank Fusion (RRF), which combines rankings rather than raw scores — making it robust to the different score distributions of each method.
def reciprocal_rank_fusion(bm25_ranked, vector_ranked, k=60):
"""
bm25_ranked and vector_ranked are lists of document indices
ordered from most to least relevant.
"""
rrf_scores = {}
for rank, doc_id in enumerate(bm25_ranked):
rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + 1 / (k + rank + 1)
for rank, doc_id in enumerate(vector_ranked):
rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(rrf_scores, key=rrf_scores.get, reverse=True)
# Example usage
bm25_ranked = [0, 3, 1, 2, 4] # from BM25 search
vector_ranked = [2, 4, 0, 1, 3] # from vector search
fused = reciprocal_rank_fusion(bm25_ranked, vector_ranked)
print("Fused ranking:", fused)
RRF is simple, parameter-light, and works well in practice. Many vector databases now offer hybrid search out of the box — Weaviate, Qdrant, and Pinecone all support combining keyword and vector retrieval natively, often with configurable weighting.
When to Use Which: A Decision Guide
Use BM25 When
- Queries contain specific identifiers: product codes, error messages, version numbers, SKUs, or proper nouns.
- The corpus is small and you need fast, deterministic results without embedding infrastructure.
- You need explainable rankings for debugging or compliance.
- The domain has precise vocabulary where synonyms would cause false matches (legal, medical coding, parts catalogs).
- Users are power users who type keyword-style queries rather than natural language questions.
Use Vector Search When
- Queries are natural language questions that may paraphrase the source content.
- The corpus spans diverse terminology for the same concepts.
- You need cross-lingual retrieval, since multilingual embedding models can match across languages.
- Users ask conceptual or exploratory questions like "what are the risks of this approach".
- The content is long-form prose rather than structured records.
Use Hybrid Search When
- You are building a general-purpose RAG system serving diverse query types.
- You cannot predict in advance whether a query will be keyword-based or semantic.
- You want maximum recall without tuning per-query routing logic.
- Production reliability matters more than minimal infrastructure cost.
Best Practices for RAG Retrieval
Beyond choosing a retrieval method, several engineering practices significantly improve RAG retrieval quality:
- Chunk thoughtfully. Retrieval quality depends on chunk size. Too small and context is lost; too large and relevance signals dilute. Aim for 200-500 tokens with meaningful overlap, and consider document-aware chunking that respects headings and sections.
- Add metadata filters. Use hybrid retrieval with metadata filters (date, source, category) to narrow the candidate pool before semantic or lexical scoring. This often improves precision more than any algorithm tweak.
- Re-rank with a cross-encoder. After retrieving 20-50 candidates with BM25 or vector search, re-rank the top results with a cross-encoder model like
bge-reranker-v2-m3. Cross-encoders jointly encode query and document, capturing interactions that bi-encoders miss. - Evaluate with retrieval metrics. Measure recall@k and precision@k on a labeled evaluation set. Without measurement, you are guessing. Frameworks like RAGAS and TruLens can automate this.
- Query expansion helps both methods. Use an LLM to expand queries with synonyms or sub-questions before retrieval. This boosts BM25 recall and vector search precision simultaneously.
- Keep embedding models and chunking strategy in sync. If you change embedding models or chunk size, you must re-index. Version your indexes to allow rollback.
- Log and inspect retrieval results. Build a UI that shows which chunks were retrieved for each query. Most RAG failures are retrieval failures, not generation failures.
Conclusion
BM25 and vector search are not competing technologies — they are complementary tools that excel in different retrieval scenarios. BM25 remains unmatched for exact keyword matching, identifiers, and transparent ranking, while vector search enables semantic understanding that bridges vocabulary gaps. For most production RAG systems, the right answer is hybrid retrieval combining both methods, ideally followed by a cross-encoder re-ranker and informed by rigorous evaluation. By understanding the strengths and failure modes of each approach, you can build retrieval pipelines that surface the right context consistently, giving your LLM the grounding it needs to produce accurate, trustworthy answers.