Hybrid Search: Combining Keyword and Vector Search

A
Author
29 Jul, 2026 2 min read 857 views

Vector search is great at meaning ("cancel my subscription" finds "how to end your plan") but weak at exact tokens: error codes, product SKUs, function names. Keyword search is the opposite. Hybrid search runs both and merges the results.

Keyword search in PostgreSQL

ALTER TABLE doc_chunks
  ADD COLUMN tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED;
CREATE INDEX doc_chunks_tsv_idx ON doc_chunks USING gin (tsv);

SELECT id, ts_rank_cd(tsv, q) AS rank
FROM doc_chunks, websearch_to_tsquery('english', 'ERR_CONN_RESET proxy') q
WHERE tsv @@ q
ORDER BY rank DESC
LIMIT 20;

Merging results with Reciprocal Rank Fusion

Scores from the two searches aren't on the same scale, so merge by rank instead. Reciprocal Rank Fusion (RRF) gives each document 1 / (k + rank) from each list and sums them.

WITH semantic AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS rnk
  FROM doc_chunks ORDER BY embedding <=> $1 LIMIT 20
),
keyword AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS rnk
  FROM doc_chunks, websearch_to_tsquery('english', $2) q
  WHERE tsv @@ q
  ORDER BY ts_rank_cd(tsv, q) DESC LIMIT 20
)
SELECT c.id, c.content,
       COALESCE(1.0 / (60 + s.rnk), 0) + COALESCE(1.0 / (60 + k.rnk), 0) AS score
FROM semantic s
FULL OUTER JOIN keyword k ON k.id = s.id
JOIN doc_chunks c ON c.id = COALESCE(s.id, k.id)
ORDER BY score DESC
LIMIT 8;

The same idea in application code

def rrf(*ranked_lists, k=60):
    scores = {}
    for results in ranked_lists:
        for rank, doc_id in enumerate(results, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

merged = rrf(vector_ids, keyword_ids)[:8]

When hybrid helps most

  • Technical docs with identifiers, version numbers and error messages.
  • Legal or medical text where exact terms matter.
  • Short queries of one or two words, where embeddings have little to work with.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.