Overview
Fusion Retrieval combines multiple search strategies (vector, keyword, graph) and merges results using reciprocal rank fusion. This approach is more robust across diverse query types than any single method.
Pattern #3 in the 37 RAG Patterns Collection
Why Fusion?
| Search Type | Strength | Weakness |
|---|---|---|
| Vector | Semantic similarity | Misses exact matches |
| Keyword | Exact term matching | No semantic understanding |
| Graph | Relationship aware | Requires structured data |
Fusion combines all strengths while minimizing weaknesses.
Architecture
graph LR
A[Query] --> B[Vector Search]
A --> C[Keyword Search]
A --> D[Graph Search]
B --> E[Result Set 1]
C --> F[Result Set 2]
D --> G[Result Set 3]
E --> H[Reciprocal Rank Fusion]
F --> H
G --> H
H --> I[Merged Results]
I --> J[LLM Generation]
style A fill:#f9f,stroke:#333
style H fill:#ff9,stroke:#333
style J fill:#9f9,stroke:#333Reciprocal Rank Fusion
The magic formula that combines rankings:
RRF(d) = Σ(1 / (k + rank_i(d)))
Where:
- ▸
d= document - ▸
rank_i(d)= rank of document in retriever i - ▸
k= constant (typically 60)
Implementation
Step 1: Multiple Retrievers
def multi_retrieval(query):
# Vector search
vector_results = opensearch.search(
index="embeddings",
body={
"query": {"knn": {"vector": embed(query), "k": 10}}
}
)
# Keyword search (BM25)
keyword_results = opensearch.search(
index="text",
body={
"query": {"match": {"content": query}}
}
)
# Graph search (if applicable)
graph_results = traverse_knowledge_graph(query)
return vector_results, keyword_results, graph_resultsStep 2: Reciprocal Rank Fusion
def reciprocal_rank_fusion(results_lists, k=60):
"""Combine multiple ranked lists using RRF"""
scores = {}
for results in results_lists:
for rank, doc_id in enumerate(results, start=1):
if doc_id not in scores:
scores[doc_id] = 0
scores[doc_id] += 1 / (k + rank)
# Sort by combined score
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [doc_id for doc_id, score in ranked]Step 3: Complete Pipeline
def fusion_rag(query):
# Get results from multiple strategies
vector, keyword, graph = multi_retrieval(query)
# Combine with RRF
fused_ids = reciprocal_rank_fusion([
[r['_id'] for r in vector],
[r['_id'] for r in keyword],
[r['id'] for r in graph]
])
# Fetch top documents
context = fetch_documents(fused_ids[:5])
# Generate response
response = bedrock.invoke_model(
modelId="meta.llama3-70b-instruct-v1:0",
body=json.dumps({
"prompt": f"Context: {context}\n\nQuestion: {query}"
})
)
return responseComplete Notebook
🚀 Run the full implementation:
📓 Fusion Retrieval AWS Notebook
Includes:
- ▸Multiple retrieval strategies setup
- ▸RRF implementation with tuning
- ▸Benchmark comparisons
- ▸OpenSearch hybrid configuration
- ▸Performance optimization
Performance
| Metric | Value |
|---|---|
| Accuracy | +20% vs single-strategy |
| Latency | ~400ms (parallel) |
| Cost/query | $0.10 |
| Robustness | High across query types |
When to Use
Best For
- ▸Production applications requiring high reliability
- ▸Diverse query types (semantic + exact matches)
- ▸Critical accuracy where single-method failure is costly
- ▸General-purpose search without query type prediction
Combine With
- ▸Reranking (#4) - Further refine fused results
- ▸Caching (#33) - Cache fusion results for speed
- ▸Adaptive RAG (#8) - Route queries to best fusion mix
Tuning Tips
1. Adjust k Parameter
# Higher k = more equal weight distribution
k = 60 # Standard
k = 20 # More weight to top ranks
k = 100 # More democratic fusion2. Weight Different Strategies
def weighted_rrf(results_lists, weights, k=60):
scores = {}
for results, weight in zip(results_lists, weights):
for rank, doc_id in enumerate(results, start=1):
if doc_id not in scores:
scores[doc_id] = 0
scores[doc_id] += weight / (k + rank)
return scores
# Example: favor vector over keyword
fused = weighted_rrf(
[vector_results, keyword_results],
weights=[0.7, 0.3]
)3. Filter Low-Confidence Results
# Only include docs appearing in multiple retrievers
def multi_retriever_filter(fused_ids, min_appearances=2):
return [
doc_id for doc_id in fused_ids
if count_appearances(doc_id) >= min_appearances
]Related Papers
- ▸Reciprocal Rank Fusion: Original Paper (2009)
- ▸Hybrid Search: Dense-Sparse Retrieval
Next Steps
- ▸Try it: Run the Fusion Retrieval notebook
- ▸Add reranking: Implement Pattern #4 for refinement
- ▸Optimize: Use Caching (#33) for frequently fused queries
- ▸Scale: Try Hybrid Search (#34) for production
Part of: 37 RAG Patterns Collection
See also: Complete Pattern Index