LangChain VectorStoreRetriever — empty results, wrong docs, filters ignored
<code>VectorStoreRetriever</code> wraps any vector store into a LangChain retriever. Most retrieval failures are configuration issues (bad thresholds, wrong search type, filters not pushed down to the store) rather than model or embedding problems.
Quick fix (TL;DR)
search_kwargs: k too small, score_threshold too tight, filters written in a format the underlying store does not recognize, or MMR parameters that over-diversify. Fix by (a) starting with plain similarity + k=10 and measuring hit rate, (b) using store-native filter formats via the retriever's search_kwargs["filter"], and (c) tuning MMR only after similarity is proven working.Real error messages you'll see
These are the exact strings returned by the LangChain framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.
docs = retriever.invoke("some query")
print(docs)
# []
# search_kwargs={"score_threshold": 0.9, "k": 5} — threshold too tightretriever = vs.as_retriever(search_kwargs={"filter": {"category": "policy"}})
docs = retriever.invoke("...")
# All docs returned regardless of category — filter format wrong for this storeretriever = vs.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "fetch_k": 50, "lambda_mult": 0.1}
)
# lambda_mult=0.1 heavily favours diversity — returns off-topic docs
Reference
search_type options and when to use each
| <code>search_type</code> | When to use |
|---|---|
"similarity" | Default — cosine or L2 similarity, top-k |
"similarity_score_threshold" | When you want to filter out low-similarity hits |
"mmr" | When results are too similar to each other (diversity) |
Filter format by vector store backend
| Backend | Filter format |
|---|---|
| Pinecone | MongoDB-style dict: {"category": {"$eq": "policy"}} |
| Chroma | MongoDB-style: {"category": "policy"} |
| Weaviate | GraphQL-style operator dict |
| Qdrant | Native Qdrant filter dict with must/should |
| Milvus / Zilliz | Boolean expression string: "category == 'policy'" |
| pgvector | SQL WHERE clause or dict depending on wrapper |
| FAISS | No native filter — filter post-retrieval in Python |
| ElasticSearch | Elasticsearch query DSL |
Root causes, ranked by frequency
Based on developer reports across LangChain forums, GitHub issues, and Discord community during 2025–2026.
- 26%score_threshold too tight. 0.9+ is aggressive; most legitimate hits score 0.5-0.8 on cosine.
- 18%Filter format not native to the store. Passing a Chroma-style filter to Pinecone or vice versa.
- 14%k too small for downstream reranker. k=3 sent to a reranker leaves nothing to work with.
- 10%Embedding mismatch. Indexed with one model, queried with another; scores meaningless.
- 8%MMR lambda too low. lambda_mult below 0.3 prioritises diversity over relevance.
- 7%Filter field not indexed. Some stores need explicit metadata field indexing for filter push-down.
- 7%Async retriever wrapping a sync store. Async invoke works but blocks the event loop.
- 10%Wrong distance metric. Cosine similarity vs L2 distance behave differently; store default may not match the embedding model's space.
Fixes — copy-paste solutions
Start with plain similarity + generous k, then measure
Configure the retriever with the simplest possible search_kwargs. Measure hit rate on a labeled test set. Only introduce advanced features once you know they help.
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vs = Chroma( collection_name="my_docs", embedding_function=embeddings, persist_directory="./chroma_db", ) # Baseline: plain similarity, k=10 retriever = vs.as_retriever( search_type="similarity", search_kwargs={"k": 10}, ) # Measure with a labeled test set test_queries = [ ("What is the refund policy?", ["doc_refund_policy_v3", "doc_terms_of_service"]), ("How do I reset my password?", ["doc_password_reset_kb", "doc_account_faq"]), # ... ] hits = 0 misses = 0 for query, expected_ids in test_queries: docs = retriever.invoke(query) retrieved_ids = {d.metadata.get("id") for d in docs} if any(exp in retrieved_ids for exp in expected_ids): hits += 1 else: misses += 1 print(f"MISS: {query!r}") print(f" expected any of: {expected_ids}") print(f" retrieved: {list(retrieved_ids)[:5]}") print(f"Hit rate: {hits}/{hits+misses} = {hits/(hits+misses):.1%}") # Then experiment with variations and see which lifts hit rate
Push filters down to the store in the store's native format
The filter in search_kwargs is passed as-is to the underlying vector store. Use the store's native filter format, not a LangChain-generic one.
# --- Pinecone: MongoDB-style operators --- from langchain_pinecone import PineconeVectorStore retriever = PineconeVectorStore(index_name="...").as_retriever( search_kwargs={ "k": 10, "filter": {"category": {"$eq": "policy"}, "date": {"$gte": "2025-01-01"}}, } ) # --- Chroma: simple dict (LangChain wrapper translates) --- from langchain_community.vectorstores import Chroma retriever = Chroma(...).as_retriever( search_kwargs={ "k": 10, "filter": {"category": "policy"}, # equality } ) # --- Qdrant: native filter format --- from langchain_qdrant import QdrantVectorStore from qdrant_client.models import Filter, FieldCondition, MatchValue retriever = QdrantVectorStore(...).as_retriever( search_kwargs={ "k": 10, "filter": Filter(must=[ FieldCondition(key="category", match=MatchValue(value="policy")) ]), } ) # --- Milvus / Zilliz: boolean expression string --- from langchain_milvus import Milvus retriever = Milvus(...).as_retriever( search_kwargs={ "k": 10, "expr": "category == 'policy' and date >= '2025-01-01'", } ) # --- FAISS: no native filter — post-filter in Python --- from langchain_community.vectorstores import FAISS from langchain_core.documents import Document def faiss_with_filter(retriever, query: str, filter_fn) -> list[Document]: docs = retriever.invoke(query) return [d for d in docs if filter_fn(d)] filtered = faiss_with_filter( faiss_retriever, "policy question", lambda d: d.metadata.get("category") == "policy", )
Tune MMR for diversity only after similarity is working
Maximum Marginal Relevance re-ranks the top fetch_k candidates to balance relevance and diversity. lambda_mult = 1 gives pure similarity; 0 gives pure diversity. Sensible starting point: 0.5-0.7.
# When to reach for MMR: # - Corpus has many near-duplicate documents # - Same passage indexed multiple times (different pages, versions) # - Similar-worded but distinct concepts retriever_mmr = vs.as_retriever( search_type="mmr", search_kwargs={ "k": 5, # final number of results "fetch_k": 30, # candidate pool BEFORE re-ranking (must be > k) "lambda_mult": 0.6, # 0.5-0.7 is a sensible starting range # closer to 1 = more relevance # closer to 0 = more diversity } ) # For A/B testing MMR vs plain similarity on your test set: def compare_retrievers(similarity_retriever, mmr_retriever, test_queries): sim_hit_rate = evaluate(similarity_retriever, test_queries) mmr_hit_rate = evaluate(mmr_retriever, test_queries) print(f"similarity hit rate: {sim_hit_rate:.1%}") print(f"mmr hit rate: {mmr_hit_rate:.1%}") # Common finding: MMR HURTS hit rate but IMPROVES answer quality (diverse context # reduces one-sided answers). Measure end-to-end answer quality, not just retrieval. # For score-threshold retrieval, tune with real data — do not guess: retriever_thresh = vs.as_retriever( search_type="similarity_score_threshold", search_kwargs={"k": 10, "score_threshold": 0.4}, # Start LOW (0.3-0.4) and raise only if you see too much irrelevant context. )
Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- Build a labeled test set (30-100 query-doc pairs) before tuning any retriever config.
- Start with
search_type="similarity"andk=10; add complexity only when measured to help. - Match filter format to the vector store — never assume a generic dict works.
- Use the SAME embedding model for indexing and querying. Version pin.
- For score-threshold retrieval, start with threshold 0.3-0.4 and raise carefully.
- MMR
fetch_kshould be at least 3×k;lambda_multin 0.5-0.7. - Log retriever hit rate as a production SLI — regressions surface fast.
Frequently asked questions
EnsembleRetriever combines both.search_kwargs["filter"] in the store's native format. Some stores need the metadata field indexed at ingest time for efficient filtering. Check your store's docs.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.