Hybrid search
Hybrid search combines keyword ranking such as BM25 with dense vector search and fuses the results. Learn how it works, fusion methods, tuning and pitfalls.
Overview
Hybrid search is a retrieval method that runs a keyword search and a vector search on the same collection, then merges the two ranked lists into one. The keyword side, usually BM25, is good at exact words, names, codes and rare terms. The vector side, built on embeddings, is good at meaning, paraphrase and vague questions. By fusing both, hybrid search returns results that are right more often than either method alone.
In practice hybrid search is the default choice for production retrieval and for the retrieval step of retrieval-augmented generation (RAG). It is cheap to add when your search engine or vector store already supports both modes. The main work is deciding how to merge scores, how many candidates to fetch from each side, and whether to add a reranker such as cross-encoder reranking on top. This page explains the idea step by step, shows a worked example, and connects it to the way tabular foundation models and table-centric systems find the right rows, columns and documents.
What it is
Hybrid search is not a single algorithm. It is a pattern: two retrievers, one fusion step. The two retrievers see the same query in different ways.
- A lexical retriever treats the query as a bag of words. It looks each word up in an inverted index and scores documents by how often and how rarely the words appear. BM25 is the standard choice.
- A dense retriever turns the query into a vector with an embedding model and finds the nearest document vectors. Nearest-neighbor indexes such as HNSW or libraries like FAISS make this fast.
- A fusion step combines the two result lists. Common methods are reciprocal rank fusion, weighted score sum, and learned fusion.
Some systems add a third piece, a sparse learned retriever, which uses a neural model to produce weighted keyword-like vectors. Others add a final reranker. Both are optional and both fit inside the hybrid pattern.
Why not just one retriever
Each retriever has blind spots. Keyword search fails when the user and the document use different words for the same idea. A user asks for "car insurance cost" and the document says "vehicle coverage premium". Dense search fails in the opposite direction. It can blur exact tokens. A query for error code "E4021" or a product SKU may return documents that talk about similar errors but not that code. Hybrid search covers both failures.
An everyday analogy helps. Imagine a library with two helpers. One helper is a strict indexer who only finds books that contain your exact words. The other is a thoughtful librarian who understands what you mean but sometimes hands you a book on a nearby topic. If you ask both and then compare their picks, books that both helpers like are very likely the right ones. Books only one helper likes are still worth a look.
Where it came from
Combining several rankers is an old idea in information retrieval. Early research on "data fusion" and "metasearch" in the 1990s merged result lists from different search engines. Fusion of scores from multiple retrieval signals was also a core idea behind web ranking.
The modern form grew from two trends. First, lexical ranking matured into BM25, which stayed hard to beat for many years. Second, neural embedding models made dense retrieval practical in the late 2010s and early 2020s. Papers on dense passage retrieval showed that dense models could beat BM25 on question answering in some settings, but later benchmark work, including the BEIR benchmark in 2021, showed that BM25 remained strong out of domain and that dense models often generalized less well to new topics. That result pushed practitioners toward combining the two.
Reciprocal rank fusion was proposed in a 2009 paper by Cormack, Clarke and Buettcher. It is popular today because it needs no score calibration. As RAG spread from 2023 onward, hybrid search became a standard feature in search engines and vector databases, including Elasticsearch, Weaviate, Qdrant, Milvus and pgvector used together with Postgres full-text search.
How it works
The flow has five steps. Each is explained below with a plain analogy.
Step 1: Index the data twice
Every document or chunk is stored in two forms.
- An inverted index that maps each term to the documents containing it, along with counts. This is like the index at the back of a textbook.
- A vector index that stores one embedding per chunk. This is like a map where similar passages sit close together.
Both indexes must point to the same document identifiers, otherwise results cannot be merged.
Step 2: Run both searches
The query is processed twice. For the lexical side it is tokenized, lowercased, and possibly stemmed. For the dense side the same query string goes through the embedding model. Each retriever returns its top K candidates, with K often between 20 and 100. A larger K gives fusion more material but costs more time.
Step 3: Normalize or rank
The two retrievers produce numbers on different scales. A BM25 score might be 14.2 and a cosine similarity might be 0.83. These cannot be added directly. There are two ways around this.
- Rank-based fusion ignores the raw scores and uses only the positions in each list.
- Score-based fusion rescales scores, for example with min-max normalization or z-scores, then combines them.
Step 4: Fuse
Reciprocal rank fusion (RRF) gives each document a score of 1 divided by (k plus rank) from every list it appears in, and sums those. The constant k is often 60. A document ranked first in both lists beats a document ranked first in only one. It is like asking two judges to rank contestants. You do not care what scale each judge scores on, only where each judge placed them.
Weighted sum computes alpha * dense_score + (1 - alpha) * lexical_score after normalization. Alpha lets you lean toward meaning or toward exact terms. Some products expose this as a single "alpha" or "boost" setting.
Learned fusion trains a small model, often a learning-to-rank model, that takes both scores and other features such as document length, freshness or field matches, and outputs a final score.
Step 5: Optionally rerank
The fused shortlist, often 20 to 100 items, can be re-scored by a heavier model. A cross-encoder reads the query and each candidate together and produces a sharper relevance score. Late-interaction models such as ColBERT can also serve as a second stage. The top few results then go to the user or to a language model.
The pipeline as a diagram
query
|-- tokenize --> BM25 over inverted index --> top K lexical
|-- embed -----> ANN over vector index ----> top K dense
|
fuse (RRF or weighted)
|
optional rerank
|
top N results
Key concepts and terms
| Term | Meaning |
|---|---|
| Lexical retrieval | Matching on literal words using an inverted index |
| Dense retrieval | Matching on embedding vectors that capture meaning |
| Sparse learned retrieval | Neural models that output weighted term vectors |
| Inverted index | Map from term to the documents that contain it |
| ANN | Approximate nearest neighbor search for fast vector lookup |
| RRF | Reciprocal rank fusion, a rank-only way to merge lists |
| Alpha | Weight that balances the dense and lexical scores |
| Recall at K | Share of relevant items found in the top K results |
| Precision | Share of returned items that are relevant |
| nDCG | A ranking metric that rewards relevant items placed higher |
| Candidate set | The shortlist handed to the next stage |
| Chunking | Splitting documents into pieces before indexing |
A worked example
Suppose a small support knowledge base has four chunks.
- D1: "Reset the router using the red button for ten seconds."
- D2: "How to restore factory defaults on your network gateway."
- D3: "Error E4021 means the modem failed to authenticate."
- D4: "Billing questions are handled through the account portal."
The query is "E4021 router reset".
Lexical ranking (BM25). The rare token "E4021" appears only in D3, so D3 gets a large boost. "Router" and "reset" appear in D1. Result: D3 first, D1 second, then others.
Dense ranking. The embedding of the query sits near texts about resetting network devices. Result: D2 first, D1 second, D3 third, D4 last.
RRF with k = 60.
| Doc | Lexical rank | Dense rank | RRF score |
|---|---|---|---|
| D1 | 2 | 2 | 1/62 + 1/62 = 0.0323 |
| D3 | 1 | 3 | 1/61 + 1/63 = 0.0323 |
| D2 | not in list | 1 | 1/61 = 0.0164 |
| D4 | not in list | 4 | 1/64 = 0.0156 |
D1 and D3 tie at the top, and both are good answers. A reranker could then break the tie by reading the query and each chunk together. Without hybrid search, lexical alone would have missed D2 and dense alone would have ranked D3 too low for an exact error code query.
Code: RRF in Python
from collections import defaultdict
def rrf(rankings, k=60):
"""rankings: list of lists of doc ids, best first."""
scores = defaultdict(float)
for ranked in rankings:
for pos, doc_id in enumerate(ranked, start=1):
scores[doc_id] += 1.0 / (k + pos)
return sorted(scores, key=scores.get, reverse=True)
lexical = ["D3", "D1"]
dense = ["D2", "D1", "D3", "D4"]
print(rrf([lexical, dense])[:3])
Code: hybrid query in SQL with Postgres
WITH lex AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS r
FROM chunks, websearch_to_tsquery('english', 'E4021 router reset') q
WHERE tsv @@ q
LIMIT 50
),
vec AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS r
FROM chunks
ORDER BY embedding <=> $1
LIMIT 50
)
SELECT COALESCE(lex.id, vec.id) AS id,
COALESCE(1.0/(60+lex.r), 0) + COALESCE(1.0/(60+vec.r), 0) AS score
FROM lex FULL OUTER JOIN vec ON lex.id = vec.id
ORDER BY score DESC
LIMIT 10;
This pattern works with pgvector for the vector side and built-in full-text search for the lexical side.
Code: configuration sketch
retriever:
lexical:
engine: bm25
top_k: 50
fields: [title^2, body]
dense:
model: embedding-model-placeholder
index: hnsw
top_k: 50
fusion:
method: rrf
k: 60
rerank:
enabled: true
model: cross-encoder-placeholder
top_n: 8
Strengths and limitations
Strengths
- Better recall across query types. Exact-term queries and fuzzy meaning queries both work.
- Robust out of domain. The lexical side does not depend on a trained model, so it holds up on new vocabulary.
- Explainable part. You can show which words matched on the lexical side.
- Incremental to adopt. Many engines offer both modes already.
- Good for RAG. Higher recall in retrieval gives the language model a better chance of seeing the needed passage.
Limitations
- Two indexes to maintain. Storage, update jobs and consistency checks double.
- Tuning burden. Alpha, K, normalization and field boosts all matter and interact.
- Latency. Two searches plus fusion cost more than one search, though they can run in parallel.
- Weak at multi-hop or aggregate questions. Hybrid search finds passages, not answers that need joining or counting. Structured methods such as GraphRAG or SQL-style tools address those.
- Embedding drift. Changing the embedding model requires re-embedding everything.
- Language and tokenization issues. Lexical analysis depends on language, and mixed-language corpora need care.
How it compares with alternatives
| Approach | Good at | Weak at | Cost |
|---|---|---|---|
| Lexical only (BM25) | Exact terms, rare tokens, transparency | Synonyms, paraphrase | Low |
| Dense only | Meaning, paraphrase, cross-language | Exact codes, names, numbers | Medium |
| Hybrid search | Both of the above | Needs tuning, two indexes | Medium |
| Hybrid plus cross-encoder reranking | Top-of-list precision | Added latency per query | Higher |
| ColBERT late interaction | Fine-grained token matching | Larger index | Higher |
| GraphRAG | Relationships and global questions | Build cost, complexity | Highest |
| Contextual retrieval | Chunks that lose context | Extra indexing step | Medium |
Hybrid search is often combined with the other approaches in the table rather than replaced by them. Advanced RAG designs, described under advanced RAG and modular RAG, treat hybrid retrieval as one swappable module.
Role in tabular foundation models
Tabular foundation models learn from raw tables: rows, columns, mixed numeric and categorical types, missing values, and often no useful column names. Hybrid search is not a model inside that family. It is a retrieval layer that sits around it, and it matters in several concrete ways. For the model families themselves, see /model/architecture and /model/research.
Finding the right table, column or row
Enterprise data is spread across many tables. Before a model can work on a table it has to be found. Table names, column headers, descriptions and sample values can be indexed for both lexical and dense search. A lexical query catches an exact identifier such as cust_id or a code like NAICS 5415. A dense query catches "client number" when the schema says customer_ref. This is hybrid search used as schema and data discovery.
Retrieval of similar rows for in-context learning
In-context learning models for tables take a set of labeled rows as context and predict labels for new rows without weight updates. When the full table is too large for the context window, a retrieval step can pick the most relevant rows. A dense retriever on row embeddings finds rows that look alike overall. A lexical or exact-match retriever finds rows that share the same key values, such as an identical category code. Combining the two selects context rows that are both similar in meaning and aligned on exact keys. This is an area of active research and the details vary by system.
Handling missing values and imputation
Imputation needs candidates to copy from or compare with. Retrieving the nearest neighbor rows, using exact match on available fields plus embedding similarity on the rest, gives a short list of donors. A model then fills the gap. Hybrid retrieval is helpful because missing columns weaken a pure vector comparison, while exact-match on the present key fields stays reliable.
Anomaly detection and reference lookups
For anomaly detection, a row can be compared with its retrieved neighbors. If a transaction has no close neighbors under either exact keys or embeddings, it may be unusual. Hybrid retrieval gives a stronger neighborhood than either signal alone, particularly when some fields are free text and others are codes.
Sector and semantic classification
Classifying a company or product into a sector or semantic type often depends on text such as descriptions plus short codes. A hybrid retriever can fetch the closest labeled examples or taxonomy entries: lexical for exact code or term overlap, dense for descriptive similarity. A classifier or a tabular model then decides among the retrieved candidates. See /model/benchmarks for how classification quality is measured in this area.
No column names
When headers are absent or meaningless, a model must infer meaning from values. Hybrid search over value samples can help match an unlabeled column to a known schema. Lexical search matches repeated literal values such as country codes. Dense search matches value patterns such as dates or currency strings after they are embedded. This is a supporting use and not a replacement for a model that can read the values directly.
Continual learning and fresh data
New rows and documents arrive constantly. Retrieval lets a system use new knowledge without retraining a model. Adding a document to both indexes makes it available at once. This complements continual learning, where model weights are updated over time, and it avoids forgetting problems because the memory lives in the index.
Honest limits
Hybrid search retrieves text and vectors. It does not compute joins, sums or group-bys, and it does not replace a model that reasons over numeric columns. Treat it as the lookup layer that supplies the right context to such models.
Fusion, tuning and operations in depth
This section goes deeper into the parts of hybrid search that decide whether it works well in production: how the fusion methods differ, how to choose the weight between the two sides, how filters and fields interact, and what to monitor once the system is live.
Fusion methods side by side
| Method | Input used | Needs calibration | Needs labels | Typical use |
|---|---|---|---|---|
| Reciprocal rank fusion | Ranks only | No | No | First baseline, mixed engines |
| Weighted sum, min-max | Scores | Yes | Helpful | Tuned systems with stable score ranges |
| Weighted sum, z-score | Scores | Yes | Helpful | Score distributions with outliers |
| Convex combination of normalized scores | Scores | Yes | Yes, for alpha | Search products with one alpha setting |
| Learned fusion | Scores and features | Handled by the model | Yes | Large systems with click or judgment data |
| Cascade (filter then rerank) | Both lists | Not applicable | No | Strict precision needs |
Rank-based fusion is robust because ranks are always on the same scale. Its weakness is that it throws away the size of the gap between scores. If the lexical side has one very strong match and a long tail of weak ones, RRF treats rank 1 and rank 2 almost the same. Score-based fusion keeps that information but is only as good as its normalization.
Why min-max can mislead
Min-max normalization rescales the best score in a list to 1 and the worst to 0. This is risky when a list is short or has one outlier. If one document has a very large BM25 score because it repeats a rare term many times, every other document is squeezed toward zero. A z-score or a rank-based method is gentler. Another option is to normalize against a fixed reference range learned from many past queries rather than from the current list.
A weighted fusion in code
def minmax(scores):
lo, hi = min(scores.values()), max(scores.values())
if hi == lo:
return {k: 1.0 for k in scores}
return {k: (v - lo) / (hi - lo) for k, v in scores.items()}
def weighted_fusion(lex_scores, dense_scores, alpha=0.5):
lex = minmax(lex_scores)
den = minmax(dense_scores)
ids = set(lex) | set(den)
fused = {i: alpha * den.get(i, 0.0) + (1 - alpha) * lex.get(i, 0.0) for i in ids}
return sorted(fused, key=fused.get, reverse=True)
lex = {"D3": 14.2, "D1": 6.1}
den = {"D2": 0.83, "D1": 0.80, "D3": 0.74, "D4": 0.31}
print(weighted_fusion(lex, den, alpha=0.4))
A document missing from one list gets zero from that side. That is a design choice. An alternative is to fetch the missing score directly from the index so that no document is penalized only because it fell just below the cutoff.
Choosing alpha
Alpha is the share given to the dense side. A value near 0 behaves like keyword search. A value near 1 behaves like vector search. There is no universal best value. The right setting depends on how your users write queries.
- Short, code-like queries (SKUs, error codes, names) favor a lower alpha.
- Long natural-language questions favor a higher alpha.
- Mixed traffic often does best with a middle value or with a small router that sets alpha per query.
A practical way to tune is a grid sweep.
def sweep(queries, lex_fn, dense_fn, judge, alphas=(0.0, 0.2, 0.4, 0.6, 0.8, 1.0)):
results = {}
for a in alphas:
total = 0.0
for q in queries:
fused = weighted_fusion(lex_fn(q["text"]), dense_fn(q["text"]), alpha=a)
total += judge(fused[:10], q["relevant_ids"])
results[a] = total / len(queries)
return results
Plot the curve. If it is flat between 0.3 and 0.7, the system is not sensitive and any value in that band is fine. If it has a sharp peak, check that your test set is large enough before trusting it.
Query routing
Instead of one alpha for all queries, a router can decide.
- Detect identifiers: if the query matches a pattern such as a code, an email-like token or a quoted phrase, boost the lexical side.
- Detect questions: if the query starts with a question word or is long, boost the dense side.
- Detect language: if the query language differs from the main corpus language, rely more on a multilingual embedding model.
A router can be a few regular expressions or a small classifier. It should be evaluated like any other component, with its own error rate.
Fields, boosts and filters
Real documents have structure: title, headings, body, tags, author, date. Hybrid search can use this in several ways.
- Field boosts on the lexical side. A match in the title counts more than a match in the body.
- Multiple vectors per document. One embedding for the title and one for the body, fused later.
- Hard filters. Date ranges, tenant identifiers, language and access rights must restrict both retrievers.
- Soft signals. Freshness or popularity can be added as extra features in learned fusion.
A common bug is applying a filter after retrieval. If you fetch the top 50 and then drop items the user may not see, you can end up with only a few results. Apply filters inside the index query when the engine allows it. Approximate nearest neighbor indexes sometimes handle filters less gracefully than inverted indexes, so test recall with the filter turned on.
Sparse learned retrieval as a third signal
Some systems add a learned sparse retriever. A neural model reads the text and outputs weights for terms, including terms that were not in the text but are related. The result looks like a keyword vector but carries some meaning. It is stored in an inverted index and searched like keywords. In a hybrid system it can be fused with BM25 and dense scores. It is a good fit when you want better recall than BM25 but need the speed and explainability of an inverted index.
Latency and cost
| Stage | Typical cost driver | Ways to reduce it |
|---|---|---|
| Lexical search | Index size, query length | Shard, cache frequent queries |
| Dense search | Vector count, index type | Use HNSW or quantized vectors |
| Embedding the query | Model size | Cache, use a smaller model |
| Fusion | List size | Keep K modest |
| Reranking | Candidates times model size | Rerank fewer items, batch requests |
The two retrievers are independent, so run them in parallel. The total time is then close to the slower of the two plus fusion. A query cache helps with repeated questions. Embedding caches help with repeated texts.
Index maintenance
Hybrid search works only if both indexes describe the same data. Plan for these events.
- New documents. Write to both indexes in one job, and record success for each.
- Updates and deletes. Deleting from only one index leaves ghost results.
- Re-embedding. When you change the embedding model, build the new vector index beside the old one, switch over, then remove the old one.
- Schema changes. If you add a field to the lexical index, decide whether the vector side needs it too.
A nightly consistency check that compares document counts and sample identifiers across the two indexes catches most drift early.
Monitoring a live system
Track a few simple numbers over time.
- Share of queries with zero results from each side.
- Overlap between the two top-10 lists. Very high overlap means one side adds little. Very low overlap means the two sides disagree and fusion is doing a lot of work.
- Click or acceptance rate on the first result, if you have user feedback.
- Latency percentiles for each stage.
- Answer quality scores from an offline evaluation run.
def overlap_at_k(a, b, k=10):
sa, sb = set(a[:k]), set(b[:k])
return len(sa & sb) / k
When hybrid search is not enough
Some questions need more than a ranked list of passages.
- Questions that join facts from several documents may need a multi-hop strategy such as agentic RAG.
- Questions about relationships across a whole corpus may need a graph-based method such as GraphRAG.
- Questions that start vague may need query rewriting or HyDE before retrieval.
- Questions that need counts, sums or filters over structured data need a database query, not a passage retriever.
Hybrid search is the strong base layer. These other methods are added on top when the questions call for them.
Practical guidance and best practices
- Start with RRF. It needs no score calibration and works well as a first baseline. Move to weighted fusion only if you have evaluation data showing a gain.
- Fetch more than you return. Pull 50 to 100 candidates from each retriever and fuse down to a smaller list.
- Tune on your own queries. Collect a few hundred real queries with relevance judgments. Measure recall at K and nDCG. Tools like RAGAS and DeepEval can evaluate the end-to-end RAG result.
- Use filters before or inside both retrievers. Metadata filters such as date, tenant, language or document type should apply to both sides so that results stay consistent.
- Keep the same chunks in both indexes. Mismatched chunking makes fusion unreliable.
- Normalize text carefully. Lowercasing, accent folding and sensible tokenization matter for the lexical side. Keep a separate exact field for codes and identifiers.
- Add a reranker when precision matters. A reranker on the top 20 to 50 candidates usually gives the largest quality gain per unit of effort.
- Handle chunk context. If chunks lose meaning when cut from their parent, try contextual retrieval, which prepends a short explanation to each chunk before indexing.
- Watch latency. Run the two retrievers in parallel and cap K.
- Version your embeddings. Store the model name and version with each vector, and plan re-indexing.
- Use a library or engine that already supports hybrid mode. Frameworks such as LlamaIndex include hybrid retrieval components, and engines like Elasticsearch can run both modes in one request.
A simple evaluation loop
def recall_at_k(results, relevant, k=10):
hits = len(set(results[:k]) & set(relevant))
return hits / max(1, len(relevant))
def evaluate(queries, retriever, k=10):
scores = []
for q in queries:
found = retriever(q["text"], k=k)
scores.append(recall_at_k(found, q["relevant_ids"], k))
return sum(scores) / len(scores)
# compare: lexical, dense, hybrid
Run the same loop for each configuration and change one setting at a time.
Common mistakes
- Adding raw scores. BM25 and cosine similarity are not on the same scale. Normalize or use ranks.
- Using a tiny K. If each retriever returns only 5 items, fusion has little to work with.
- Ignoring tokenization of identifiers. Codes like
AB-1234may be split into pieces by a default analyzer, which hurts exact matching. - Tuning on a handful of examples. A setting that looks good on ten queries may fail broadly.
- Skipping evaluation by type. Report results separately for keyword-like queries and natural-language queries. Hybrid search helps most where the two differ.
- Letting indexes drift. Updating the vector index without the lexical index, or the reverse, gives inconsistent results.
- Forgetting access control. Apply permission filters in both retrievers.
- Assuming hybrid is always better. On some narrow, well-matched datasets a single retriever is enough, and the extra complexity is not worth it.
- Changing the embedding model without re-indexing. Mixed vectors from different models are not comparable.
- Passing too many chunks to the language model. More context is not always better. A tight, well-ranked list usually gives better answers.
Frequently asked questions
What is hybrid search in simple terms?
Hybrid search runs two searches at once, one that matches exact words and one that matches meaning, and then merges the two ranked lists into a single result list. The idea is that exact matching catches names, codes and rare terms, while meaning-based matching catches paraphrases and loosely worded questions. Documents that rank well in both lists rise to the top, which makes the final list more dependable than either list alone.
What is the difference between hybrid search and semantic search?
Semantic search usually means dense vector search, which compares the meaning of the query with the meaning of documents using embeddings. Hybrid search includes semantic search as one half and adds keyword search as the other half. The keyword half helps when the query contains exact terms that embeddings may blur, such as product codes, rare names or numbers.
Which fusion method should I use?
Reciprocal rank fusion is a good starting point because it only uses ranks and needs no calibration between BM25 and vector scores. Weighted score fusion can work better when you have labeled queries and can tune the weight, but it needs normalization and is more sensitive to changes in the data. Learned fusion with a ranking model is the most flexible, though it requires training data and maintenance.
Do I still need a reranker if I use hybrid search?
A reranker is optional but often worthwhile. Hybrid search is tuned for recall, meaning it tries to get the right items into a shortlist. A cross-encoder reranker is tuned for precision, meaning it puts the best items first within that shortlist. Together they give a strong two-stage design, and the reranker only has to score a few dozen candidates rather than the whole corpus.
Is BM25 still useful now that embeddings exist?
Yes. BM25 is fast, needs no training, works on any language with a good tokenizer, and handles exact terms very well. Benchmarks that test many different domains have repeatedly shown that BM25 is a hard baseline to beat out of domain. In hybrid search it provides the exact-match signal that dense models tend to lack.
How many results should each retriever return before fusion?
A common range is 20 to 100 per retriever, depending on corpus size and latency budget. Too few results limit what fusion can recover, and too many add cost with little gain. The right number is best found by measuring recall at the fused list size on your own queries and picking the smallest K that stops improving.
Can hybrid search work with a Postgres database?
Yes. Postgres has full-text search for the lexical side and extensions such as pgvector for the vector side. You can run both in one SQL statement and merge them with a rank-based formula. This is attractive when you want to keep structured data, text and vectors in one system with one set of access rules.
How does hybrid search help retrieval-augmented generation?
In RAG, the language model can only use what the retriever supplies. If the retriever misses the key passage, the model may guess or give a wrong answer. Hybrid search raises the chance that the key passage is in the context, because it covers both exact and meaning-based matches. Combined with reranking and sound chunking, it is one of the most reliable ways to improve answer quality.
How do I know whether hybrid search is working better for my data?
Build a small test set of real queries with known relevant documents, then compare lexical only, dense only and hybrid on recall at K and nDCG. Look at failure cases by query type, such as codes versus questions. End-to-end metrics for faithfulness and context quality from tools like RAGAS give a view of the impact on final answers.
Is hybrid search useful for tabular data?
It is useful around tabular data. It helps find the right table, column or example rows, and it helps match identifiers and descriptions across schemas. It does not perform numeric computation, so it is typically paired with SQL engines or tabular models that do the actual prediction or analysis.
Glossary
- ANN (approximate nearest neighbor): A fast search that finds close vectors without checking all of them.
- BM25: A keyword ranking function based on term frequency, rarity and length normalization.
- Chunk: A passage of a document stored as a unit in the index.
- Cosine similarity: A measure of the angle between two vectors, often used for embeddings.
- Dense vector: A list of numbers, typically hundreds long, produced by an embedding model.
- Embedding: A numeric representation of text or other data that captures meaning.
- Fusion: The step that merges ranked lists from several retrievers.
- Inverted index: A data structure that maps terms to the documents containing them.
- K: The number of candidates requested from a retriever.
- nDCG: Normalized discounted cumulative gain, a ranking quality metric.
- Recall at K: The fraction of relevant items found in the top K results.
- Reranker: A model that re-scores a shortlist to improve ordering.
- RRF: Reciprocal rank fusion, a rank-only method to merge lists.
- Sparse retrieval: Retrieval using high-dimensional vectors where most entries are zero, such as term-based vectors.
- Tokenization: Splitting text into units such as words or word pieces.
Summary
Hybrid search combines a keyword retriever such as BM25 with a dense vector retriever and fuses their results, usually with reciprocal rank fusion or a tuned weighted sum. It improves recall on both exact-term and meaning-based queries, and it is a common base for RAG pipelines. Its costs are two indexes, extra tuning and some added latency, which are usually worth paying.
For tabular foundation models and table-centric systems, hybrid search acts as the lookup layer. It finds relevant tables, columns, example rows and taxonomy entries, supports imputation and anomaly checks through neighbor retrieval, and keeps knowledge fresh without retraining. Pair it with a reranker, evaluate it on your own queries, and keep it clearly separate from the numeric reasoning that the models themselves perform. For more on the models, see /model/research, /model/architecture and /model/benchmarks.
Related
It belongs to RAG in my stack, under LLMs, retrieval, and evaluation. Others in the same group: