Skip to content

Retrieval

Retrieval is the step that finds the most relevant chunks for a query. The quality of retrieval directly determines answer quality — you can't generate a good answer from bad context. This covers similarity search, diversity, hybrid methods, and retrieval optimization.

Table of Contents

Installation / Setup

pip install langchain langchain-openai faiss-cpu

Core Syntax

Retrieval methods

Method How it works Best for
Similarity Cosine similarity ranking Default, most use cases
MMR (Maximal Marginal Relevance) Balance relevance + diversity Avoid redundant chunks
Hybrid Combine semantic + keyword search Precise queries + broad coverage
Multi-query Generate multiple queries, merge results Complex questions
Parent document Retrieve small chunks, return full context When context is needed
Self-RAG Model decides when to retrieve When some queries don't need retrieval

Similarity search (default)

# basic retrieval
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)
docs = retriever.invoke("What is machine learning?")

MMR (diversity)

# MMR balances relevance with diversity
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.7}
)
# fetch_k: candidates to consider
# lambda_mult: 1.0 = pure relevance, 0.0 = pure diversity

Score-based retrieval (with thresholds)

# return chunks above a similarity threshold
docs = vectorstore.similarity_search_with_score(
    "query", k=10, score_threshold=0.7
)
# filter low-quality results

Hybrid search (semantic + keyword)

# FAISS doesn't support hybrid natively — combine manually
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# keyword retriever
bm25 = BM25Retriever.from_documents(chunks)
bm25.k = 5

# semantic retriever
faiss_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# combine with equal weights
hybrid = EnsembleRetriever(
    retrievers=[bm25, faiss_retriever],
    weights=[0.5, 0.5],
)

Multi-query retrieval

from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI

multi_retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=ChatOpenAI(model="gpt-4o-mini"),
)
# generates 3 diverse queries, retrieves for each, deduplicates

Common Operations

Top-K selection

K value Coverage Speed Token usage
1–2 Narrow Fastest Low
3–5 Balanced (default) Fast Medium
6–10 Broad Slower High
10+ Comprehensive Slowest Very high

Rule of thumb: K=3–5 for most use cases. Increase for complex questions; decrease for simple factoid queries.

Retrieval quality metrics

# measure retrieval quality
def evaluate_retrieval(retriever, queries_and_expected):
    hits = 0
    total = len(queries_and_expected)
    for query, expected_keywords in queries_and_expected:
        docs = retriever.invoke(query)
        retrieved_text = " ".join(d.page_content for d in docs).lower()
        if any(kw.lower() in retrieved_text for kw in expected_keywords):
            hits += 1
    return hits / total    # hit rate

Practical Examples

Example 1 — Optimized RAG with MMR

from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# MMR retriever for diverse, non-redundant context
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.7}
)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer based on context:\n{context}"),
    ("human", "{question}"),
])

rag = (
    {"context": retriever | (lambda d: "\n\n".join(x.page_content for x in d)),
     "question": RunnablePassthrough()}
    | prompt | ChatOpenAI(model="gpt-4o") | StrOutputParser()
)

Example 2 — Score-threshold retrieval

# only return high-confidence matches
results = vectorstore.similarity_search_with_score("query", k=10, score_threshold=0.75)

if not results:
    answer = "I don't have relevant information to answer this."
else:
    context = "\n\n".join(doc.page_content for doc, score in results)
    answer = llm.invoke(f"Based on:\n{context}\n\nQ: query")

Example 3 — Parent document retrieval

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter

# small chunks for precise retrieval
child_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
# large chunks for context
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=InMemoryStore(),
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)
retriever.add_documents(documents)
# retrieves small chunks but returns full parent documents

Common Patterns

Pattern: conditional retrieval

def should_retrieve(query, llm):
    """Check if query needs document retrieval."""
    response = llm.invoke(
        f"Can this be answered from general knowledge? yes/no:\n{query}")
    return "no" in response.content.lower()

Pattern: retrieval + reranking

# 1) retrieve many candidates
candidates = vectorstore.similarity_search(query, k=20)
# 2) rerank top results
reranked = reranker.rerank(query, candidates, top_k=5)

Pattern: cache retrieval results

import hashlib, json

def cached_retrieval(query, retriever, cache_file="retrieval_cache.json"):
    cache = json.load(open(cache_file)) if os.path.exists(cache_file) else {}
    key = hashlib.md5(query.encode()).hexdigest()
    if key in cache:
        return cache[key]
    results = retriever.invoke(query)
    cache[key] = [d.page_content for d in results]
    json.dump(cache, open(cache_file, "w"))
    return cache[key]

Best Practices

  • Start with similarity search, K=3 — it works well for most cases.
  • Use MMR when retrieved chunks are too similar to each other.
  • Hybrid search is the best for production — combine semantic + keyword.
  • Score thresholds prevent returning irrelevant results.
  • Multi-query helps with complex or ambiguous questions.
  • Parent document retrieval gives precise retrieval with full context.
  • Test with real queries — retrieval quality is measured by answer quality.

Common Mistakes

Mistake Fix
Using K=1 Too narrow — use K=3–5 for better coverage
Not filtering by score Add score_threshold to avoid irrelevant chunks
Ignoring diversity Use MMR or hybrid search
Using only semantic search Add BM25 for keyword-based queries
Same K for all queries Tune K based on query complexity

Troubleshooting

Symptom Cause / Fix
Retrieving irrelevant chunks Embedding model mismatch or poor chunking
Missing relevant documents K too small or wrong retrieval method
Too many redundant chunks Use MMR for diversity
Slow retrieval Use ANN index (FAISS, Qdrant) instead of brute force
No results above threshold Lower threshold or improve embedding model

Official Documentation

Last Updated

2026-09-06 — Initial version.