RAG & LLMs / 5. RETRIEVAL

Stage 5: Retrieval

Hybrid BM25 + Dense — best of keyword and semantic search


EXPLANATION

A Retriever wraps your vector store with smarter logic. Plain vector search misses exact keyword matches. BM25 (TF-IDF style) catches exact terms but misses semantics. Hybrid does both.

Retriever types in LangChain:
• VectorStoreRetriever → basic cosine similarity
• BM25Retriever → keyword-based (sparse), catches exact matches
• EnsembleRetriever → combines BM25 + dense with RRF fusion
• MultiQueryRetriever → generates query variants, merges results
• ContextualCompressionRetriever → retrieves then compresses to relevant sentences only

DATA FLOW

Query: "How do transformers handle long sequences?"
                     ↓
     ┌───────────────┴───────────────┐
     │                               │
  BM25 (keyword)             Dense (semantic)
  finds: "transformer"       finds: related concepts
  finds: "long sequences"    even without exact words
     │                               │
     └───────────────┬───────────────┘
                     ↓
            Reciprocal Rank Fusion
                     ↓
             Top-10 merged chunks

CODE

PYTHON
1from langchain.retrievers import EnsembleRetriever, MultiQueryRetriever
2from langchain_community.retrievers import BM25Retriever
3from langchain_google_genai import ChatGoogleGenerativeAI
4
5llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", google_api_key="YOUR_KEY")
6
7# ── BM25 retriever (keyword / sparse) ────────────────────────────
8bm25 = BM25Retriever.from_documents(chunks)
9bm25.k = 10
10
11# ── Dense retriever (semantic / embedding-based) ──────────────────
12dense = vectorstore.as_retriever(
13 search_type="similarity",
14 search_kwargs={"k": 10},
15)
16
17# ── Hybrid: BM25 40% + Dense 60% (EnsembleRetriever uses RRF) ─────
18hybrid_retriever = EnsembleRetriever(
19 retrievers=[bm25, dense],
20 weights=[0.4, 0.6],
21)
22
23# ── MultiQueryRetriever: query expansion ──────────────────────────
24# Generates 3 paraphrases of the query, merges all results
25multi_retriever = MultiQueryRetriever.from_llm(
26 retriever=hybrid_retriever,
27 llm=llm,
28 include_original=True,
29)
30
31# ── Test retrieval ────────────────────────────────────────────────
32query = "What is backpropagation in neural networks?"
33docs = hybrid_retriever.invoke(query)
34print(f"Retrieved {len(docs)} chunks from hybrid retriever")
35for i, doc in enumerate(docs[:3]):
36 print(f"[{i+1}] {doc.page_content[:150]}...")
← PREV4. Vector StoreNEXT →6. Cross-Encoder Reranking