OyeChats
FeaturesSolutionsIntegrationsPricingDocsBlogContact us
All posts
Search6 min read

Hybrid search, explained without the buzzwords

Why blending vector similarity with keyword search consistently outperforms either method alone, and how to implement it.

AI
AI Team
Engineering · May 26, 2026

Vector search gets a lot of the press in modern retrieval, and for good reason. It handles paraphrasing, synonyms, and messy human phrasing in a way that plain keyword search never could. But if you have ever watched a visitor ask a question that includes an internal product code, a version number, or an acronym, you have also seen vector search miss badly. Hybrid search exists because both methods have blind spots, and their blind spots barely overlap.

What each method is actually good at

Vector search compares meaning. It turns text into a dense numerical representation and looks for chunks whose meaning is close to the query. That is exactly what you want when a visitor asks about "cancelling my plan" and the docs use the phrase "ending your subscription".

Keyword search, in the form of BM25 or a Postgres TSVECTOR index, compares literal tokens. It is exactly what you want when a visitor pastes the string "ERR_TIMEOUT_1044" and needs the doc that mentions that exact code. No embedding model is going to reliably guess that a specific error code should map to a specific troubleshooting page.

How to combine the two

The simplest and most reliable technique is reciprocal rank fusion. Run both searches, get two ranked lists, and combine them by rank rather than by raw score. A chunk that shows up near the top of either list gets credit. A chunk that shows up near the top of both lists gets a lot of credit.

def rrf(vector_hits, keyword_hits, k=60):
    scores = {}
    for rank, doc_id in enumerate(vector_hits):
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
    for rank, doc_id in enumerate(keyword_hits):
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

The k parameter is a smoothing constant. A value around 60 works well in practice. Larger values flatten the contribution of top ranked results. Smaller values sharpen it.

The failure modes hybrid quietly fixes

  • Rare terminology like SKUs, error codes, and legal clause references start returning the right page again.
  • Paraphrased questions still land on the right chunk even when the vocabulary does not match.
  • A single embedding model bug no longer takes down retrieval entirely, because keyword search continues to work.

What to measure

A good hybrid rollout is boring by design. Track recall at ten for a fixed evaluation set of real questions, and compare vector only, keyword only, and hybrid. In our internal evaluation, hybrid beat vector alone on roughly nineteen of twenty question types, and beat keyword alone on all of them. The exceptions were queries where a single perfect keyword match should completely dominate the result.

You do not need a research team to ship hybrid search. You need one vector index, one keyword index, one small function to fuse them, and a habit of measuring against real questions. Everything else is polish.

Hybrid searchVector searchBM25

See it on your own site

OyeChats answers every visitor from your own docs, scores their intent as they chat, and routes the buyers to your team. Free to start — live in under 10 minutes.