diff --git a/docs.json b/docs.json index af1b9a7..77bfb43 100644 --- a/docs.json +++ b/docs.json @@ -104,6 +104,14 @@ "icon": "share-nodes" }, "documentation/hql/keyword_search", + { + "group": "Rerankers", + "expanded": false, + "pages": [ + "documentation/hql/rerankers/overview" + ], + "icon": "arrows-up-down" + }, { "group": "Traversals", "expanded": false, diff --git a/documentation/hql/rerankers/overview copy.mdx b/documentation/hql/rerankers/overview copy.mdx new file mode 100644 index 0000000..6212961 --- /dev/null +++ b/documentation/hql/rerankers/overview copy.mdx @@ -0,0 +1,110 @@ +--- +title: "Rerankers Overview" +description: "Improve search result quality by reordering results after initial retrieval." +icon: "arrows-up-down" +--- + +## What are Rerankers? + +Rerankers are powerful post-processing operations that improve the quality and diversity of search results by reordering them after the initial retrieval phase. They enable you to: + +- Combine results from multiple search strategies (hybrid search) +- Reduce redundancy by diversifying results +- Optimize the relevance-diversity trade-off +- Improve the overall user experience of your search application + +## When to Use Rerankers + +Apply rerankers in your query pipeline when you need to: + +- **Merge multiple search methods**: Combine vector search with BM25 keyword search, or merge results from multiple vector searches +- **Diversify results**: Eliminate near-duplicate content and show varied perspectives +- **Optimize ranking**: Fine-tune the balance between relevance and variety based on your use case +- **Improve search quality**: Leverage sophisticated ranking algorithms without changing your underlying search infrastructure + +## Available Rerankers + +HelixQL provides two powerful reranking strategies: + +### RerankRRF (Reciprocal Rank Fusion) + +A technique for combining multiple ranked lists without requiring score calibration. Perfect for hybrid search scenarios where you want to merge results from different search methods. + +```rust +::RerankRRF() // Uses default k=60 +::RerankRRF(k: 30.0) // Custom k parameter +``` + +[Learn more about RerankRRF →](/documentation/hql/rerankers/rerank-rrf) + +### RerankMMR (Maximal Marginal Relevance) + +A diversification technique that balances relevance with diversity to reduce redundancy. Ideal when you want to show varied results instead of similar or duplicate content. + +```rust +::RerankMMR(lambda: 0.7) // Default cosine distance +::RerankMMR(lambda: 0.5, distance: "euclidean") // Custom distance metric +``` + +[Learn more about RerankMMR →](/documentation/hql/rerankers/rerank-mmr) + +## Basic Usage Pattern + +RRF Usage: +```rust focus=3 +QUERY SearchDocuments(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankRRF() // Apply reranking + ::RANGE(0, 10) // Get top 10 results + RETURN results +``` + +MMR Usage: + +```rust focus=3 +QUERY SearchDocuments(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: 0.7) // Apply reranking + ::RANGE(0, 10) // Get top 10 results + RETURN results +``` + +## Chaining Rerankers + +You can chain multiple rerankers together for complex result optimization: + +```rust focus={3-4} +QUERY AdvancedSearch(query_vec: [F64]) => + results <- SearchV(query_vec, 150) + ::RerankRRF(k: 60) // First: combine multiple rankings + ::RerankMMR(lambda: 0.6) // Then: diversify results + ::RANGE(0, 10) + RETURN results +``` + +## Best Practices + +1. **Retrieve more results initially**: Fetch 100-200 candidates to give rerankers sufficient options to work with +2. **Apply rerankers before RANGE**: Rerank first, then limit the number of results returned +3. **Choose the right reranker**: Use RRF for combining searches, MMR for diversification +4. **Test with your data**: Experiment with different parameters to find what works best for your use case + +## Common Patterns + +```rust +// Pattern 1: Simple diversification +SearchV(vec, 100)::RerankMMR(lambda: 0.7)::RANGE(0, 10) + +// Pattern 2: Hybrid search fusion +SearchV(vec, 100)::RerankRRF()::RANGE(0, 10) + +// Pattern 3: Fusion + diversification +SearchV(vec, 150)::RerankRRF()::RerankMMR(lambda: 0.6)::RANGE(0, 10) +``` + +## Next Steps + +Explore the detailed documentation for each reranker: + +- [RerankRRF (Reciprocal Rank Fusion)](/documentation/hql/rerankers/rerank-rrf) +- [RerankMMR (Maximal Marginal Relevance)](/documentation/hql/rerankers/rerank-mmr) diff --git a/documentation/hql/rerankers/overview.mdx b/documentation/hql/rerankers/overview.mdx new file mode 100644 index 0000000..4fa13a2 --- /dev/null +++ b/documentation/hql/rerankers/overview.mdx @@ -0,0 +1,99 @@ +--- +title: "Rerankers Overview" +description: "Improve search result quality by reordering results after initial retrieval." +icon: "arrows-up-down" +--- + +## What are Rerankers? + +Rerankers are powerful post-processing operations that improve the quality and diversity of search results by reordering them after the initial retrieval phase. They enable you to: + +- Combine results from multiple search strategies (hybrid search) +- Reduce redundancy by diversifying results +- Optimize the relevance-diversity trade-off +- Improve the overall user experience of your search application + +## When to Use Rerankers + +Apply rerankers in your query pipeline when you need to: + +- **Merge multiple search methods**: Combine vector search with BM25 keyword search, or merge results from multiple vector searches +- **Diversify results**: Eliminate near-duplicate content and show varied perspectives +- **Optimize ranking**: Fine-tune the balance between relevance and variety based on your use case +- **Improve search quality**: Leverage sophisticated ranking algorithms without changing your underlying search infrastructure + +## Available Rerankers + +HelixQL provides two powerful reranking strategies: + +### RerankRRF (Reciprocal Rank Fusion) + +A technique for combining multiple ranked lists without requiring score calibration. Perfect for hybrid search scenarios where you want to merge results from different search methods. + +```rust +::RerankRRF // Uses default k=60 +::RerankRRF(k: 30.0) // Custom k parameter +``` + +### RerankMMR (Maximal Marginal Relevance) + +A diversification technique that balances relevance with diversity to reduce redundancy. Ideal when you want to show varied results instead of similar or duplicate content. + +```rust +::RerankMMR(lambda: 0.7) +``` + + +## Basic Usage Pattern + +RRF Usage: +```rust focus=3 +QUERY SearchDocuments(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankRRF // Apply reranking + ::RANGE(0, 10) // Get top 10 results + RETURN results +``` + +MMR Usage: + +```rust focus=3 +QUERY SearchDocuments(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: 0.7) // Apply reranking + ::RANGE(0, 10) // Get top 10 results + RETURN results +``` + +## Chaining Rerankers + +You can chain multiple rerankers together for complex result optimization: + +```rust focus={3-4} +QUERY AdvancedSearch(query_vec: [F64]) => + results <- SearchV(query_vec, 150) + ::RerankRRF(k: 60) // First: combine multiple rankings + ::RerankMMR(lambda: 0.6) // Then: diversify results + ::RANGE(0, 10) + RETURN results +``` + +## Best Practices + +1. **Retrieve more results initially**: Fetch 100-200 candidates to give rerankers sufficient options to work with +2. **Apply rerankers before RANGE**: Rerank first, then limit the number of results returned +3. **Choose the right reranker**: Use RRF for combining searches, MMR for diversification +4. **Test with your data**: Experiment with different parameters to find what works best for your use case + +## Common Patterns + +```rust +// Pattern 1: Simple diversification +SearchV(vec, 100)::RerankMMR(lambda: 0.7)::RANGE(0, 10) + +// Pattern 2: Hybrid search fusion +SearchV(vec, 100)::RerankRRF::RANGE(0, 10) + +// Pattern 3: Fusion + diversification +SearchV(vec, 150)::RerankRRF::RerankMMR(lambda: 0.6)::RANGE(0, 10) +``` \ No newline at end of file diff --git a/documentation/hql/rerankers/rerank-mmr.mdx b/documentation/hql/rerankers/rerank-mmr.mdx new file mode 100644 index 0000000..597a709 --- /dev/null +++ b/documentation/hql/rerankers/rerank-mmr.mdx @@ -0,0 +1,971 @@ +--- +title: "RerankMMR" +description: "Diversify search results using Maximal Marginal Relevance." +icon: "grid-2" +--- + +## Rerank with MMR (Maximal Marginal Relevance)   + +RerankMMR is a diversification technique that balances relevance with diversity to reduce redundancy in search results. It iteratively selects results that are both relevant to the query and dissimilar to already-selected results, ensuring users see varied content instead of near-duplicates. + +```rust +::RerankMMR(lambda: 0.7) // Cosine distance (default) +::RerankMMR(lambda: 0.5, distance: "euclidean") // Euclidean distance +::RerankMMR(lambda: 0.6, distance: "dotproduct") // Dot product distance +``` + + +When using the SDKs or curling the endpoint, the query name must match what is defined in the `queries.hx` file exactly. + + +## How It Works + +MMR uses an iterative selection process with the following formula: + +``` +MMR = λ × Sim1(d, q) - (1-λ) × max(Sim2(d, d_i)) +``` + +Where: +- `d` is a candidate document +- `q` is the query +- `d_i` are already-selected documents +- `λ` (lambda) controls the relevance vs. diversity trade-off +- `Sim1` measures relevance to the query +- `Sim2` measures similarity to selected documents + +The algorithm works by: +1. Starting with the most relevant result +2. For each subsequent position, calculating MMR scores for remaining candidates +3. Selecting the candidate with the highest MMR score (balancing relevance and novelty) +4. Repeating until all positions are filled + +## When to Use RerankMMR + +Use RerankMMR when you want to: + +- **Diversify search results**: Eliminate near-duplicate content and show varied perspectives +- **Reduce redundancy**: Avoid showing multiple similar articles, products, or documents +- **Improve user experience**: Provide comprehensive coverage rather than repetitive results +- **Balance exploration and relevance**: Give users both highly relevant and exploratory options + +## Parameters + +### lambda (required) + +A value between 0.0 and 1.0 that controls the relevance vs. diversity trade-off: + +- **Higher values (0.7-1.0)**: Favor relevance over diversity + - Results will be more similar to original ranking + - Use when relevance is paramount + - Minimal diversification + +- **Lower values (0.0-0.3)**: Favor diversity over relevance + - Results will be maximally varied + - Use when avoiding redundancy is critical + - May include less relevant but diverse results + +- **Balanced values (0.4-0.6)**: Balance relevance and diversity + - Good compromise for most use cases + - Maintains reasonable relevance while reducing duplicates + +- **Typical recommendation**: Start with 0.5-0.7 for most applications + + +The optimal lambda value depends on your specific use case. Test different values to find what works best for your users. + + +### distance (optional, default: "cosine") + +The distance metric used for calculating similarity: + +- **"cosine"**: Cosine similarity (default) + - Works well for normalized vectors + - Common choice for text embeddings + - Range: -1 to 1 + +- **"euclidean"**: Euclidean distance + - Works well for absolute distances + - Sensitive to vector magnitude + - Good for spatial data + +- **"dotproduct"**: Dot product similarity + - Works well for unnormalized vectors + - Computationally efficient + - Considers vector magnitude + +## Example 1: Basic diversification with default cosine distance + + +```rust Query focus={1-4} +QUERY DiverseSearch(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: 0.7) + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, category: String) => + document <- AddV(vector, { + title: title, + category: category + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + category: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +from helix.client import Client + +client = Client(local=True, port=6969) + +documents = [ + {"vector": [0.1, 0.2, 0.3, 0.4], "title": "Introduction to Python", "category": "Programming"}, + {"vector": [0.11, 0.21, 0.31, 0.41], "title": "Python Basics", "category": "Programming"}, + {"vector": [0.5, 0.6, 0.7, 0.8], "title": "Machine Learning Overview", "category": "AI"}, + {"vector": [0.51, 0.61, 0.71, 0.81], "title": "Deep Learning Intro", "category": "AI"}, + {"vector": [0.3, 0.4, 0.5, 0.6], "title": "Data Structures", "category": "Computer Science"}, +] + +for doc in documents: + client.query("InsertDocument", doc) + +query_vector = [0.12, 0.22, 0.32, 0.42] +results = client.query("DiverseSearch", {"query_vec": query_vector}) +print("Diverse search results:", results) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = HelixDB::new(Some("http://localhost"), Some(6969), None); + + let documents = vec![ + (vec![0.1, 0.2, 0.3, 0.4], "Introduction to Python", "Programming"), + (vec![0.11, 0.21, 0.31, 0.41], "Python Basics", "Programming"), + (vec![0.5, 0.6, 0.7, 0.8], "Machine Learning Overview", "AI"), + (vec![0.51, 0.61, 0.71, 0.81], "Deep Learning Intro", "AI"), + (vec![0.3, 0.4, 0.5, 0.6], "Data Structures", "Computer Science"), + ]; + + for (vector, title, category) in &documents { + let _inserted: serde_json::Value = client.query("InsertDocument", &json!({ + "vector": vector, + "title": title, + "category": category, + })).await?; + } + + let query_vector = vec![0.12, 0.22, 0.32, 0.42]; + let results: serde_json::Value = client.query("DiverseSearch", &json!({ + "query_vec": query_vector, + })).await?; + + println!("Diverse search results: {results:#?}"); + + Ok(()) +} +``` + +```go Go [expandable] +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": "Introduction to Python", "category": "Programming"}, + {"vector": []float64{0.11, 0.21, 0.31, 0.41}, "title": "Python Basics", "category": "Programming"}, + {"vector": []float64{0.5, 0.6, 0.7, 0.8}, "title": "Machine Learning Overview", "category": "AI"}, + {"vector": []float64{0.51, 0.61, 0.71, 0.81}, "title": "Deep Learning Intro", "category": "AI"}, + {"vector": []float64{0.3, 0.4, 0.5, 0.6}, "title": "Data Structures", "category": "Computer Science"}, + } + + 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("DiverseSearch", helix.WithData(map[string]any{ + "query_vec": queryVector, + })).Scan(&results); err != nil { + log.Fatalf("DiverseSearch failed: %s", err) + } + + fmt.Printf("Diverse search results: %#v\n", results) +} +``` + +```typescript TypeScript [expandable] +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: "Introduction to Python", category: "Programming" }, + { vector: [0.11, 0.21, 0.31, 0.41], title: "Python Basics", category: "Programming" }, + { vector: [0.5, 0.6, 0.7, 0.8], title: "Machine Learning Overview", category: "AI" }, + { vector: [0.51, 0.61, 0.71, 0.81], title: "Deep Learning Intro", category: "AI" }, + { vector: [0.3, 0.4, 0.5, 0.6], title: "Data Structures", category: "Computer Science" }, + ]; + + 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("DiverseSearch", { + query_vec: queryVector, + }); + + console.log("Diverse search results:", results); +} + +main().catch((err) => { + console.error("DiverseSearch query failed:", err); +}); +``` + +```bash Curl [expandable] +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.1,0.2,0.3,0.4],"title":"Introduction to Python","category":"Programming"}' + +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.11,0.21,0.31,0.41],"title":"Python Basics","category":"Programming"}' + +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.5,0.6,0.7,0.8],"title":"Machine Learning Overview","category":"AI"}' + +curl -X POST \ + http://localhost:6969/DiverseSearch \ + -H 'Content-Type: application/json' \ + -d '{"query_vec":[0.12,0.22,0.32,0.42]}' +``` + + +--- + +## Example 2: High diversity with euclidean distance + + +```rust Query focus={1-4} +QUERY HighDiversitySearch(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: 0.3, distance: "euclidean") + ::RANGE(0, 15) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, category: String) => + document <- AddV(vector, { + title: title, + category: category + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + category: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +from helix.client import Client + +client = Client(local=True, port=6969) + +documents = [ + {"vector": [0.1, 0.2, 0.3, 0.4], "title": "React Tutorial", "category": "Frontend"}, + {"vector": [0.12, 0.22, 0.32, 0.42], "title": "React Hooks", "category": "Frontend"}, + {"vector": [0.5, 0.6, 0.7, 0.8], "title": "Node.js Guide", "category": "Backend"}, + {"vector": [0.2, 0.3, 0.4, 0.5], "title": "Vue.js Basics", "category": "Frontend"}, +] + +for doc in documents: + client.query("InsertDocument", doc) + +query_vector = [0.11, 0.21, 0.31, 0.41] +results = client.query("HighDiversitySearch", {"query_vec": query_vector}) +print("High diversity results:", results) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = HelixDB::new(Some("http://localhost"), Some(6969), None); + + let documents = vec![ + (vec![0.1, 0.2, 0.3, 0.4], "React Tutorial", "Frontend"), + (vec![0.12, 0.22, 0.32, 0.42], "React Hooks", "Frontend"), + (vec![0.5, 0.6, 0.7, 0.8], "Node.js Guide", "Backend"), + (vec![0.2, 0.3, 0.4, 0.5], "Vue.js Basics", "Frontend"), + ]; + + for (vector, title, category) in &documents { + let _inserted: serde_json::Value = client.query("InsertDocument", &json!({ + "vector": vector, + "title": title, + "category": category, + })).await?; + } + + let query_vector = vec![0.11, 0.21, 0.31, 0.41]; + let results: serde_json::Value = client.query("HighDiversitySearch", &json!({ + "query_vec": query_vector, + })).await?; + + println!("High diversity results: {results:#?}"); + + Ok(()) +} +``` + +```go Go [expandable] +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": "React Tutorial", "category": "Frontend"}, + {"vector": []float64{0.12, 0.22, 0.32, 0.42}, "title": "React Hooks", "category": "Frontend"}, + {"vector": []float64{0.5, 0.6, 0.7, 0.8}, "title": "Node.js Guide", "category": "Backend"}, + {"vector": []float64{0.2, 0.3, 0.4, 0.5}, "title": "Vue.js Basics", "category": "Frontend"}, + } + + 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.11, 0.21, 0.31, 0.41} + var results map[string]any + if err := client.Query("HighDiversitySearch", helix.WithData(map[string]any{ + "query_vec": queryVector, + })).Scan(&results); err != nil { + log.Fatalf("HighDiversitySearch failed: %s", err) + } + + fmt.Printf("High diversity results: %#v\n", results) +} +``` + +```typescript TypeScript [expandable] +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: "React Tutorial", category: "Frontend" }, + { vector: [0.12, 0.22, 0.32, 0.42], title: "React Hooks", category: "Frontend" }, + { vector: [0.5, 0.6, 0.7, 0.8], title: "Node.js Guide", category: "Backend" }, + { vector: [0.2, 0.3, 0.4, 0.5], title: "Vue.js Basics", category: "Frontend" }, + ]; + + for (const doc of documents) { + await client.query("InsertDocument", doc); + } + + const queryVector = [0.11, 0.21, 0.31, 0.41]; + const results = await client.query("HighDiversitySearch", { + query_vec: queryVector, + }); + + console.log("High diversity results:", results); +} + +main().catch((err) => { + console.error("HighDiversitySearch query failed:", err); +}); +``` + +```bash Curl [expandable] +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.1,0.2,0.3,0.4],"title":"React Tutorial","category":"Frontend"}' + +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.5,0.6,0.7,0.8],"title":"Node.js Guide","category":"Backend"}' + +curl -X POST \ + http://localhost:6969/HighDiversitySearch \ + -H 'Content-Type: application/json' \ + -d '{"query_vec":[0.11,0.21,0.31,0.41]}' +``` + + +--- + +## Example 3: Balanced approach with dot product + + +```rust Query focus={1-4} +QUERY BalancedSearch(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: 0.5, distance: "dotproduct") + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, category: String) => + document <- AddV(vector, { + title: title, + category: category + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + category: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +from helix.client import Client + +client = Client(local=True, port=6969) + +documents = [ + {"vector": [0.1, 0.2, 0.3, 0.4], "title": "Database Design", "category": "Architecture"}, + {"vector": [0.11, 0.21, 0.31, 0.41], "title": "SQL Optimization", "category": "Database"}, + {"vector": [0.5, 0.6, 0.7, 0.8], "title": "NoSQL Systems", "category": "Database"}, + {"vector": [0.3, 0.4, 0.5, 0.6], "title": "API Design", "category": "Architecture"}, +] + +for doc in documents: + client.query("InsertDocument", doc) + +query_vector = [0.12, 0.22, 0.32, 0.42] +results = client.query("BalancedSearch", {"query_vec": query_vector}) +print("Balanced search results:", results) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = HelixDB::new(Some("http://localhost"), Some(6969), None); + + let documents = vec![ + (vec![0.1, 0.2, 0.3, 0.4], "Database Design", "Architecture"), + (vec![0.11, 0.21, 0.31, 0.41], "SQL Optimization", "Database"), + (vec![0.5, 0.6, 0.7, 0.8], "NoSQL Systems", "Database"), + (vec![0.3, 0.4, 0.5, 0.6], "API Design", "Architecture"), + ]; + + for (vector, title, category) in &documents { + let _inserted: serde_json::Value = client.query("InsertDocument", &json!({ + "vector": vector, + "title": title, + "category": category, + })).await?; + } + + let query_vector = vec![0.12, 0.22, 0.32, 0.42]; + let results: serde_json::Value = client.query("BalancedSearch", &json!({ + "query_vec": query_vector, + })).await?; + + println!("Balanced search results: {results:#?}"); + + Ok(()) +} +``` + +```go Go [expandable] +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": "Database Design", "category": "Architecture"}, + {"vector": []float64{0.11, 0.21, 0.31, 0.41}, "title": "SQL Optimization", "category": "Database"}, + {"vector": []float64{0.5, 0.6, 0.7, 0.8}, "title": "NoSQL Systems", "category": "Database"}, + {"vector": []float64{0.3, 0.4, 0.5, 0.6}, "title": "API Design", "category": "Architecture"}, + } + + 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("BalancedSearch", helix.WithData(map[string]any{ + "query_vec": queryVector, + })).Scan(&results); err != nil { + log.Fatalf("BalancedSearch failed: %s", err) + } + + fmt.Printf("Balanced search results: %#v\n", results) +} +``` + +```typescript TypeScript [expandable] +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: "Database Design", category: "Architecture" }, + { vector: [0.11, 0.21, 0.31, 0.41], title: "SQL Optimization", category: "Database" }, + { vector: [0.5, 0.6, 0.7, 0.8], title: "NoSQL Systems", category: "Database" }, + { vector: [0.3, 0.4, 0.5, 0.6], title: "API Design", category: "Architecture" }, + ]; + + 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("BalancedSearch", { + query_vec: queryVector, + }); + + console.log("Balanced search results:", results); +} + +main().catch((err) => { + console.error("BalancedSearch query failed:", err); +}); +``` + +```bash Curl [expandable] +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.1,0.2,0.3,0.4],"title":"Database Design","category":"Architecture"}' + +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.5,0.6,0.7,0.8],"title":"NoSQL Systems","category":"Database"}' + +curl -X POST \ + http://localhost:6969/BalancedSearch \ + -H 'Content-Type: application/json' \ + -d '{"query_vec":[0.12,0.22,0.32,0.42]}' +``` + + +--- + +## Example 4: Dynamic lambda for user-controlled diversity + + +```rust Query focus={1-4} +QUERY CustomDiversitySearch(query_vec: [F64], diversity: F64) => + results <- SearchV(query_vec, 100) + ::RerankMMR(lambda: diversity) + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, category: String) => + document <- AddV(vector, { + title: title, + category: category + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + category: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +from helix.client import Client + +client = Client(local=True, port=6969) + +documents = [ + {"vector": [0.1, 0.2, 0.3, 0.4], "title": "Cloud Computing", "category": "Infrastructure"}, + {"vector": [0.11, 0.21, 0.31, 0.41], "title": "AWS Services", "category": "Cloud"}, + {"vector": [0.5, 0.6, 0.7, 0.8], "title": "Docker Containers", "category": "DevOps"}, + {"vector": [0.3, 0.4, 0.5, 0.6], "title": "Kubernetes", "category": "Orchestration"}, +] + +for doc in documents: + client.query("InsertDocument", doc) + +query_vector = [0.12, 0.22, 0.32, 0.42] + +# Try different diversity levels +for diversity_level in [0.3, 0.5, 0.7, 0.9]: + results = client.query("CustomDiversitySearch", { + "query_vec": query_vector, + "diversity": diversity_level + }) + print(f"Results with diversity={diversity_level}:", results) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = HelixDB::new(Some("http://localhost"), Some(6969), None); + + let documents = vec![ + (vec![0.1, 0.2, 0.3, 0.4], "Cloud Computing", "Infrastructure"), + (vec![0.11, 0.21, 0.31, 0.41], "AWS Services", "Cloud"), + (vec![0.5, 0.6, 0.7, 0.8], "Docker Containers", "DevOps"), + (vec![0.3, 0.4, 0.5, 0.6], "Kubernetes", "Orchestration"), + ]; + + for (vector, title, category) in &documents { + let _inserted: serde_json::Value = client.query("InsertDocument", &json!({ + "vector": vector, + "title": title, + "category": category, + })).await?; + } + + let query_vector = vec![0.12, 0.22, 0.32, 0.42]; + + // Try different diversity levels + for diversity in [0.3, 0.5, 0.7, 0.9] { + let results: serde_json::Value = client.query("CustomDiversitySearch", &json!({ + "query_vec": query_vector.clone(), + "diversity": diversity, + })).await?; + println!("Results with diversity={diversity}: {results:#?}"); + } + + Ok(()) +} +``` + +```go Go [expandable] +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": "Cloud Computing", "category": "Infrastructure"}, + {"vector": []float64{0.11, 0.21, 0.31, 0.41}, "title": "AWS Services", "category": "Cloud"}, + {"vector": []float64{0.5, 0.6, 0.7, 0.8}, "title": "Docker Containers", "category": "DevOps"}, + {"vector": []float64{0.3, 0.4, 0.5, 0.6}, "title": "Kubernetes", "category": "Orchestration"}, + } + + 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 different diversity levels + for _, diversity := range []float64{0.3, 0.5, 0.7, 0.9} { + var results map[string]any + if err := client.Query("CustomDiversitySearch", helix.WithData(map[string]any{ + "query_vec": queryVector, + "diversity": diversity, + })).Scan(&results); err != nil { + log.Fatalf("CustomDiversitySearch failed: %s", err) + } + fmt.Printf("Results with diversity=%.1f: %#v\n", diversity, results) + } +} +``` + +```typescript TypeScript [expandable] +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: "Cloud Computing", category: "Infrastructure" }, + { vector: [0.11, 0.21, 0.31, 0.41], title: "AWS Services", category: "Cloud" }, + { vector: [0.5, 0.6, 0.7, 0.8], title: "Docker Containers", category: "DevOps" }, + { vector: [0.3, 0.4, 0.5, 0.6], title: "Kubernetes", category: "Orchestration" }, + ]; + + for (const doc of documents) { + await client.query("InsertDocument", doc); + } + + const queryVector = [0.12, 0.22, 0.32, 0.42]; + + // Try different diversity levels + for (const diversity of [0.3, 0.5, 0.7, 0.9]) { + const results = await client.query("CustomDiversitySearch", { + query_vec: queryVector, + diversity: diversity, + }); + console.log(`Results with diversity=${diversity}:`, results); + } +} + +main().catch((err) => { + console.error("CustomDiversitySearch query failed:", err); +}); +``` + +```bash Curl [expandable] +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.1,0.2,0.3,0.4],"title":"Cloud Computing","category":"Infrastructure"}' + +curl -X POST \ + http://localhost:6969/InsertDocument \ + -H 'Content-Type: application/json' \ + -d '{"vector":[0.5,0.6,0.7,0.8],"title":"Docker Containers","category":"DevOps"}' + +# Try different diversity levels +curl -X POST \ + http://localhost:6969/CustomDiversitySearch \ + -H 'Content-Type: application/json' \ + -d '{"query_vec":[0.12,0.22,0.32,0.42],"diversity":0.7}' + +curl -X POST \ + http://localhost:6969/CustomDiversitySearch \ + -H 'Content-Type: application/json' \ + -d '{"query_vec":[0.12,0.22,0.32,0.42],"diversity":0.3}' +``` + + +--- + +## Chaining with RerankRRF + +Combine RRF and MMR for hybrid search with diversification: + + +```rust Query focus={1-5} +QUERY HybridDiverseSearch(query_vec: [F64]) => + results <- SearchV(query_vec, 150) + ::RerankRRF(k: 60) // First: combine rankings + ::RerankMMR(lambda: 0.6) // Then: diversify + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, category: String) => + document <- AddV(vector, { + title: title, + category: category + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + category: String +} +``` + + +## Best Practices + +### Retrieve Sufficient Candidates + +MMR works best with a larger pool of candidates: + +```rust +// Good: Fetch 100-200, return 10-20 +SearchV(vec, 100)::RerankMMR(lambda: 0.7)::RANGE(0, 10) + +// Not ideal: Fetch 10, return 10 +SearchV(vec, 10)::RerankMMR(lambda: 0.7)::RANGE(0, 10) +``` + +### Lambda Parameter Tuning + +Start with these guidelines and adjust based on your results: + +- **News/Articles**: 0.5-0.6 (balance coverage and relevance) +- **E-commerce**: 0.6-0.7 (favor relevance, some variety) +- **Content Discovery**: 0.3-0.5 (favor diversity) +- **FAQ/Support**: 0.7-0.9 (favor relevance) + +### Choosing Distance Metrics + +- **Text embeddings**: Use "cosine" (default) +- **Image embeddings**: Use "cosine" or "euclidean" +- **Custom vectors**: Test all three and compare results + +### Combining with Filtering + +Apply filters before reranking for better performance: + +```rust +QUERY FilteredDiverseSearch(query_vec: [F64], category: String) => + results <- SearchV(query_vec, 200) + ::WHERE(_::{category}::EQ(category)) // Filter first + ::RerankMMR(lambda: 0.6) // Then diversify + ::RANGE(0, 15) + RETURN results +``` + +## Performance Considerations + + +MMR has O(n²) complexity due to pairwise comparisons. For optimal performance: +- Limit the candidate set (e.g., 100-200 results) +- Consider caching for frequently-accessed queries +- Monitor query latency and adjust candidate size accordingly + + +Key performance factors: +- **Candidate set size**: Larger sets increase computation time quadratically +- **Distance metric**: Dot product is fastest, euclidean is slowest +- **Result count**: More results require more iterations + +## Troubleshooting + +### Results seem too similar + +**Problem**: Results still show near-duplicates + +**Solutions**: +- Lower lambda value (try 0.3-0.5) +- Increase candidate pool size +- Verify your vectors capture meaningful differences + +### Results seem irrelevant + +**Problem**: Diverse results but poor relevance + +**Solutions**: +- Increase lambda value (try 0.7-0.9) +- Ensure initial search retrieves quality candidates +- Consider using RRF before MMR + +### Performance issues + +**Problem**: Queries are too slow + +**Solutions**: +- Reduce candidate pool size +- Use dot product distance metric +- Return fewer final results +- Consider caching popular queries + +## Use Cases + +### News and Media + +Diversify news articles to show varied perspectives: + +```rust +SearchV
(query_vec, 100)::RerankMMR(lambda: 0.5)::RANGE(0, 10) +``` + +### E-commerce + +Show varied product categories while maintaining relevance: + +```rust +SearchV(query_vec, 150)::RerankMMR(lambda: 0.65)::RANGE(0, 20) +``` + +### Content Discovery + +Maximize exploration with diverse recommendations: + +```rust +SearchV(user_vec, 200)::RerankMMR(lambda: 0.4, distance: "euclidean")::RANGE(0, 15) +``` + +### Document Retrieval + +Balance comprehensive coverage with relevance: + +```rust +SearchV(query_vec, 100)::RerankMMR(lambda: 0.6)::RANGE(0, 10) +``` + +## Related + +- [RerankRRF](/documentation/hql/rerankers/rerank-rrf) - For combining multiple search results +- [Rerankers Overview](/documentation/hql/rerankers/overview) - General reranking concepts +- [Vector Search](/documentation/hql/vectors/searching) - Basic vector search operations diff --git a/documentation/hql/rerankers/rerank-rrf.mdx b/documentation/hql/rerankers/rerank-rrf.mdx new file mode 100644 index 0000000..7f71499 --- /dev/null +++ b/documentation/hql/rerankers/rerank-rrf.mdx @@ -0,0 +1,677 @@ +--- +title: "RerankRRF" +description: "Combine multiple ranked lists using Reciprocal Rank Fusion." +icon: "layer-group" +--- + +## 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. + +```rust +::RerankRRF() // Uses default k=60 +::RerankRRF(k: 60) // Custom k parameter +``` + + +When using the SDKs or curling the endpoint, the query name must match what is defined in the `queries.hx` file exactly. + + +## How It Works + +RRF uses a simple but effective formula to combine rankings: + +``` +RRF_score(d) = Σ 1/(k + rank_i(d)) +``` + +Where: +- `d` is a document/result +- `rank_i(d)` is the rank of document `d` in the i-th result list +- `k` is a constant that controls the impact of ranking differences + +The algorithm works by: +1. Taking multiple ranked lists of results +2. For each item, calculating its RRF score based on its positions across all lists +3. Re-ranking items by their combined RRF scores +4. 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 + + +Start with the default value of 60 and adjust based on your specific data distribution and requirements. + + +## Example 1: Basic reranking with default parameters + + +```rust Query focus={1-4} +QUERY SearchDocuments(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankRRF() + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, content: String) => + document <- AddV(vector, { + title: title, + content: content + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + content: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +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) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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(()) +} +``` + +```go Go [expandable] +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) +} +``` + +```typescript TypeScript [expandable] +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); +}); +``` + +```bash Curl [expandable] +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 + + +```rust Query focus={1-4} +QUERY SearchWithCustomK(query_vec: [F64]) => + results <- SearchV(query_vec, 100) + ::RerankRRF(k: 30.0) + ::RANGE(0, 20) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, content: String) => + document <- AddV(vector, { + title: title, + content: content + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + content: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +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) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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(()) +} +``` + +```go Go [expandable] +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) +} +``` + +```typescript TypeScript [expandable] +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); +}); +``` + +```bash Curl [expandable] +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 + + +```rust Query focus={1-4} +QUERY FlexibleRerank(query_vec: [F64], k_value: F64) => + results <- SearchV(query_vec, 100) + ::RerankRRF(k: k_value) + ::RANGE(0, 10) + RETURN results + +QUERY InsertDocument(vector: [F64], title: String, content: String) => + document <- AddV(vector, { + title: title, + content: content + }) + RETURN document +``` + +```rust Schema +V::Document { + title: String, + content: String +} +``` + + +Here's how to run the query using the SDKs or curl + + +```python Python [expandable] +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) +``` + +```rust Rust [expandable] +use helix_rs::{HelixDB, HelixDBClient}; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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(()) +} +``` + +```go Go [expandable] +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) + } +} +``` + +```typescript TypeScript [expandable] +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); +}); +``` + +```bash Curl [expandable] +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: + +```rust +// Good: Fetch 100, return 10 +SearchV(vec, 100)::RerankRRF()::RANGE(0, 10) + +// Not ideal: Fetch 10, return 10 +SearchV(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: + +```rust +QUERY AdvancedSearch(query_vec: [F64], min_score: F64) => + results <- SearchV(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: + +```rust +SearchV(query_embedding, 100)::RerankRRF(k: 50)::RANGE(0, 20) +``` + +### Document Retrieval + +Merge semantic search with keyword matching for comprehensive document retrieval: + +```rust +SearchV(semantic_vec, 150)::RerankRRF()::RANGE(0, 10) +``` + +### Content Recommendation + +Blend multiple recommendation signals (user preferences, popularity, recency): + +```rust +SearchV(user_profile_vec, 100)::RerankRRF(k: 70)::RANGE(0, 15) +``` + +## Related + +- [RerankMMR](/documentation/hql/rerankers/rerank-mmr) - For diversifying search results +- [Vector Search](/documentation/hql/vectors/searching) - Basic vector search operations +- [Result Operations](/documentation/hql/result_ops) - Other result manipulation operations