Fuse BM25 and Dense Search With RRF
BM25 ranks documents by exact words; dense search ranks them by meaning. Their scores live on different scales, so merge the ranks, not the scores.
Implement rrf(rankings, k=60), reciprocal rank fusion. rankings is a list of ranked lists of document ids. Each document's fused score is the sum, over every ranking it appears in, of 1 / (k + rank), with rank starting at 1. Return the ids ordered by fused score, highest first; break exact ties by id so the output is stable.
Then hybrid_search(query, docs, k=3): BM25 ranking from the provided bm25_rank, dense ranking from the provided dense_rank, fused with rrf, top k documents back.
A document ranked first by both gets 2/(k+1), the most possible; one ranked first by a single ranker still gets 1/(k+1), which usually beats anything ranked fifth by both. k=60 is the value from the original paper.