Hybrid Search: Combining Vector & Full-Text Search

GT

GigaRAG team

Retrieval20 min read
On this page
Editorial split-screen showing a dense vector similarity cloud on the left and a sparse BM25 keyword grid on the right, with a query splitting into both and fusing into one ranked result list, illustrating how hybrid search combines semantic and lexical retrieval for GigaRAG.
Editorial split-screen showing a dense vector similarity cloud on the left and a sparse BM25 keyword grid on the right, with a query splitting into both and fusing into one ranked result list, illustrating how hybrid search combines semantic and lexical retrieval for GigaRAG.

How Does Hybrid Search Combine Vector and Full-Text Search?

Hybrid search is the fix most RAG pipeline and agent memory builders reach for after retrieval starts failing in two opposite directions at once. You query for an error code and get paragraphs about unrelated errors because the vector embedding drifted toward semantic neighbors. You query for a concept and get chunks that happen to share a word but miss the intent entirely. Neither vector-only nor full-text search handles both cases. Hybrid search runs the same query through a dense embedding index and a sparse BM25 index in parallel, then fuses the two ranked lists into one. That fusion is where most of the engineering lives, and where most tutorials stop. This guide covers the mechanics, compares RRF against weighted sum and cross-encoder reranking with real latency and cost tradeoffs, and states plainly what hybrid search won't fix: bad chunking, weak embeddings, or irrelevant source data. GigaRAG shows up once as a RAG-optimized option you can evaluate, but the method here is vendor-neutral.

At a glanceDetails
DefinitionCombines vector and keyword search
Primary benefitBetter retrieval relevance
Common fusion methodsRRF, weighted sum, cross-encoder
Typical use caseRAG pipelines and agent memory
Main limitationIncreased latency and tuning
ImplementationRequires both indexes

In This Guide

Hybrid search combines lexical (full-text) retrieval with semantic (vector) retrieval, then fuses both result sets into a single ranked list. It runs two searches in parallel and merges the scores, so exact keyword matches and conceptual matches both surface.

Lexical search in one paragraph

Lexical search matches query terms against an inverted index. BM25 scores documents by term frequency and rarity. It's precise for product codes, error strings, and proper nouns. It fails on synonyms and paraphrases because it only sees exact tokens.

Semantic search in one paragraph

Semantic search embeds text into dense vectors and ranks by cosine similarity. It catches "how do I reset my password" matching "password recovery steps." It misses exact identifiers and rare terms that embeddings blur.

The fusion idea

Neither signal alone covers RAG retrieval. Hybrid search runs both, normalizes their scores, and merges them. The result: a query for "error E11000 duplicate key" gets both the exact error-code match and a conceptually related chunk about unique index violations.

[!note] Hybrid search is not a single algorithm but a family of techniques that combine results from two or more retrieval methods. The fusion method you choose significantly impacts both relevance and performance.

RRF vs Weighted Sum vs Cross-Encoder: Which Fusion Method Should You Choose?

FactorRRFWeighted Sum
RelevanceGood, no trainingGood, requires tuning
LatencyLowLow
CostMinimalMinimal
ComplexitySimpleModerate
Best forQuick baselineWhen scores are comparable

The two searches don't just differ in algorithm. They differ in how they represent text at all. One turns words into coordinates in a high-dimensional space. The other counts which words appear and how often.

Dense vectors: semantic similarity

Dense vectors are embeddings: fixed-length arrays of floats, typically 768 or 1536 dimensions, where each dimension encodes some learned property of the text. A model like BERT or a sentence transformer produces them. Two chunks about the same concept land near each other in that space, even when they share no words. Cosine similarity measures that closeness.

The catch: dense vectors are opaque. You can't inspect a vector and see why two chunks matched. And rare terms get smoothed away, because the model averages meaning across many training examples. A product code like "XJ-4471" may not have a stable embedding at all.

Sparse vectors: exact term matching with BM25

Sparse vectors are mostly zeros. Each dimension corresponds to a term in the vocabulary, and the value is a weight: term frequency, inverse document frequency, or a BM25 score. A chunk containing "duplicate key" three times gets a high weight on those two dimensions and zero on everything else.

This is why sparse retrieval nails exact matches. If the query contains "E11000," a sparse index finds every chunk with that token. Dense retrieval might miss it entirely, because the embedding model never learned what that code means.

Why RAG pipelines need both

