Two systems that fail differently
A keyword index is perfect on a part number and blind to a paraphrase. An embedding index is the reverse, and neither weakness is fixable within its own family.
Running both and merging is worth several points of recall on almost every benchmark, and the gain is largest exactly where each one alone is weakest.
The scores are not comparable
BM25 returns an unbounded sum that depends on query length and corpus statistics. Cosine similarity returns a bounded angle.
Adding them directly lets whichever has the larger numbers decide everything, and that is usually BM25 on a long query.
Min-max normalisation within each result list makes them comparable, and it is unstable: the scale depends on the best and worst result of that particular query.
Standardising is steadier but still assumes the two score distributions have a shape worth comparing, which they do not.
Weighted fusion
With normalised scores, one weight controls the balance, and near 0.5 is the usual starting point.
The weight is corpus-specific. Technical documentation with many identifiers wants more sparse, and conversational content wants more dense.
It has to be tuned on labelled queries, and it has to be retuned whenever the embedding model changes.
Rank fusion, which avoids the problem
Reciprocal rank fusion ignores the scores entirely and uses only the positions, so nothing needs normalising and nothing needs tuning.
It is the default in most hybrid systems for that reason: one constant, no calibration, and results that are usually close to a carefully tuned weighted sum.
One hybrid query, in order
- Run the query against the inverted index and take the top .
- Embed the query and search the vector index for its own top .
- Merge the two lists by rank fusion, or by weighted normalised scores.
- Deduplicate, since the same passage usually appears in both.
- Rerank the merged top candidates with a cross-encoder.
Both retrievers are asked for more than the final answer needs, because fusion can only reorder what it was given and a passage missing from both lists is lost.
The two searches are independent, so they run in parallel and the added latency is the slower of the two rather than their sum.