RerankRRF
⚠️ Warning
HelixQL is deprecated in HelixDB v2. Queries are now written with the Rust DSL and dispatched as JSON — see the Querying guide. This section is kept as a reference for legacy HelixQL projects.
For the complete documentation index optimized for AI agents, see llms.txt.
Rerank with RRF (Reciprocal Rank Fusion)
RerankRRF is a powerful technique for combining multiple ranked lists without requiring score calibration. It’s particularly effective for hybrid search scenarios where you want to merge results from different search methods like vector search and BM25 keyword search.
::RerankRRF // Uses default k=60
::RerankRRF(k: 60) // Custom k parameter
⚠️ Warning
When using the SDKs or curling the endpoint, the query name must match what is defined in the
queries.hxfile exactly.
How It Works
RRF uses a simple but effective formula to combine rankings:
RRF_score(d) = Σ 1/(k + rank_i(d))
Where:
dis a document/resultrank_i(d)is the rank of documentdin the i-th result listkis a constant that controls the impact of ranking differences
The algorithm works by:
- Taking multiple ranked lists of results
- For each item, calculating its RRF score based on its positions across all lists
- Re-ranking items by their combined RRF scores
- Items that appear highly ranked in multiple lists get boosted
When to Use RerankRRF
Use RerankRRF when you want to:
- Merge multiple search methods: Combine vector search with BM25, or multiple vector searches with different embeddings
- Avoid score normalization: RRF doesn’t require calibrating or normalizing scores from different search systems
- Boost consensus results: Items that rank highly across multiple search strategies will be prioritized
- Implement hybrid search: Create a unified ranking from complementary search approaches
Parameters
k (optional, default: 60)
Controls how much weight is given to ranking position:
- Higher values (e.g., 80-100): Reduce the impact of ranking differences, treat all highly-ranked items more equally
- Lower values (e.g., 20-40): Emphasize top-ranked items more strongly, create sharper distinctions
- Default (60): Provides a balanced approach suitable for most use cases
ℹ️ Note
Start with the default value of 60 and adjust based on your specific data distribution and requirements.
Example 1: Basic reranking with default parameters
QUERY SearchDocuments(query_vec: [F64]) =>
results <- SearchV<Document>(query_vec, 100)
::RerankRRF
::RANGE(0, 10)
RETURN results
QUERY InsertDocument(vector: [F64], title: String, content: String) =>
document <- AddV<Document>(vector, {
title: title,
content: content
})
RETURN document
V::Document {
title: String,
content: String
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
documents = [
{"vector": [0.1, 0.2, 0.3, 0.4], "title": "Machine Learning Basics", "content": "Introduction to ML"},
{"vector": [0.2, 0.3, 0.4, 0.5], "title": "Deep Learning", "content": "Neural networks explained"},
{"vector": [0.15, 0.25, 0.35, 0.45], "title": "AI Applications", "content": "Real-world AI uses"},
]
for doc in documents:
client.query("InsertDocument", doc)
query_vector = [0.12, 0.22, 0.32, 0.42]
results = client.query("SearchDocuments", {"query_vec": query_vector})
print("Search results:", results)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = HelixDB::new(Some("http://localhost"), Some(6969), None);
let documents = vec![
(vec![0.1, 0.2, 0.3, 0.4], "Machine Learning Basics", "Introduction to ML"),
(vec![0.2, 0.3, 0.4, 0.5], "Deep Learning", "Neural networks explained"),
(vec![0.15, 0.25, 0.35, 0.45], "AI Applications", "Real-world AI uses"),
];
for (vector, title, content) in &documents {
let _inserted: serde_json::Value = client.query("InsertDocument", &json!({
"vector": vector,
"title": title,
"content": content,
})).await?;
}
let query_vector = vec![0.12, 0.22, 0.32, 0.42];
let results: serde_json::Value = client.query("SearchDocuments", &json!({
"query_vec": query_vector,
})).await?;
println!("Search results: {results:#?}");
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
documents := []map[string]any{
{"vector": []float64{0.1, 0.2, 0.3, 0.4}, "title": "Machine Learning Basics", "content": "Introduction to ML"},
{"vector": []float64{0.2, 0.3, 0.4, 0.5}, "title": "Deep Learning", "content": "Neural networks explained"},
{"vector": []float64{0.15, 0.25, 0.35, 0.45}, "title": "AI Applications", "content": "Real-world AI uses"},
}
for _, doc := range documents {
var inserted map[string]any
if err := client.Query("InsertDocument", helix.WithData(doc)).Scan(&inserted); err != nil {
log.Fatalf("InsertDocument failed: %s", err)
}
}
queryVector := []float64{0.12, 0.22, 0.32, 0.42}
var results map[string]any
if err := client.Query("SearchDocuments", helix.WithData(map[string]any{
"query_vec": queryVector,
})).Scan(&results); err != nil {
log.Fatalf("SearchDocuments failed: %s", err)
}
fmt.Printf("Search results: %#v\n", results)
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
const documents = [
{ vector: [0.1, 0.2, 0.3, 0.4], title: "Machine Learning Basics", content: "Introduction to ML" },
{ vector: [0.2, 0.3, 0.4, 0.5], title: "Deep Learning", content: "Neural networks explained" },
{ vector: [0.15, 0.25, 0.35, 0.45], title: "AI Applications", content: "Real-world AI uses" },
];
for (const doc of documents) {
await client.query("InsertDocument", doc);
}
const queryVector = [0.12, 0.22, 0.32, 0.42];
const results = await client.query("SearchDocuments", {
query_vec: queryVector,
});
console.log("Search results:", results);
}
main().catch((err) => {
console.error("SearchDocuments query failed:", err);
});
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.1,0.2,0.3,0.4],"title":"Machine Learning Basics","content":"Introduction to ML"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.2,0.3,0.4,0.5],"title":"Deep Learning","content":"Neural networks explained"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.15,0.25,0.35,0.45],"title":"AI Applications","content":"Real-world AI uses"}'
curl -X POST \
http://localhost:6969/SearchDocuments \
-H 'Content-Type: application/json' \
-d '{"query_vec":[0.12,0.22,0.32,0.42]}'
Example 2: Custom k parameter for more aggressive reranking
QUERY SearchWithCustomK(query_vec: [F64]) =>
results <- SearchV<Document>(query_vec, 100)
::RerankRRF(k: 30.0)
::RANGE(0, 20)
RETURN results
QUERY InsertDocument(vector: [F64], title: String, content: String) =>
document <- AddV<Document>(vector, {
title: title,
content: content
})
RETURN document
V::Document {
title: String,
content: String
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
documents = [
{"vector": [0.1, 0.2, 0.3, 0.4], "title": "Python Tutorial", "content": "Learn Python basics"},
{"vector": [0.2, 0.3, 0.4, 0.5], "title": "Advanced Python", "content": "Python design patterns"},
{"vector": [0.15, 0.25, 0.35, 0.45], "title": "Python for Data Science", "content": "Using pandas and numpy"},
]
for doc in documents:
client.query("InsertDocument", doc)
query_vector = [0.12, 0.22, 0.32, 0.42]
results = client.query("SearchWithCustomK", {"query_vec": query_vector})
print("Search results with custom k:", results)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = HelixDB::new(Some("http://localhost"), Some(6969), None);
let documents = vec![
(vec![0.1, 0.2, 0.3, 0.4], "Python Tutorial", "Learn Python basics"),
(vec![0.2, 0.3, 0.4, 0.5], "Advanced Python", "Python design patterns"),
(vec![0.15, 0.25, 0.35, 0.45], "Python for Data Science", "Using pandas and numpy"),
];
for (vector, title, content) in &documents {
let _inserted: serde_json::Value = client.query("InsertDocument", &json!({
"vector": vector,
"title": title,
"content": content,
})).await?;
}
let query_vector = vec![0.12, 0.22, 0.32, 0.42];
let results: serde_json::Value = client.query("SearchWithCustomK", &json!({
"query_vec": query_vector,
})).await?;
println!("Search results with custom k: {results:#?}");
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
documents := []map[string]any{
{"vector": []float64{0.1, 0.2, 0.3, 0.4}, "title": "Python Tutorial", "content": "Learn Python basics"},
{"vector": []float64{0.2, 0.3, 0.4, 0.5}, "title": "Advanced Python", "content": "Python design patterns"},
{"vector": []float64{0.15, 0.25, 0.35, 0.45}, "title": "Python for Data Science", "content": "Using pandas and numpy"},
}
for _, doc := range documents {
var inserted map[string]any
if err := client.Query("InsertDocument", helix.WithData(doc)).Scan(&inserted); err != nil {
log.Fatalf("InsertDocument failed: %s", err)
}
}
queryVector := []float64{0.12, 0.22, 0.32, 0.42}
var results map[string]any
if err := client.Query("SearchWithCustomK", helix.WithData(map[string]any{
"query_vec": queryVector,
})).Scan(&results); err != nil {
log.Fatalf("SearchWithCustomK failed: %s", err)
}
fmt.Printf("Search results with custom k: %#v\n", results)
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
const documents = [
{ vector: [0.1, 0.2, 0.3, 0.4], title: "Python Tutorial", content: "Learn Python basics" },
{ vector: [0.2, 0.3, 0.4, 0.5], title: "Advanced Python", content: "Python design patterns" },
{ vector: [0.15, 0.25, 0.35, 0.45], title: "Python for Data Science", content: "Using pandas and numpy" },
];
for (const doc of documents) {
await client.query("InsertDocument", doc);
}
const queryVector = [0.12, 0.22, 0.32, 0.42];
const results = await client.query("SearchWithCustomK", {
query_vec: queryVector,
});
console.log("Search results with custom k:", results);
}
main().catch((err) => {
console.error("SearchWithCustomK query failed:", err);
});
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.1,0.2,0.3,0.4],"title":"Python Tutorial","content":"Learn Python basics"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.2,0.3,0.4,0.5],"title":"Advanced Python","content":"Python design patterns"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.15,0.25,0.35,0.45],"title":"Python for Data Science","content":"Using pandas and numpy"}'
curl -X POST \
http://localhost:6969/SearchWithCustomK \
-H 'Content-Type: application/json' \
-d '{"query_vec":[0.12,0.22,0.32,0.42]}'
Example 3: Dynamic k parameter from query input
QUERY FlexibleRerank(query_vec: [F64], k_value: F64) =>
results <- SearchV<Document>(query_vec, 100)
::RerankRRF(k: k_value)
::RANGE(0, 10)
RETURN results
QUERY InsertDocument(vector: [F64], title: String, content: String) =>
document <- AddV<Document>(vector, {
title: title,
content: content
})
RETURN document
V::Document {
title: String,
content: String
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
documents = [
{"vector": [0.1, 0.2, 0.3, 0.4], "title": "Web Development", "content": "HTML, CSS, JavaScript"},
{"vector": [0.2, 0.3, 0.4, 0.5], "title": "Backend Engineering", "content": "APIs and databases"},
{"vector": [0.15, 0.25, 0.35, 0.45], "title": "DevOps", "content": "CI/CD and infrastructure"},
]
for doc in documents:
client.query("InsertDocument", doc)
query_vector = [0.12, 0.22, 0.32, 0.42]
# Try with different k values
for k in [30.0, 60.0, 90.0]:
results = client.query("FlexibleRerank", {
"query_vec": query_vector,
"k_value": k
})
print(f"Results with k={k}:", results)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = HelixDB::new(Some("http://localhost"), Some(6969), None);
let documents = vec![
(vec![0.1, 0.2, 0.3, 0.4], "Web Development", "HTML, CSS, JavaScript"),
(vec![0.2, 0.3, 0.4, 0.5], "Backend Engineering", "APIs and databases"),
(vec![0.15, 0.25, 0.35, 0.45], "DevOps", "CI/CD and infrastructure"),
];
for (vector, title, content) in &documents {
let _inserted: serde_json::Value = client.query("InsertDocument", &json!({
"vector": vector,
"title": title,
"content": content,
})).await?;
}
let query_vector = vec![0.12, 0.22, 0.32, 0.42];
// Try with different k values
for k in [30.0, 60.0, 90.0] {
let results: serde_json::Value = client.query("FlexibleRerank", &json!({
"query_vec": query_vector.clone(),
"k_value": k,
})).await?;
println!("Results with k={k}: {results:#?}");
}
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
documents := []map[string]any{
{"vector": []float64{0.1, 0.2, 0.3, 0.4}, "title": "Web Development", "content": "HTML, CSS, JavaScript"},
{"vector": []float64{0.2, 0.3, 0.4, 0.5}, "title": "Backend Engineering", "content": "APIs and databases"},
{"vector": []float64{0.15, 0.25, 0.35, 0.45}, "title": "DevOps", "content": "CI/CD and infrastructure"},
}
for _, doc := range documents {
var inserted map[string]any
if err := client.Query("InsertDocument", helix.WithData(doc)).Scan(&inserted); err != nil {
log.Fatalf("InsertDocument failed: %s", err)
}
}
queryVector := []float64{0.12, 0.22, 0.32, 0.42}
// Try with different k values
for _, k := range []float64{30.0, 60.0, 90.0} {
var results map[string]any
if err := client.Query("FlexibleRerank", helix.WithData(map[string]any{
"query_vec": queryVector,
"k_value": k,
})).Scan(&results); err != nil {
log.Fatalf("FlexibleRerank failed: %s", err)
}
fmt.Printf("Results with k=%.1f: %#v\n", k, results)
}
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
const documents = [
{ vector: [0.1, 0.2, 0.3, 0.4], title: "Web Development", content: "HTML, CSS, JavaScript" },
{ vector: [0.2, 0.3, 0.4, 0.5], title: "Backend Engineering", content: "APIs and databases" },
{ vector: [0.15, 0.25, 0.35, 0.45], title: "DevOps", content: "CI/CD and infrastructure" },
];
for (const doc of documents) {
await client.query("InsertDocument", doc);
}
const queryVector = [0.12, 0.22, 0.32, 0.42];
// Try with different k values
for (const k of [30.0, 60.0, 90.0]) {
const results = await client.query("FlexibleRerank", {
query_vec: queryVector,
k_value: k,
});
console.log(`Results with k=${k}:`, results);
}
}
main().catch((err) => {
console.error("FlexibleRerank query failed:", err);
});
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.1,0.2,0.3,0.4],"title":"Web Development","content":"HTML, CSS, JavaScript"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.2,0.3,0.4,0.5],"title":"Backend Engineering","content":"APIs and databases"}'
curl -X POST \
http://localhost:6969/InsertDocument \
-H 'Content-Type: application/json' \
-d '{"vector":[0.15,0.25,0.35,0.45],"title":"DevOps","content":"CI/CD and infrastructure"}'
# Try with different k values
curl -X POST \
http://localhost:6969/FlexibleRerank \
-H 'Content-Type: application/json' \
-d '{"query_vec":[0.12,0.22,0.32,0.42],"k_value":30.0}'
curl -X POST \
http://localhost:6969/FlexibleRerank \
-H 'Content-Type: application/json' \
-d '{"query_vec":[0.12,0.22,0.32,0.42],"k_value":60.0}'
Best Practices
Retrieve Sufficient Candidates
Always fetch more results than you ultimately need to give RRF sufficient candidates to work with:
// Good: Fetch 100, return 10
SearchV<Document>(vec, 100)::RerankRRF::RANGE(0, 10)
// Not ideal: Fetch 10, return 10
SearchV<Document>(vec, 10)::RerankRRF::RANGE(0, 10)
Parameter Tuning
Start with the default k=60 and adjust based on your observations:
- If results seem too similar, try a lower k (30-40) to emphasize top rankings
- If you want more variety, try a higher k (80-100) to flatten differences
- Test with real queries and evaluate result quality
Combining with Other Operations
RRF works well with filtering and other result operations:
QUERY AdvancedSearch(query_vec: [F64], min_score: F64) =>
results <- SearchV<Document>(query_vec, 200)
::WHERE(_.distance::GT(min_score)) // Filter first
::RerankRRF // Then rerank
::RANGE(0, 20) // Finally limit
RETURN results
Performance Considerations
RRF is computationally efficient with O(n) complexity, making it suitable for real-time applications. However:
- Larger candidate sets require more processing
- Balance result quality with query latency
- Consider caching frequently-accessed results
Use Cases
E-commerce Product Search
Combine text search with visual similarity for product discovery:
SearchV<Product>(query_embedding, 100)::RerankRRF(k: 50)::RANGE(0, 20)
Document Retrieval
Merge semantic search with keyword matching for comprehensive document retrieval:
SearchV<Document>(semantic_vec, 150)::RerankRRF::RANGE(0, 10)
Content Recommendation
Blend multiple recommendation signals (user preferences, popularity, recency):
SearchV<Content>(user_profile_vec, 100)::RerankRRF(k: 70)::RANGE(0, 15)
Related
- RerankMMR - For diversifying search results
- Vector Search - Basic vector search operations
- Result Operations - Other result manipulation operations