Production RAG systems rarely trust a single retriever. They run keyword search (BM25) and vector search side by side, then merge the ranked lists. The standard way to merge is Reciprocal Rank Fusion:
where is the 1-based position of document in ranking . A document missing from a ranking contributes nothing for that ranking.
Implement rrf(rankings, k=60) that takes a list of rankings (each a list of document ids, best first) and returns a list of (doc_id, score) tuples sorted by score, highest first. Break ties by doc_id alphabetically.
Example
rrf([["a", "b"], ["b", "c"]], k=60)
# [("b", 0.0325...), ("a", 0.0164...), ("c", 0.0161...)]