How HybridSearch Implements RRF Fusion for Dense and Sparse Retrieval

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, 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.

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":


# 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:

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:

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:


# 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 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →