Module 4 · Retrieval
Lesson 4.4
Hybrid search and RRF
Hybrid search runs vector and keyword search, then merges their ranked lists with reciprocal rank fusion.
The problem
Why it matters
Without it
Vector search can miss exact codes. Keyword search misses synonyms. Each search alone fails on some questions.
With it
Both searches run on the same question. Reciprocal rank fusion (RRF) combines their ranks, so a chunk that either search ranks high rises.
How it works
Both searches read the same question
Vector search and keyword search each run on the same question. Each returns a list ordered from best to worst, where rank 1 is the best. The two searches can run at the same time, so the wait is the slower one.
Rank Vector search Keyword search 1 refund-guide refund-form 2 order-page refund-guide 3 refund-form faq The top three chunks from each search for one question. // Two ranked lists for one question, best first.const vectorRanking = ["refund-guide", "order-page", "refund-form"];const keywordRanking = ["refund-form", "refund-guide", "faq"];console.log("Vector rank 1:", vectorRanking[0]);console.log("Keyword rank 1:", keywordRanking[0]);Output
Vector rank 1: refund-guide Keyword rank 1: refund-form
Raw scores cannot be added
A vector score of 0.78 and a keyword score of 1.65 use different scales. Adding them has no fair meaning, because each scale comes from its own formula. Only the rank of each chunk in each list is used.
Raw scores
- Vector score: 0.78 for one chunk
- Keyword score: 1.65 for one chunk
- Different scales, so no fair sum
Ranks
- Rank 1 means best in that list
- Rank 3 means third in that list
- Ranks from both lists can be combined
Two ways to combine the two lists. Each rank becomes a small score
RRF gives a chunk 1 divided by (60 plus its rank) for each list that contains it. Rank 1 gives 1/61, which is 0.0164, and rank 3 gives 1/63, which is 0.0159. The constant 60 is a common choice, and it keeps the top ranks from dominating the sum.
RRF score for one list, by rank. The gap between ranks is small, so no single list dominates. // RRF gives a rank score of 1 / (60 + rank).for (const rank of [1, 2, 3]) { console.log("Rank", rank, (1 / (60 + rank)).toFixed(4));}Output
Rank 1 0.0164 Rank 2 0.0161 Rank 3 0.0159
A chunk in both lists adds two scores
refund-guide is rank 1 in the vector list and rank 2 in the keyword list. Its total is 1/61 plus 1/62, which is 0.0325. The refund-form chunk is rank 3 and rank 1, so its total is 0.0323.
Chunk Vector list Keyword list Total refund-guide 0.0164 (rank 1) 0.0161 (rank 2) 0.0325 refund-form 0.0159 (rank 3) 0.0164 (rank 1) 0.0323 The rank scores added for the two chunks that both searches found. // refund-guide is rank 1 in one list and rank 2 in the other.const total = 1 / (60 + 1) + 1 / (60 + 2);console.log(total.toFixed(4));Output
0.0325
The fused order
The totals are sorted from high to low. The final order is refund-guide, refund-form, order-page, and faq last. A chunk found by only one search still appears, but its total is smaller.
Fused totals in the final order. order-page and faq appear in only one list.
Common mistakes
What goes wrong
- Adding the raw scores of the two searches. Their scales differ, so one search always dominates.
- Reading a chunk found by only one search as a bad result. It can still be the best answer.
- Keeping too few candidates from each search. Fusion can only reorder what the two lists return.
Use it when and skip it when
Is it the right tool?
Use it when
- Users mix exact terms, such as codes, with plain questions.
- You want results that work on both meaning and words, without tuning weights.
Skip it when
- One search already passes your test questions. Add a second search only when a test shows a gap.
- The collection is tiny. A single search reads all of it anyway.
Words to know
Terms in this lesson
- Rank
- The position of an item in a sorted list, where 1 is the best.
- Reciprocal rank fusion (RRF)
- A merge rule that adds 1 divided by (60 plus rank) for each list.
- Fusion
- Combining several ranked lists into one list.
- Candidate
- A chunk returned by a search before the final choice.