blog-post

Hybrid search in Manticore Search

Search is rarely a one-size-fits-all problem. A user typing "cheap running shoes" wants exact keyword matches, but a user asking "comfortable footwear for jogging" is expressing the same intent in different words. Traditional full-text search handles the first case well. Vector search handles the second. Hybrid search combines both in a single query so you don't have to choose.

In modern search systems, this is often described as combining lexical (sparse) retrieval with semantic (dense) retrieval. Different terms, same idea: exact matching plus meaning.

Hybrid search runs a full-text (BM25) search and a vector (KNN) search side by side, then merges the two result lists into one. Documents that score well on either signal (or both) rise to the top.

Full-text search is great at exact keywords, rare terms, and identifiers. Vector search understands meaning — that "automobile" and "car" are the same concept — because their embeddings are nearby in vector space.

Each method has blind spots:

  • Full-text struggles with synonyms and natural language
  • Vector search struggles with exact tokens like SKUs, error codes, and IDs

Hybrid search covers both.

How hybrid search fits into modern search pipelines

Hybrid search is the retrieval stage — the part that finds relevant candidates from your dataset.

Instead of relying on a single method, hybrid search combines keyword matching and semantic similarity to produce a stronger result set from the start.

In practice, this means:

  • Better recall for natural language queries
  • Precise matching for identifiers like SKUs or error codes
  • More relevant results without needing complex query logic

The goal is simple: return the best possible candidates in a single pass, using both signals together.

When should you use it?

Hybrid search is a good fit when:

  • Your queries mix intent and specifics. A search like python error 403 forbidden benefits from keyword precision on the error code and semantic understanding of the problem description.
  • You're building a RAG pipeline. Retrieval-Augmented Generation needs the most relevant chunks fed to the LLM. Hybrid retrieval consistently finds more relevant documents than either method alone.
  • Your catalog has structured and unstructured data. E-commerce products have precise names and model numbers (keyword territory) but also descriptions where meaning matters more than exact wording.
  • You can't predict how users will search. Some will paste exact phrases, others will describe what they're looking for in natural language. Hybrid search handles both gracefully.

How it works

Manticore uses Reciprocal Rank Fusion (RRF) to merge results. The idea is simple: instead of trying to compare raw BM25 scores with KNN distances (which are on completely different scales), RRF looks at rank positions. A document that's ranked #1 in the text results and #3 in the KNN results gets a higher combined score than a document that only appears in one list.

Here's a quick example. Suppose a text search and a KNN search each return their own top 3:

Text search results:

RankDocument
1Doc A
2Doc B
3Doc C

KNN search results:

RankDocument
1Doc C
2Doc A
3Doc D

RRF scores each document using the formula 1 / (rank_constant + rank). With the default rank_constant=60:

DocumentText contributionKNN contributionRRF score
Doc A1/(60+1) = 0.01641/(60+2) = 0.01610.0325
Doc C1/(60+3) = 0.01591/(60+1) = 0.01640.0323
Doc B1/(60+2) = 0.01610.0161
Doc D1/(60+3) = 0.01590.0159

Doc A ranks highest because it appears near the top in both lists. Doc C is close behind for the same reason. Doc B and Doc D each appear in only one list, so they score lower.

Why RRF?

There are two common ways to combine results:

  • Rank-based fusion (RRF) — simple, robust, no need to normalize scores
  • Score-based fusion — normalize scores first, then combine

Manticore uses RRF because it works well out of the box and avoids score calibration problems.

Under the hood, a hybrid query is split into independent sub-queries — one for full-text, one (or more) for KNN — that run in parallel. Once all sub-queries finish, RRF fuses their ranked result lists into a single output.

Why not just use one or the other?

Consider a support knowledge base with articles for different error codes — connection failures, authentication problems, sync issues. A user sees error E-5020 on screen and reports: "I can't connect to the server."

Vector search understands the symptom but not the error code. A KNN search for "can not connect to the server" returns:

#TitleKNN distance
1Error E-5030: DNS Resolution Failed0.572
2Error E-2091: App Loading Timeout0.583
3Error E-5020: SSL Certificate Mismatch0.605
4Error E-5010: Service Unavailable0.622
5Error E-4001: Login Failed0.665

The correct article (E-5020) is buried at #3. KNN ranks DNS and timeout errors higher because their descriptions are semantically closer to "can't connect." The actual problem — an SSL certificate mismatch — uses completely different vocabulary, so it scores lower.

