π What is Reciprocal Rank Fusion (RRF)?

Reciprocal Rank Fusion (RRF) is a technique used in Retrieval-Augmented Generation (RAG) systems to combine multiple ranked lists of documents into a single, improved ranking. It's particularly useful for RAG-Fusion, which enhances RAG by generating multiple queries and then using RRF to re-rank the results.
Let me explain RRF like a beginner, then compare it with filter-based fan-out retrieval, and finally show how you can apply it to your multi-query PDF RAG system.
π What is Reciprocal Rank Fusion (RRF)?
It works the same as Parallel Query (Fan Out) Retrieval i.e. LLM would generate 3 to 5 different queries based on the original user query. Now imagine you searched 3 slightly different versions of your question in Qdrant and got relevant chunks:
Rewritten Query | Top Results (Ranked) | |
Query 1 | πA, πB, πC | |
Query 2 | πC, πD, πE | |
Query 3 | πF, πA, πD |
Now, instead of:
Just combining all chunks (π duplicates possible)
Or filtering for unique ones (π loses score/rank info)
You fuse the results intelligently by their rank. This will give the Rank of each chunk. Hence putting the chunks in order based on the similarity with the generated query.
π RRF vs Parallel Query (Fan Out) Retrieval (Comparison)
Feature | Parallel Query | Reciprocal Rank Fusion (RRF) |
Keeps all chunks. | β Yes | β Yes |
Uses rank from each search. | β No | β Yes |
Gives weight to overlap. | β All equal | β Overlap = more score |
Handles chunk quality. | β Not really | β Top-ranked chunks score better |
Good when...? | Results are noisy | When ranks matter + you want diversity |
π‘ Why RRF Is Great for You
In fan-out RAG, different queries return different "views" of the same concept.
Some chunks show up across multiple queries = likely to be super relevant
RRF promotes these, without needing to manually guess which is best.
Code to get Rankings of each response
before understanding this, you need to understand the working of
from collections import defaultdict
def reciprocal_rank_fusion(results_list: list[list[str]], k: int = 60) -> list[str]:
"""
results_list = [
[chunk_id1, chunk_id2, chunk_id3], # from query1
[chunk_id3, chunk_id4, chunk_id5], # from query2
...
]
Returns: ranked list of unique chunk IDs
"""
scores = defaultdict(float)
for result in results_list:
for rank, chunk_id in enumerate(result):
scores[chunk_id] += 1 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)
results_list
: This is a list of ranked lists.scores = defaultdict(float)
: This creates a dictionary where:
Keys = chunk IDs
Values = scores (starts at 0.0 by default)
enumerate(result)
gives us the rank (position) and thechunk_id
(built-in function in Python)We then calculate the score for each
chunk_id
Letβs say
k = 60
:If
chunk1
is at rank 0 β score = 1 / (60 + 0 + 1) = 1/61If it's at rank 1 β score = 1/62
So, higher ranked items (closer to top) get more score.
π‘ Also, if the same chunk_id appears in multiple lists, their scores get added up β this is how RRF merges and rewards consensus.
Subscribe to my newsletter
Read articles from hardik sehgal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
