Vector search finds documents by meaning. Keyword search finds documents by exact terms. Each approach has a blind spot that the other covers.
Vector search excels at understanding intent: it knows that 'how to fix a leaking faucet' and 'plumbing repair guide' are semantically similar. But it struggles with exact terms -- product codes, error messages, proper names, and acronyms. A query for 'error ERR_CONNECTION_REFUSED' may retrieve documents about generic connection errors because the embedding does not preserve the exact error code.
Keyword search (BM25) excels at exact matching: it will find every document containing 'ERR_CONNECTION_REFUSED'. But it misses semantic equivalence -- a document that discusses 'connection refused by remote host' will not match even though it answers the same question.
Hybrid search combines both approaches, typically using reciprocal rank fusion (RRF) to merge the two ranked lists into a single result set. This captures both semantic matches and exact keyword matches, producing retrieval results that are more robust than either method alone. For RAG systems, hybrid search is the practical default -- it handles the full range of user queries without the failure modes that plague pure vector or pure keyword search.
BM25 is the standard keyword search algorithm used by Elasticsearch, Solr, and PostgreSQL full-text search. It ranks documents by term frequency, inverse document frequency, and document length normalization. This pure Python implementation using rank-bm25 is useful for prototyping and understanding the algorithm. In production, use your database's built-in full-text search.
RRF scores each document as 1/(k+rank) for each list it appears in, then sums the scores. Documents appearing in multiple lists get a higher combined score. The k parameter (typically 60) controls how much rank position matters -- higher k flattens the score distribution. RRF is simple, effective, and does not require score normalization across different search methods.
This single SQL query runs both vector similarity search and full-text search, then merges results using RRF -- all inside PostgreSQL. No external search service needed. The tsvector column is auto-generated from content, the GIN index accelerates text search, and the HNSW index accelerates vector search. The FULL OUTER JOIN ensures documents from either search method are included in the final ranking.
This self-contained hybrid search class combines FAISS for vector search with rank-bm25 for keyword search. The vector_weight and keyword_weight parameters let you tune the balance between semantic and lexical matching. For the error code query, BM25 will push the exact match to the top; for the semantic query, vector search will dominate. The RRF fusion ensures documents that rank well in both methods get the highest combined score.
This demonstrates the complementary failure modes that justify hybrid search. Vector search cannot preserve exact tokens like error codes -- the embedding compresses 'PG-4502' into a generic technical meaning. BM25 cannot understand semantic equivalence -- 'check which queries are running' and 'monitor active connections' share no words. Hybrid search handles both cases by combining the strengths of each method.
Naively concatenating vector and keyword search results without a fusion strategy. This doubles the result set without any meaningful ranking, and the ordering is arbitrary.
Use Reciprocal Rank Fusion (RRF) to merge the ranked lists. RRF produces a single, coherent ranking that rewards documents appearing in both lists. It requires no score normalization because it operates on ranks, not scores.
Normalizing BM25 scores and vector similarity scores to the same scale and then averaging them. These scores have fundamentally different distributions and magnitudes, so linear combination is unreliable.
Use rank-based fusion (RRF) instead of score-based combination. RRF only depends on the rank position of each document in each list, not the raw score values. This sidesteps the normalization problem entirely.
Running keyword search without any text preprocessing (stemming, stop word removal, lowercasing), which causes mismatches between query terms and document terms.
Use your database's built-in text search with language-aware stemming. PostgreSQL's to_tsvector('english', text) handles stemming, stop words, and normalization automatically. For Python, use the NLTK or spaCy tokenizers.
Using the same weight for vector and keyword search regardless of the query type. Factual lookups (error codes, product names) need heavier keyword weight, while conceptual questions need heavier vector weight.
Implement adaptive weighting based on query characteristics. Queries with rare terms, codes, or quoted phrases should boost keyword weight. Queries phrased as questions or using conversational language should boost vector weight.
Adding hybrid search complexity when pure vector search is performing well. Hybrid search adds latency (running two search systems) and complexity (fusion logic) that may not be justified.
Evaluate pure vector search on your actual query distribution first. Only add hybrid search if you identify a class of queries (exact terms, codes, names) where vector search consistently fails. The improvement should justify the added complexity.
Hybrid search combines vector (semantic) search with keyword (BM25) search to handle the full range of user queries. Vector search captures meaning but loses exact terms; keyword search captures exact terms but misses synonyms and paraphrases. Reciprocal Rank Fusion (RRF) merges the two ranked lists by scoring documents based on their rank position in each list, boosting documents that appear in both. PostgreSQL with pgvector and tsvector can run hybrid search in a single SQL query. Start with equal weights and tune based on your query distribution -- the optimal balance depends on whether your users search by concept or by specific terms.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.