You might think: just add the error code to the KNN query. But "E-5020" and "E-5010" are arbitrary identifiers with no semantic meaning — embeddings treat them as nearly identical tokens. KNN for "E-5020 can not connect to the server" does move E-5020 to #1, but only because the added text shifts the semantic context — the error code itself carries no weight.

Hybrid search solves this by sending each signal where it works best — the error code to full-text, the symptom to KNN:

SELECT title, hybrid_score()
FROM support_articles
WHERE knn(embedding, 'can not connect to the server')
  AND MATCH('E-5020')
LIMIT 5
OPTION fusion_method='rrf';
#TitleHybrid score
1Error E-5020: SSL Certificate Mismatch0.032
2Error E-5030: DNS Resolution Failed0.016
3Error E-2091: App Loading Timeout0.016
4Error E-5010: Service Unavailable0.016
5Error E-4001: Login Failed0.015

E-5020 jumps from #3 to #1 with twice the score of everything else. Full-text treats "E-5020" as an exact string — not similar to "E-5010", not close enough, just different. KNN ensures related connection errors still appear below for context.

This is the core value of hybrid search:

  • Identifiers → full-text
  • Meaning → vector search

Each method covers the other's blind spot.

Getting started

The simplest way to run a hybrid search is with hybrid_match(). If your table has auto-embeddings configured, one line does everything — text search, embedding generation, KNN search, and RRF fusion:

SELECT id, hybrid_score()
FROM products
WHERE hybrid_match('running shoes');

The JSON equivalent:

POST /search
{
  "table": "products",
  "hybrid": { "query": "running shoes" }
}

Manticore:

  • generates embeddings
  • runs both searches in parallel
  • fuses results

Full control: explicit MATCH + KNN

When you need to supply your own vectors or tune individual sub-queries, use the explicit form with MATCH() and KNN() in the WHERE clause:

SELECT id, hybrid_score()
FROM products
WHERE match('running shoes')
  AND knn(embedding, (0.12, 0.45, 0.78, ...))
OPTION fusion_method='rrf';
POST /search
{
  "table": "products",
  "knn": {
    "field": "embedding",
    "query_vector": [0.12, 0.45, 0.78, "..."]
  },
  "query": { "match": { "title": "running shoes" } },
  "options": { "fusion_method": "rrf" }
}

Each result includes:

  • hybrid_score() — fused score (used for default sorting)
  • weight() — BM25 score
  • knn_dist() — vector distance

Attribute filters (AND category = 'footwear') apply to both sub-queries.

Tuning

Three options let you adjust fusion behavior:

  • rank_constant — controls how much top positions dominate the fused score. Lower values (e.g. 10) make rank #1 count significantly more than rank #5. Higher values flatten the curve. See rank_constant .
  • fusion_weights — lets you give different importance to each sub-query. If text relevance matters more than vector similarity, weight it higher. See fusion_weights .
  • window_size — how many results each sub-query retrieves before fusion. By default, Manticore computes this automatically from your KNN parameters and query LIMIT. See window_size .

Multi-vector fusion

Hybrid search isn't limited to one text search plus one KNN search. You can fuse multiple vector searches together — useful when your data has several distinct semantic dimensions. For example, an e-commerce product has a textual description and a photo. A user searching for "minimalist white sneakers" cares about both: the title should match the style, and the product image should look like what they have in mind. By encoding the title and the image into separate vector spaces, you can search both at once and let RRF surface products that match across all three signals — keywords, text meaning, and visual similarity:

SELECT id, hybrid_score()
FROM products
WHERE match('running shoes') AS text
  AND knn(title_vec, (0.12, 0.45, ...)) AS title_sim
  AND knn(image_vec, (0.88, 0.21, ...)) AS image_sim
OPTION fusion_method='rrf',
       fusion_weights=(text=0.5, title_sim=0.3, image_sim=0.2);

All sub-queries run in parallel and are fused together via RRF.

Conclusion

Hybrid search is not about replacing full-text or vector search — it’s about using both where they work best.

Keyword search gives you precision for exact terms and identifiers. Vector search gives you flexibility for natural language and meaning. On their own, each has gaps. Together, they produce consistently better results across a wide range of queries.

With hybrid search in Manticore, you don’t need to choose between the two or build complex query logic to handle different cases. You can run both signals in parallel and get a single, unified result set.

If your search needs to handle both exact matches and intent — which most real-world applications do — hybrid search is a straightforward way to improve relevance without adding complexity.

Install Manticore Search

Install Manticore Search