Hybrid Search
- title
- Hybrid Search
- type
- concept
- summary
- Combining BM25 keyword search with embedding similarity for better retrieval
- tags
- search, information-retrieval, ai-agents
- created
- 2026-04-07
- updated
- 2026-04-07
Combining keyword-based search (BM25) with vector/embedding-based semantic search, then blending their scores for ranking. Neither approach alone covers all query types well. Together they handle both exact term matches and meaning-based similarity.
Why both are needed
BM25 (keyword search) scores documents by term frequency and inverse document frequency. It's fast, needs no ML models, and excels at exact matches โ searching for "ECONNREFUSED" finds documents containing that exact string. It fails on synonyms and paraphrases: searching "connection refused" might miss documents that only say "socket error."
Embedding search (semantic/vector search) encodes queries and documents as vectors, then finds the closest matches by cosine distance. It handles synonyms and paraphrases well โ "connection refused" and "socket error" land near each other in vector space. It fails on exact identifiers: searching for "ECONNREFUSED" might return documents about networking in general rather than the specific error code.
Hybrid search combines both: run BM25 and embedding search in parallel, normalize their scores, and blend with a configurable weight (commonly 0.5/0.5 or 0.7 BM25 / 0.3 embedding).
Practical implementation
The simplest approach, used by hippo-memory and qmd:
- Run BM25 over all documents, get top N results with scores
- Run cosine similarity over embeddings, get top N results with scores
- Normalize each score set to [0, 1]
- Combine:
final_score = ฮฑ ร bm25_score + (1 - ฮฑ) ร embedding_score - Re-rank by final score, apply token budget cutoff
The key design choice is graceful degradation. If embeddings aren't available (no model installed, cold start, resource constraints), fall back to BM25 only. The system should always return results โ embedding search is an enhancement, not a requirement.
Reciprocal Rank Fusion (RRF)
An alternative to score blending: instead of normalizing raw scores, rank results from each system and combine ranks. RRF score = ฮฃ(1 / (k + rank_i)) across all retrievers. This avoids the score normalization problem (BM25 and cosine scores have very different distributions) and works well when you're merging results from more than two sources.
Where it shows up
- RAG pipelines โ most production retrieval-augmented generation systems use hybrid search to find relevant context chunks. Pure embedding search misses exact code references; pure BM25 misses semantic connections.
- Agent memory โ hippo-memory uses BM25 as the always-available default and adds embedding similarity when @xenova/transformers is installed.
- Local search tools โ qmd combines BM25 + vector + LLM re-ranking for markdown search.
- Document retrieval โ Elasticsearch, Meilisearch, and Typesense all support hybrid search modes combining full-text and vector indexes.
- Client-side search โ full-text-search-indexeddb builds a simpler keyword-only search using indexeddb's
multiEntryinverted-index. No ranking, but sufficient for chat history lookup where you just need "messages containing these words."
Relevance to this wiki
If this wiki grows large enough that index browsing becomes insufficient, hybrid search would be the retrieval approach. BM25 handles specific term lookups (finding which page mentions "FLP impossibility"), embeddings handle conceptual queries ("what do we know about AI agent coordination failures"). qmd already does this for markdown files and has an MCP server for Claude Code integration.