RAG

Hybrid Search👨‍💻

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.

Key Takeaways

  • 1Vector search captures semantic similarity (meaning-based matching) but loses exact terms like error codes, product IDs, and proper names during the embedding process.
  • 2BM25/keyword search captures exact lexical matches and handles rare or domain-specific terms well, but misses synonyms, paraphrases, and queries phrased differently from the source text.
  • 3Reciprocal Rank Fusion (RRF) merges ranked result lists from different search methods by scoring each document based on its rank position: score = 1/(k + rank). Documents that appear in both lists get boosted.
  • 4Hybrid search is not just 'run both and concatenate'. The fusion strategy (RRF, weighted combination, or cross-encoder re-ranking) determines whether the combined results are better or worse than either individual method.
  • 5PostgreSQL with pgvector and tsvector can run both vector search and full-text search in a single query, making it the simplest path to hybrid search without additional infrastructure.
  • 6The optimal weight between vector and keyword search depends on your query distribution. Factual lookups benefit from heavier keyword weight; conceptual questions benefit from heavier vector weight.

Examples

BM25 keyword search with rank-bm25 (pure Python)

python

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.

Reciprocal Rank Fusion -- merge vector and keyword results

python

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.

Hybrid search with pgvector + PostgreSQL full-text search

sql

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.

Hybrid search with Python, FAISS, and rank-bm25

python

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.

When vector search fails vs when keyword search fails

python

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.

Common Mistakes

Mistake:

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.

Fix:

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.

Mistake:

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.

Fix:

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.

Mistake:

Running keyword search without any text preprocessing (stemming, stop word removal, lowercasing), which causes mismatches between query terms and document terms.

Fix:

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.

Mistake:

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.

Fix:

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.

Mistake:

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.

Fix:

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.

Best Practices

  • Use pgvector with tsvector for the simplest hybrid search setup. PostgreSQL runs both vector and full-text search in a single query with no external services, and the RRF fusion can be computed in SQL.
  • Start with equal weights (0.5/0.5) for vector and keyword search, then tune based on evaluation. Track which search method contributes the correct result for each query to understand the optimal balance for your use case.
  • Use RRF with k=60 as your default fusion strategy. It is simple, robust, and does not require score normalization. Only move to learned fusion weights if RRF performance is measurably insufficient.
  • Index your documents for both search methods at ingestion time. Compute embeddings for vector search and extract searchable text for keyword search in the same pipeline. Do not treat them as separate systems.
  • Evaluate hybrid search against pure vector search on three query categories: semantic queries (paraphrases), exact-term queries (codes, names), and mixed queries. Hybrid should win on exact-term and mixed queries without regressing on semantic queries.
  • Consider cross-encoder re-ranking as an alternative to or addition to RRF. A cross-encoder scores each (query, document) pair with a single forward pass and can re-rank the top-20 results from hybrid search for maximum precision.

Summary

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.

Practice RAG with hands-on challenges

Learn hybrid search hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master RAG with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.