A RAG query like "why does my Mongo insert fail with E11000" carries two signals. The error code needs exact matching. The intent, "why does my insert fail," needs semantic matching. Run only one and you lose half the query.

Sparse-only retrieval returns chunks that mention E11000 but might miss a chunk explaining duplicate key violations without the code. Dense-only retrieval returns conceptually relevant chunks but might skip the one with the exact error string. Hybrid search runs both and fuses the results, so the chunk that has both the code and the explanation ranks first.

[!tip] For RAG pipelines, start with RRF as a baseline—it requires no score normalization and is robust. If you need higher precision, consider adding a cross-encoder reranker, but be aware of the added latency; you can cache reranking results for repeated queries.

Hybrid Search: A Step-by-Step Guide

  1. Set up both a vector index and a full-text index (e.g., BM25) over your corpus.
  2. Implement separate retrieval functions for each index, returning ranked results with scores.
  3. Choose a fusion method (RRF, weighted sum, or cross-encoder) based on your latency and relevance needs.
  4. Normalize scores if using weighted sum, or apply reciprocal rank fusion to combine rankings.
  5. Test retrieval quality on a representative set of queries, measuring recall and precision.
  6. Tune parameters (e.g., weights, k in RRF) using a validation set.
  7. Monitor latency and adjust index sizes or caching to meet performance targets.
Card grid comparing three hybrid search fusion methods: reciprocal rank fusion, weighted sum, and cross-encoder reranking, each with terse attributes for relevance, tuning, and latency, as described in the GigaRAG article.

How Does Hybrid Search Work?

The mechanics are simpler than the vocabulary suggests. You run two retrievals in parallel, normalize their scores, and merge the ranked lists. Here's the full path for a support-bot query: "why does my Mongo insert fail with E11000."

Step 1: Query processing

The query string goes to two places at once. The lexical path tokenizes it: "why," "does," "my," "mongo," "insert," "fail," "with," "E11000." Stopwords drop, and the remaining terms become a sparse vector. The semantic path sends the full string to an embedding model, which returns a dense vector. Nothing else happens to the query. No expansion, no rewriting, unless you add it yourself.

Step 2: Parallel retrieval

Both indexes run at the same time. The sparse index looks for chunks containing "mongo," "insert," "fail," and "E11000," scoring each by BM25. The dense index finds chunks whose embeddings sit closest to the query vector by cosine similarity. Each path returns its own ranked list, typically 20 to 100 candidates. The lists overlap, but not completely. That's the point.

Step 3: Score normalization

Here's the problem: BM25 scores are unbounded and depend on document length. Cosine similarity runs from -1 to 1. You can't compare a BM25 score of 18.4 against a cosine score of 0.82 directly. So you normalize. Common approaches: min-max scaling to [0,1], z-scores, or converting each list to ranks and discarding raw scores entirely. Reciprocal rank fusion does the last one.

Step 4: Fusion and ranking

The normalized scores combine into one list. With RRF, each chunk gets a score of 1/(k + rank) from each list, summed across lists. With weighted sum, you blend normalized scores using an alpha parameter. The fused list is your final retrieval result, ready to feed into the LLM.

The chunk that has both "E11000" and an explanation of duplicate key violations ranks first, because it scored well on both paths. A chunk with only the error code ranks lower. A chunk about Mongo insert performance, semantically close but lexically empty, ranks lower still.

Fusion Methods Compared: RRF vs Weighted Sum vs Cross-Encoder Reranking

You've got two ranked lists. Now you need one. The three methods below all solve that problem, but they trade latency, cost, and relevance quality in different ways. Here's the honest breakdown.

Reciprocal Rank Fusion (RRF)

RRF ignores raw scores entirely. It only cares about position. Each chunk gets a score of 1/(k + rank) from each list, and those scores sum across lists. The constant k (usually 60) dampens the effect of rank differences near the top.

score(chunk) = sum over lists of 1 / (k + rank_in_list)

The main catch: RRF throws away score magnitude. A chunk ranked first by a hair and a chunk ranked first by a mile get identical treatment. For RAG pipelines, that's usually fine. You care about relative order, not absolute confidence. RRF is also parameter-free beyond k, which means less tuning. Latency is negligible: it's a simple arithmetic pass over two lists.

Weighted sum (alpha blending)

Weighted sum keeps the raw scores, normalizes them to a common scale, then blends them with a weight. The alpha parameter controls the balance.

score(chunk) = alpha * normalized_dense_score + (1 - alpha) * normalized_sparse_score

