IntermediateEstimated time: 40 mins
Hybrid Search (BM25 + Dense Vectors)
Combine lexical keyword search (BM25) with semantic dense vector search using Reciprocal Rank Fusion (RRF).
Tech Stack:PythonRank-BM25ChromaDB / FAISSLangChain EnsembleRetriever
System Architecture
Query ➔ [BM25 Keyword Retriever + Dense Vector Retriever] ➔ Reciprocal Rank Fusion (RRF) ➔ Top Re-ranked Chunks ➔ LLM
Prerequisites
- Understanding of TF-IDF / BM25 term weighting
- Understanding of Dense Embedding Cosine Distance
Installation Commands
terminalbash
pip install rank-bm25 chromadb langchain langchain-community langchain-openaiFolder Structure
Directory Layouttext
hybrid-rag/
├── hybrid_pipeline.py
└── sample_docs.jsonStep-by-Step Implementation
1
Initialize BM25 and Dense Retrievers
Create both BM25Retriever (for precise keyword matches, acronyms, and error codes) and VectorStoreRetriever (for conceptual similarity).
hybrid_pipeline.pypython
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.retrievers import EnsembleRetriever
from langchain_core.documents import Document
docs = [
Document(page_content="Error code 0x80070005 indicates Access Denied in Windows permissions."),
Document(page_content="Permission issues often occur when services lack administrative privileges."),
Document(page_content="Database connection timeout error 504 gateway response from Nginx."),
]
# 1. BM25 Lexical Retriever
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 2
# 2. Dense Semantic Vector Retriever
embeddings = OpenAIEmbeddings()
chroma_db = Chroma.from_documents(docs, embeddings)
dense_retriever = chroma_db.as_retriever(search_kwargs={"k": 2})
# 3. Ensemble Retriever with Reciprocal Rank Fusion (50% weight each)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, dense_retriever],
weights=[0.5, 0.5]
)
# Test exact keyword query
results = ensemble_retriever.invoke("0x80070005")
for r in results:
print(r.page_content)Example Execution (Input & Output)
Input Query:
Query: "0x80070005 access error"
Generated Grounded Output:
Rank 1 Document: "Error code 0x80070005 indicates Access Denied in Windows permissions." (Score boosted by exact BM25 match)
Why this works: Dense embeddings often struggle with arbitrary alphanumeric error codes, but BM25 pins them directly.
Troubleshooting Common Gotchas
BM25 returns irrelevant results due to common stop words
Configure custom tokenizers or stopword filters in Rank-BM25.
Known Limitations
- •Requires maintaining both an inverted keyword index and a vector index in sync.
Security Considerations
- •Ensure metadata filtering applies equally to both the BM25 index and the vector store.