# How HybridSearch Implements RRF Fusion for Dense and Sparse Retrieval

> Discover how Semantica's HybridSearch uses RRF fusion to combine dense vector similarity and sparse metadata filtering, boosting search relevance with aggregated reciprocal ranks.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: deep-dive
- Published: 2026-08-17

---

**Semantica's HybridSearch module combines dense vector similarity and sparse metadata filtering by scoring documents with the Reciprocal Rank Fusion (RRF) formula, aggregating reciprocal ranks across result lists with a configurable constant (default 60).**

Semantica's `HybridSearch` class provides a unified interface for hybrid retrieval, fusing results from dense vector embeddings and sparse metadata constraints. The implementation relies on the `SearchRanker` utility to reconcile ranked lists from different sources using the **Reciprocal Rank Fusion (RRF)** algorithm. This approach ensures that documents appearing in both dense and sparse results receive appropriately boosted scores without requiring explicit score normalization between disparate search modalities.

## The RRF Algorithm and Constant k

The RRF implementation in Semantica follows the established reciprocal rank formula for combining multiple ranked lists:

```

score(d) = Σ (1 / (k + rank_l(d)))

```

Where:
- `d` represents a document ID
- `rank_l(d)` is the document's zero-indexed position in list `l`
- `k` is a damping constant that prevents high-ranked items from dominating (default **60**)

In [`semantica/vector_store/hybrid_search.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/vector_store/hybrid_search.py), the `SearchRanker.reciprocal_rank_fusion` method (lines 48-86) implements this calculation by iterating over each result list, computing `1/(k+rank)` for every document, and accumulating these values into a final score dictionary.

## Dense and Sparse Retrieval Components

HybridSearch orchestrates two distinct retrieval mechanisms before fusion occurs.

### Dense Vector Search (`_vector_search`)

The dense component performs cosine-similarity search against vector embeddings. When `HybridSearch.search()` receives a `query_vector`, the internal `_vector_search` method retrieves the top-k most similar vectors. These results form the first ranked list passed to the `SearchRanker`.

### Sparse Metadata Filtering (`MetadataFilter`)

The sparse component evaluates boolean constraints against document metadata using the `MetadataFilter` class. This produces a second ranked list based on metadata matches (such as `eq("category", "science")` or `gt("year", 2020)`), allowing hybrid queries that combine semantic similarity with structured filters.

## The SearchRanker Pipeline

When a `HybridSearch` object initializes, it instantiates a `SearchRanker` with a configurable strategy. By default, the constructor selects `"reciprocal_rank_fusion"`:

```python

# From HybridSearch.__init__ (lines 71-73)

self.ranker = SearchRanker(
    config.get("ranking_strategy", "reciprocal_rank_fusion")
)

```

The fusion logic resides in `SearchRanker.reciprocal_rank_fusion` (lines 48-86). The method accepts multiple result lists and the constant `k`, then iterates through every list to calculate reciprocal ranks, aggregates scores per document ID, sorts by descending score, and reconstructs the result objects with fused scores appended.

## Multi-Source Search Execution

The `HybridSearch.multi_source_search` method (lines 63-66) demonstrates the complete workflow. It gathers results from each configured source and delegates ranking to the `SearchRanker`:

```python
def multi_source_search(self, query_vector, sources, k=10, **options):
    all_results = []
    for source in sources:
        # Retrieve from each source (dense vectors or metadata filters)

        results = self._retrieve_from_source(query_vector, source)
        all_results.append(results)
    
    # Route to RRF implementation (lines 63-66)

    return self.ranker.rank(all_results, k=k, **options)

```

When the strategy is `"reciprocal_rank_fusion"`, the `rank` method invokes `reciprocal_rank_fusion` internally, passing the consolidated result lists and the requested `k` parameter.

## Practical Implementation Example

The following example demonstrates a complete hybrid search pipeline combining dense vectors with metadata filtering:

```python
from semantica.vector_store import HybridSearch, MetadataFilter

# Initialize hybrid search with default RRF strategy

search = HybridSearch()

# Define sparse filter: category must be "science"

metadata_filter = MetadataFilter().eq("category", "science")

# Execute search combining dense vectors and sparse metadata

results = search.search(
    query_vector=query_embedding,
    vectors=document_embeddings,
    metadata=document_metadata,
    vector_ids=doc_ids,
    k=10,
    metadata_filter=metadata_filter
)

# Results ordered by RRF score (default k=60)

for result in results:
    print(f"{result['id']}: {result['score']}")

```

To customize the RRF constant, pass it during the ranking call:

```python

# Override default k=60 with k=40

fused_results = search.ranker.rank(results_lists, k=40)

```

## Summary

- **Reciprocal Rank Fusion** combines dense vector similarity and sparse metadata results without normalizing incompatible score scales.
- The `SearchRanker` class in [`semantica/vector_store/hybrid_search.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/vector_store/hybrid_search.py) implements RRF via the `reciprocal_rank_fusion` method using the formula `sum(1/(k + rank))`.
- **Default constant** `k` is **60**, configurable at ranking time via the `k` parameter.
- `HybridSearch` automatically instantiates `SearchRanker` with the `"reciprocal_rank_fusion"` strategy during initialization (lines 71-73).
- The `multi_source_search` method aggregates results from disparate sources before delegating to the ranker (lines 63-66).

## Frequently Asked Questions

### What is the default k value in Semantica's RRF implementation?

The default value is **60**. This constant determines the ranking penalty applied to lower-ranked documents and can be overridden by passing the `k` parameter to the `rank` method when executing searches.

### How does HybridSearch handle documents that appear in only one result list?

Documents appearing in only the dense or sparse list still receive an RRF score based on their rank in that single list. Since RRF sums contributions across all lists where a document appears, single-list documents rank lower than documents present in both lists, but remain in the final results ordered by their computed score.

### Can I use a different ranking strategy besides RRF in HybridSearch?

Yes. While the default strategy is `"reciprocal_rank_fusion"`, the `SearchRanker` constructor accepts alternative strategies via the `ranking_strategy` configuration parameter. The system routes to the appropriate ranking method based on this string identifier.

### Where is the RRF scoring logic located in the source code?

The core algorithm resides in [`semantica/vector_store/hybrid_search.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/vector_store/hybrid_search.py), specifically within the `SearchRanker.reciprocal_rank_fusion` method (lines 48-86). The integration point that invokes this method during multi-source searches is located at lines 63-66 in the same file.