Alpha of 0.7 favors semantic results. Alpha of 0.3 favors lexical. The good news: you get fine-grained control over which signal dominates. The bad news: you now have a parameter to tune, and the normalization step matters more than people admit. Min-max scaling is sensitive to outliers. Z-scores assume a distribution your scores may not have. Get normalization wrong and the blend is meaningless. Latency is still low, but slightly higher than RRF because of the normalization pass.

Cross-encoder reranking

This is a different beast. Instead of fusing scores arithmetically, you take the top N candidates from both lists (say 50), concatenate each chunk with the query, and run a cross-encoder model over every pair. The cross-encoder reads the query and chunk together, so it catches relevance signals that independent scoring misses. It outputs a relevance score per chunk, and you re-rank by that.

for chunk in top_n_candidates:
    relevance = cross_encoder(query, chunk)
rerank(candidates, by=relevance)

The tradeoff is cost and latency. A cross-encoder pass over 50 chunks takes hundreds of milliseconds on CPU, tens on GPU. That's 10 to 100 times slower than RRF or weighted sum. But relevance quality jumps measurably. For RAG pipelines where retrieval quality directly determines answer quality, the extra latency often pays for itself. For agent memory with tight latency budgets, it may not.

Which fusion method should RAG builders choose?

It depends on your latency budget and how much tuning you can afford. Start with RRF. It's parameter-free, fast, and good enough for most RAG pipelines. Move to weighted sum when you have evaluation data showing one signal consistently underperforms and you need to adjust the balance. Add cross-encoder reranking when retrieval quality is the bottleneck and you can absorb the latency. Don't start with the most complex option. Start simple, measure recall@k, and escalate only when the numbers justify it.

Tuning Hybrid Search for RAG Pipelines and Agent Memory

Fusion methods are only as good as the parameters behind them. RRF needs almost no tuning. Weighted sum needs one parameter done right. Cross-encoder reranking needs a latency budget. Here's how to set each.

Choosing alpha: when to favor lexical vs semantic

Alpha controls how much weight the dense score gets. The honest answer is it depends on your query mix. Queries with product codes, error strings, or proper nouns favor lexical. Alpha around 0.3 works there. Queries that are paraphrases or conceptual questions favor semantic. Alpha around 0.7 works there.

Start at 0.5. Run your evaluation set. If exact-match queries miss, drop alpha toward 0.3. If conceptual queries miss, raise it toward 0.7. Don't tune alpha on a single query. Tune it on at least 50 labeled examples, or the number is noise.

Evaluating retrieval quality: recall@k and MRR

Recall@k tells you whether the right chunk made it into the top k results. For RAG, k is usually 5 or 10. If recall@10 is below 0.8, your pipeline is dropping relevant chunks before the LLM ever sees them.

MRR tells you where the first relevant chunk lands. MRR of 1.0 means it's always first. MRR of 0.5 means it's usually second. For agent memory, MRR matters more than recall@k. The agent reads only the top few results, so position is everything.

Measure both before and after any tuning change. A change that raises recall@10 but drops MRR is a tradeoff, not a win.

Common tuning mistakes in RAG pipelines

The most common mistake is tuning fusion parameters without an evaluation set. You're guessing. Build the eval set first, even if it's 50 hand-labeled queries.

The second mistake is overfitting alpha to a handful of queries. Alpha of 0.65 on five queries means nothing on five hundred.

The third is ignoring normalization in weighted sum. If your dense scores range from 0.2 to 0.9 and your sparse scores range from 0 to 40, the blend is broken before alpha even applies. Normalize both to the same scale first.

Implementation Example: A Vendor-Neutral Hybrid Search Pipeline

You've tuned alpha. You've picked a fusion method. Now you need to build the thing. This walkthrough uses generic concepts that map to any vector database with sparse and dense index support: Weaviate, Elastic, Qdrant, Pinecone, Milvus. The pseudocode is not tied to one vendor's API.

Setting up dense and sparse indexes

You need two indexes over the same chunks. The dense index stores embeddings from your model of choice. The sparse index stores BM25-style term frequencies, usually built from the same chunk text.

dense_index = create_index(
    name="chunks_dense",
    vector_dim=768,
    metric="cosine"
)

sparse_index = create_index(
    name="chunks_sparse",
    analyzer="bm25"
)

Index every chunk into both. The dense index gets the embedding vector. The sparse index gets the raw text, which the engine tokenizes and weights internally. Keep chunk IDs identical across both indexes. That's how fusion matches results later.

