> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mixpeek.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Fusion Strategies

> How multiple search results are combined into a single ranked list using RRF, DBSF, Weighted, Max, or Learned fusion

When a [feature search](https://docs.mixpeek.com/docs/retrieval/stages/feature-search.md) stage queries multiple embedding indexes (e.g., text + image), it produces separate ranked lists that need to be merged. Fusion strategies determine how those lists become one.

![Comparison of RRF, Weighted, and Learned fusion strategies](https://mintcdn.com/mixpeek/TwtTrae3Fi3EFJ72/assets/relevance/fusion-strategies-comparison.svg?fit=max\&auto=format\&n=TwtTrae3Fi3EFJ72\&q=85\&s=0d47c4b60054bfb57ef4dc48f7ea7169)

## Strategy Reference

| Strategy | Formula | Configuration | Best For |
| - | - | - | - |
| `rrf` | `w / (k + rank)` | Optional `weight` per search (k=60) | General purpose, no tuning needed |
| `dbsf` | `w₁·norm₁ + w₂·norm₂` | Optional `weight` per search | Different score distributions, or one search should lead |
| `weighted` | `w₁·score₁ + w₂·score₂` | `weight` per search | Known feature importance |
| `max` | `max(score₁, score₂)` | None | Any single match is sufficient |
| `learned` | Thompson Sampling | Interaction data | Personalized, adaptive weights |

## Reciprocal Rank Fusion (RRF)

The default strategy. RRF ignores raw similarity scores and uses only rank position. This makes it robust when different features produce scores on different scales. Each search's `weight` (default 1.0) multiplies its rank contribution. With k=60 a weight shifts the ranking only slightly; use DBSF when one search should lead.

**Formula:**

```
score(doc) = Σ  1 / (k + rank_i(doc))
```

Where `k = 60` (constant that prevents top-ranked items from dominating) and `rank_i` is the document's position in the i-th feature's result list.

**Why it works:** A document ranked #1 by text search and #3 by image search gets a higher fused score than a document ranked #2 by both. The rank-based approach means you don't need to calibrate score ranges across features.

> [!WARNING]
>
> **RRF scores are small on purpose — read order, not magnitude.** Because the score
> is `1 / (k + rank)` with `k = 60`, the best possible result scores about
> `1 / (60 + 1) ≈ 0.0164` per search (a little higher when several searches rank the
> same document). A top score of `~0.017` is **expected and healthy** — it is a fused
> rank position, **not** a similarity or a quality percentage, so a `0.0167` top hit
> does not mean the search matched poorly. Rank documents by their **relative** order.
> If you need an absolute similarity value (e.g. `0.0`–`1.0`), use a single search
> without fusion, or a `weighted`/`max` strategy that preserves raw scores.

```json Configuration theme={null}
{
  "stage_name": "feature_search",
  "stage_type": "filter",
  "config": {
    "stage_id": "feature_search",
    "parameters": {
      "searches": [
        {
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        },
        {
          "feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        }
      ],
      "fusion": "rrf",
      "final_top_k": 25
    }
  }
}
```

> [!TIP]
>
> RRF is the best default. Use it unless you have a specific reason to choose another strategy.

## Distribution-Based Score Fusion (DBSF)

DBSF normalizes scores from each feature into a common distribution before combining them. This handles cases where one feature produces scores in \[0.8, 0.99] and another in \[0.1, 0.6].

**How it works:**

1. For each feature, take the minimum and maximum score it returned
2. Normalize each score to \[0, 1]: `normalized = (score - min) / (max - min)`
3. Multiply by the search's `weight` (default 1.0) and sum across features

DBSF is useful when features have different score distributions, but the raw scores themselves carry meaningful information (unlike RRF which ignores scores entirely).

Because each search is normalized on its own, DBSF works across different embedding models. Set `weight` on each search to make one of them lead. For example, a transcript search at `1.0` and a frame search at `0.2` puts the best transcript match first, even though the two models score on different scales. `weighted` fusion is refused across embedding models because it sums raw scores.

```json Configuration theme={null}
{
  "stage_name": "feature_search",
  "stage_type": "filter",
  "config": {
    "stage_id": "feature_search",
    "parameters": {
      "searches": [
        {
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        },
        {
          "feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        }
      ],
      "fusion": "dbsf",
      "final_top_k": 25
    }
  }
}
```

## Weighted Fusion

You manually assign a weight to each search feature. Scores are multiplied by their weight and summed. Use this when you know from domain expertise that one feature is more important than another.

**Formula:**

```
score(doc) = w₁ · score₁(doc) + w₂ · score₂(doc) + ...
```

Weights don't need to sum to 1 — they're relative importance indicators.

```json Configuration theme={null}
{
  "stage_name": "feature_search",
  "stage_type": "filter",
  "config": {
    "stage_id": "feature_search",
    "parameters": {
      "searches": [
        {
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100,
          "weight": 0.7
        },
        {
          "feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding",
          "query": {"input_mode": "content", "value": "{{INPUT.image_url}}"},
          "top_k": 100,
          "weight": 0.3
        }
      ],
      "fusion": "weighted",
      "final_top_k": 25
    }
  }
}
```

> [!WARNING]
>
> Weighted fusion is sensitive to score scale differences. If text scores are in \[0.8, 1.0] and image scores are in \[0.1, 0.5], a 50/50 weight split will still favor text. Consider DBSF or RRF if score ranges differ significantly.

## Max Fusion

Takes the maximum score across all features for each document. A document only needs to be a strong match on **one** feature to rank highly.

**Formula:**

```
score(doc) = max(score₁(doc), score₂(doc), ...)
```

Use this when any single strong match is sufficient — for example, a product that matches either the text description or the visual similarity should rank high.

```json Configuration theme={null}
{
  "stage_name": "feature_search",
  "stage_type": "filter",
  "config": {
    "stage_id": "feature_search",
    "parameters": {
      "searches": [
        {
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        },
        {
          "feature_uri": "mixpeek://image_extractor@v1/google_siglip_base_v1",
          "query": {"input_mode": "content", "value": "{{INPUT.image_url}}"},
          "top_k": 100
        }
      ],
      "fusion": "max",
      "final_top_k": 25
    }
  }
}
```

## Learned Fusion

Learned fusion replaces static weights with weights that adapt automatically from user [interaction](https://docs.mixpeek.com/docs/retrieval/interactions.md) data. Under the hood, it uses Thompson Sampling with Beta distributions to balance exploration (trying different weight combinations) with exploitation (using what's known to work).

See [Learned Fusion](https://docs.mixpeek.com/docs/relevance/learned-fusion.md) for the full deep dive.

```json Configuration theme={null}
{
  "stage_name": "feature_search",
  "stage_type": "filter",
  "config": {
    "stage_id": "feature_search",
    "parameters": {
      "searches": [
        {
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "query": {"input_mode": "text", "value": "{{INPUT.query}}"},
          "top_k": 100
        },
        {
          "feature_uri": "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding",
          "query": {"input_mode": "content", "value": "{{INPUT.image_url}}"},
          "top_k": 100
        }
      ],
      "fusion": "learned",
      "final_top_k": 25
    }
  }
}
```

## Choosing a Strategy

```
Do you have multiple search features?
├── No  → No fusion needed (single feature search)
└── Yes
    ├── Do you have interaction data (100+ signals)?
    │   ├── Yes → Use "learned" (adapts automatically)
    │   └── No
    │       ├── Do you know which features matter more?
    │       │   ├── Yes → Use "weighted"
    │       │   └── No
    │       │       ├── Do features have different score scales?
    │       │       │   ├── Yes → Use "dbsf"
    │       │       │   └── No → Use "rrf" (default)
    │       └── Is any single match sufficient?
    │           └── Yes → Use "max"
```

## Performance Comparison

| Strategy | Latency Overhead | Configuration Effort | Adapts Over Time | Best Quality Ceiling |
| - | :-: | :-: | :-: | :-: |
| RRF | \< 5ms | None | No | Good |
| DBSF | \< 5ms | None | No | Good |
| Weighted | \< 5ms | Manual tuning | No | Good (if well-tuned) |
| Max | \< 5ms | None | No | Moderate |
| Learned | \< 10ms | Interaction tracking | Yes | Best |

All strategies add minimal latency. The real difference is in quality: learned fusion converges toward optimal weights for your specific users and content, while static strategies require manual tuning or accept a one-size-fits-all approach.

## Related

* [Feature Search stage](https://docs.mixpeek.com/docs/retrieval/stages/feature-search.md) — where fusion is configured
* [Learned Fusion deep dive](https://docs.mixpeek.com/docs/relevance/learned-fusion.md) — Thompson Sampling explained
* [Interaction Signals](https://docs.mixpeek.com/docs/retrieval/interactions.md) — capturing the data that powers learned fusion
* [Evaluations](https://docs.mixpeek.com/docs/retrieval/evaluations.md) — measuring the impact of different fusion strategies


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.