Writing the hybrid query

A hybrid query runs both retrievals in parallel, then merges. Here's the shape:

query = "how do I reset my API key"

dense_results = dense_index.search(
    vector=embed(query),
    top_k=20
)

sparse_results = sparse_index.search(
    text=query,
    top_k=20
)

Top-k is higher than you need at the end. You're pulling candidates for fusion, not final results. Twenty per side is a reasonable default. If your corpus is large, bump it to 50.

Applying fusion and returning results

Now fuse the two result lists. RRF is the simplest thing that works:

def rrf_fuse(dense_results, sparse_results, k=60):
    scores = {}
    for rank, result in enumerate(dense_results):
        scores[result.id] = scores.get(result.id, 0) + 1 / (k + rank + 1)
    for rank, result in enumerate(sparse_results):
        scores[result.id] = scores.get(result.id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

A chunk that appears in both lists gets a higher fused score. A chunk that appears in only one list still gets a score, just lower. That's the point: neither signal can veto the other.

Return the top 5 to 10 fused chunks to your LLM. Don't return all 40 candidates. The LLM context window is finite, and irrelevant chunks dilute the answer.

If you'd rather not build this pipeline yourself, GigaRAG is one RAG-optimized option among several. It handles hybrid retrieval, fusion, and chunk management behind a single API, which saves you the index setup and tuning work above. It's not the only choice. Weaviate, Elastic, and Qdrant all support hybrid search natively, and any of them can work if you're willing to manage the pipeline yourself. The tradeoff is control versus setup time. GigaRAG trades some control for speed to production. Evaluate it against your own requirements, not as a default.

Benefits of Hybrid Search for RAG and Agent Memory

The payoff shows up in retrieval quality, not in the architecture. Here's what changes when you fuse lexical and semantic signals.

Improved recall on exact-match queries

Vector search alone misses exact terms. Product codes, error strings, proper nouns, API endpoint names: embeddings blur them into "similar meaning," which is not what the user typed. Hybrid search keeps the sparse signal in play. BM25 matches the literal string, and the fused ranking keeps that chunk near the top. For a support bot answering "error E11000," that's the difference between retrieving the right troubleshooting doc and retrieving a vaguely related paragraph about errors in general.

Better handling of mixed query types

Real user queries are messy. One asks "how do I reset my API key," the next asks "auth problem." The first needs lexical precision. The second needs semantic understanding. A hybrid system handles both without you switching retrieval modes per query. The dense side catches the paraphrase, the sparse side catches the exact term, and fusion sorts out which signal matters more for that particular query.

Reduced hallucination from irrelevant retrieval

Hallucination in RAG usually starts upstream. The LLM gets irrelevant chunks, then confidently answers from them. Hybrid search narrows the candidate pool before the LLM ever sees it. Fewer irrelevant chunks in the top-k means fewer wrong answers downstream. It won't eliminate hallucination. Bad chunking and poor embeddings still leak through. But it cuts the most common failure mode: retrieval that misses the obvious exact match.

What Hybrid Search Cannot Do

Hybrid search improves retrieval. It does not fix retrieval. The distinction matters because RAG builders often treat it as a cure-all, then blame the fusion method when results still miss.

Out-of-domain queries still fail

If your knowledge base doesn't contain the answer, no retrieval method will find it. Hybrid search combines two signals, but both signals draw from the same source data. A query about a topic your chunks never cover returns irrelevant results whether you use BM25, dense vectors, or both. The failure is upstream: you need better source material, not better fusion.

Increased latency and infrastructure cost

You're running two retrieval passes instead of one. That's two index lookups, two scoring passes, and a fusion step on every query. In practice, expect 1.5x to 2x the latency of vector-only search, depending on index size and hardware. You also maintain two indexes: a dense vector store and a sparse inverted index. That's more memory, more disk, and more operational surface to monitor.

Tuning complexity is real

Hybrid search adds parameters you didn't have before. The alpha weight, the fusion method, the normalization strategy, the top-k per index before fusion. Each one shifts results. Tuning them requires an evaluation set with labeled relevance judgments, which most teams don't have. You'll spend time on retrieval experiments that vector-only search never asked for.

Hybrid search won't fix bad chunking or embeddings

If your chunks split a concept across two documents, no retrieval method reassembles it. If your embedding model doesn't understand your domain's vocabulary, the dense signal is noise regardless of fusion. Hybrid search amplifies what your pipeline already produces. Garbage chunks plus garbage embeddings equals garbage retrieval, just with two signals agreeing on the wrong answer.

Hybrid Search vs Vector Search vs Semantic Search: Key Differences

The terms overlap, which is why the confusion exists. Vector search and semantic search are nearly the same thing: both use dense embeddings to match by meaning. Hybrid search adds a second signal on top.

Vector search runs one retrieval pass using dense embeddings. It matches by semantic similarity, so "how do I reset my password" and "password recovery steps" score close together. What it misses: exact terms. Product codes, error strings, proper nouns. A vector model may not know that "ERR_524" is a specific thing, not a concept.

Hybrid search runs two passes. The vector pass catches meaning. A BM25 pass catches exact terms. Fusion combines both into one ranked list. You get semantic matching plus literal matching. The cost is latency and tuning, as covered above.

Semantic search is vector search under a friendlier name. It's one signal: dense embeddings only. Hybrid search includes semantic search as one of its two components. The other component is lexical.

So hybrid search is not an alternative to semantic search. It's semantic search plus full-text search, fused. If someone says they use semantic search, they mean vector-only. If they say hybrid, they mean both signals.

Final Thoughts on Hybrid Search for RAG Builders

Hybrid search earns its place in RAG pipelines when the failure mode is clear: vector-only misses exact terms, keyword-only misses intent. Combining both signals fixes that specific gap. It won't fix anything else.

The honest tradeoff is tuning. Alpha weights, fusion method, score normalization. Each choice shifts recall and precision in ways you can't predict without testing on your own queries. Budget time for that. A hybrid search setup you don't tune is just two mediocre searches running in parallel.

Start with RRF if you want something that works without much fiddling. Move to weighted sum when you know which signal matters more for your data. Reach for cross-encoder reranking only when relevance quality justifies the added latency and cost.

For RAG builders who want hybrid search without wiring the pieces together, GigaRAG is one option worth evaluating. It's built for retrieval-augmented generation specifically, so the fusion and tuning defaults lean toward chunk retrieval rather than generic search. Not the only option. Not automatically the right one. But if you're building agent memory or a RAG pipeline and want hybrid search working today, it's a reasonable place to start.

Frequently Asked Questions

Vector search relies solely on semantic embeddings, which can miss exact keyword matches. Hybrid search combines vector results with full-text (keyword) results, capturing both semantic meaning and exact terms. This often improves recall and precision, especially for queries with proper nouns or specific phrases.

What is hybrid search and how does it work?

Hybrid search is a retrieval technique that merges results from multiple search methods, typically vector similarity and full-text (e.g., BM25). It runs both searches in parallel, then fuses the ranked lists using methods like reciprocal rank fusion (RRF) or weighted sum. This helps overcome the limitations of each individual method.

Semantic search uses vector embeddings to understand the meaning of a query, but it may ignore exact keywords. Hybrid search combines semantic search with keyword-based full-text search, so it can handle both semantic intent and exact term matching. This makes hybrid search more robust for diverse queries.

How does hybrid search combine vector and full-text search in Python?

In Python, you can use libraries like LangChain or vector databases such as Weaviate or Qdrant that support hybrid search. You typically set up both a vector index and a full-text index, then call a hybrid query method that returns fused results. For custom implementation, you can run separate queries and combine rankings using RRF or weighted scoring.

What is hybrid search in vector databases?

Many vector databases (e.g., Weaviate, Qdrant, Elasticsearch) now offer built-in hybrid search features. They allow you to combine vector similarity with full-text search (like BM25) in a single query, automatically fusing the results. This simplifies implementation for RAG pipelines.

How does hybrid search compare to BM25 alone?

BM25 alone is a strong keyword-based method, but it fails to capture semantic meaning. Hybrid search adds vector search, which understands synonyms and context. This combination often yields higher relevance, especially for natural language queries, while still benefiting from BM25's exact-match strength.

Hybrid search can increase latency due to running multiple searches and fusion. It also adds complexity in tuning fusion parameters. Additionally, it may still struggle with out-of-domain queries where neither method has good coverage. It is not a silver bullet but a practical improvement over single-method retrieval.

About GigaRAG

GigaRAG is for agent memory and RAG pipeline builders. get this right. Whether you are working through How Does Hybrid Search Combine Vector and Full-Text Search? or something adjacent, we publish what we have actually tested, including where it falls short.

All posts