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

# Auto-Tune

> Self-improving relevance that adapts per user

Retriever fusion weights automatically adapt based on how each user interacts with search results. Instead of manually tuning `text: 0.7, image: 0.3`, the system learns from clicks, purchases, and feedback to discover the optimal blend for every user — using Thompson Sampling (a multi-armed bandit algorithm) with hierarchical fallback from personal to demographic to global priors.

Auto-Tune closes the gap between "search works" and "search works *for this user*" without building a separate recommendation system.

## How It Works

<Steps>
  <Step title="User searches">
    A query arrives with a `user_id`. The system looks up that user's learned fusion weights — or falls back to segment-level or global weights if the user is new.
  </Step>

  <Step title="Results ranked by fusion weights">
    The feature search stage runs each embedding search and fuses results using the personalized weights. Early on, weights are exploratory (high variance). As data accumulates, they stabilize around what works for this user.
  </Step>

  <Step title="User interacts">
    The user clicks, purchases, skips, or provides feedback. Each interaction is recorded with the document ID, position, and the feature URI that produced the match.
  </Step>

  <Step title="Weights updated">
    Positive interactions (clicks, purchases) increase the weight of the feature that surfaced the result. Negative signals (skips, negative feedback) decrease it. Different interaction types carry different reward magnitudes — a purchase is a stronger signal than a click.
  </Step>

  <Step title="Next search reflects preferences">
    The next query from this user samples from the updated weight distributions. After dozens of interactions, the system converges on near-optimal weights while still occasionally exploring alternatives.
  </Step>
</Steps>

<Frame>
  <img src="https://mintcdn.com/mixpeek/TwtTrae3Fi3EFJ72/assets/relevance/thompson-sampling-cycle.svg?fit=max&auto=format&n=TwtTrae3Fi3EFJ72&q=85&s=ac47429ed459437033adb07d0ec59c02" alt="Thompson Sampling: Beta distributions evolve from uniform to peaked as interactions accumulate" width="1200" height="480" data-path="assets/relevance/thompson-sampling-cycle.svg" />
</Frame>

## Quick Start

### 1. Create a retriever with learned fusion

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.mixpeek.com/v1/retrievers" \
    -H "Authorization: Bearer $MP_API_KEY" \
    -H "X-Namespace: $MP_NAMESPACE" \
    -H "Content-Type: application/json" \
    -d '{
      "retriever_name": "product-search-personalized",
      "collection_identifiers": ["col_products"],
      "input_schema": {
        "query": { "type": "text", "description": "Search query", "required": true },
        "user_id": { "type": "text", "description": "User identifier", "required": true }
      },
      "stages": [
        {
          "stage_name": "Personalized 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": "learned",
              "learning_config": {
                "context_features": ["INPUT.user_id"],
                "reward_map": {
                  "click": 1.0,
                  "purchase": 3.0,
                  "add_to_cart": 2.0,
                  "negative_feedback": -2.0
                },
                "min_interactions": 5,
                "exploration_bonus": 1.0,
                "decay_factor": 0.995
              },
              "final_top_k": 25
            }
          }
        }
      ]
    }'
  ```

  ```python Python SDK theme={null}
  from mixpeek import Mixpeek

  client = Mixpeek(api_key="your-api-key")

  retriever = client.retrievers.create(
      retriever_name="product-search-personalized",
      collection_identifiers=["col_products"],
      input_schema={
          "query": {"type": "text", "description": "Search query", "required": True},
          "user_id": {"type": "text", "description": "User identifier", "required": True},
      },
      stages=[{
          "stage_name": "Personalized 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": "learned",
                  "learning_config": {
                      "context_features": ["INPUT.user_id"],
                      "reward_map": {
                          "click": 1.0,
                          "purchase": 3.0,
                          "add_to_cart": 2.0,
                          "negative_feedback": -2.0,
                      },
                      "min_interactions": 5,
                      "exploration_bonus": 1.0,
                      "decay_factor": 0.995,
                  },
                  "final_top_k": 25,
              },
          },
      }],
  )
  retriever_id = retriever["retriever_id"]
  ```
</CodeGroup>

### 2. Execute with a user ID

```bash theme={null}
curl -X POST "https://api.mixpeek.com/v1/retrievers/$RETRIEVER_ID/execute" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {
      "query": "wireless earbuds noise canceling",
      "user_id": "user_456"
    }
  }'
```

With zero interactions, this behaves like RRF (uniform weights). The response includes an `execution_id` and each result contains a `feature_id` and `feature_uri` — you'll use these in Step 3.

### 3. Post interactions — results automatically improve

When a user clicks, purchases, or otherwise engages with a result, post an interaction using the `feature_id` and `execution_id` from the search response:

```bash theme={null}
curl -X POST "https://api.mixpeek.com/v1/retrievers/interactions" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "retriever_id": "'$RETRIEVER_ID'",
    "feature_id": "doc_product_789",
    "interaction_type": ["click", "purchase"],
    "position": 2,
    "user_id": "user_456",
    "session_id": "sess_abc",
    "execution_id": "exec_from_step_2",
    "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1"
  }'
```

| Field              | Required | Description                                                                                                                                   |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `feature_id`       | Yes      | The `feature_id` from the search result the user interacted with.                                                                             |
| `interaction_type` | Yes      | Array of interaction types for this event (e.g. `["click"]` or `["click", "purchase"]`). Multiple types in one call record a compound action. |
| `position`         | Yes      | 0-indexed position of the result in the ranked list. Recorded for analytics and rank-aware metrics (NDCG).                                    |
| `execution_id`     | No       | The `execution_id` from the search response. Links the interaction to a specific search.                                                      |
| `session_id`       | No       | Client-generated session identifier. Groups interactions within a browsing session.                                                           |
| `feature_uri`      | No       | The `feature_uri` that produced the result. When provided, the system learns which embedding features the user prefers.                       |

**Python SDK shortcut** — `create_interaction_from_result()` extracts all the IDs for you:

```python theme={null}
results = client.retrievers.execute(retriever_id, inputs={"query": "earbuds", "user_id": "user_456"})
client.retrievers.create_interaction_from_result(results, position=2, interaction_type=["purchase"], user_id="user_456")
```

After enough interactions, the same search for `user_456` returns results personalized to their feature preferences. If this user consistently engages with text-matched results over image-matched ones, the text feature weight increases for their queries.

## Key Concepts

<CardGroup cols={2}>
  <Card title="Reward Signals" icon="signal" href="/retrieval/reward-signals">
    Configure how different interaction types (clicks, purchases, feedback) influence learned fusion weights. Customize the reward map, handle negative signals, and tune temporal decay.
  </Card>

  <Card title="Rollout & Safety" icon="shield-check" href="/retrieval/auto-tune-rollout">
    Safely deploy learned fusion with traffic splitting, shadow mode, kill switches, per-user opt-out, and weight bounds. Includes a recommended rollout plan.
  </Card>

  <Card title="Interactions" icon="hand-pointer" href="/retrieval/interactions">
    How to capture clicks, purchases, and feedback that power the learning loop.
  </Card>

  <Card title="Evaluations" icon="chart-line" href="/retrieval/evaluations">
    Measure whether learned fusion actually improves retrieval quality. Compare NDCG, precision, and recall against static fusion baselines.
  </Card>
</CardGroup>

## Configuration Reference

The `learning_config` object is set inside the feature search stage parameters alongside `fusion: "learned"`:

| Field                        | Type       | Default                                         | Description                                                                                                                                                                      |
| ---------------------------- | ---------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context_features`           | `string[]` | `["INPUT.user_id"]`                             | Input fields used for personal-level weight learning. Each value references an `INPUT.*` field from the retriever's `input_schema`.                                              |
| `demographic_features`       | `string[]` | `[]`                                            | Input fields for segment-level fallback (e.g., `"INPUT.user_segment"`). Used when a user has insufficient personal history.                                                      |
| `reward_signal`              | `string`   | `"click"`                                       | *Deprecated.* Single interaction type used as the learning signal. Use `reward_map` instead. If both are provided, `reward_map` takes precedence and `reward_signal` is ignored. |
| `reward_map`                 | `object`   | See [Reward Signals](/retrieval/reward-signals) | Maps interaction types to reward magnitudes. Positive values reinforce; negative values penalize.                                                                                |
| `min_interactions`           | `integer`  | `5`                                             | Minimum interactions before using personal-level weights. Below this threshold, the system falls back to demographic or global weights.                                          |
| `exploration_bonus`          | `float`    | `1.0`                                           | Initial multiplier for distribution variance. Higher values increase exploration (more weight variability).                                                                      |
| `exploration_decay`          | `float`    | `0.99`                                          | Per-interaction decay applied to the exploration bonus. Gradually shifts from exploration to exploitation.                                                                       |
| `exploration_floor`          | `float`    | `0.1`                                           | Minimum exploration bonus. Prevents the system from fully exploiting — there is always some chance of trying alternative weights.                                                |
| `decay_factor`               | `float`    | `0.995`                                         | Per-day exponential decay applied to older interactions. `1.0` disables decay (interactions never fade).                                                                         |
| `decay_window_days`          | `integer`  | `365`                                           | Interactions older than this are ignored entirely.                                                                                                                               |
| `min_weight`                 | `float`    | `0.05`                                          | Minimum weight any feature can receive after sampling. Prevents a feature from being silenced.                                                                                   |
| `max_weight`                 | `float`    | `0.95`                                          | Maximum weight any feature can receive after sampling. Prevents one feature from completely dominating.                                                                          |
| `rollout_pct`                | `float`    | `100.0`                                         | Percentage of requests (0-100) that use learned weights. The rest use static fusion. Uses deterministic bucketing so a user does not flip-flop between treatments.               |
| `shadow_mode`                | `boolean`  | `false`                                         | When `true`, learned weights are computed and logged but static fusion results are served. Use this to evaluate learned fusion before going live.                                |
| `algorithm`                  | `string`   | `"thompson_sampling"`                           | Learning algorithm. Currently only `"thompson_sampling"` is supported.                                                                                                           |
| `prior_alpha`                | `float`    | `1.0`                                           | Alpha parameter for the Beta prior distribution. Higher values bias toward exploitation.                                                                                         |
| `prior_beta`                 | `float`    | `1.0`                                           | Beta parameter for the Beta prior distribution. `alpha=1, beta=1` is a uniform prior.                                                                                            |
| `max_reward_per_interaction` | `float`    | `5.0`                                           | Maximum absolute reward value per interaction. Rewards beyond this are clamped.                                                                                                  |
| `circuit_breaker_timeout_ms` | `integer`  | `1000`                                          | Timeout in milliseconds for learned weight resolution. If exceeded, falls back to static fusion. Range: 1–30000.                                                                 |
| `fallback_strategy`          | `string`   | `"hierarchical"`                                | How to resolve weights when personal data is insufficient. `"hierarchical"` uses the four-level fallback below; `"global"` skips demographic and goes straight to global.        |

<Note>
  **Every `learning_config` field is optional** — each has the default shown above. The only field you almost always set is `context_features` (so the system knows which `INPUT.*` field identifies a user). The minimal config in the [quickstart](#1-create-a-retriever-with-learned-fusion) (`context_features` + `reward_map` + `min_interactions` + `exploration_bonus`) is enough to get started; the rest are tuning knobs.
</Note>

### Complete `learning_config` example

Every documented field, set to its default. Drop this into the feature search stage's `parameters` (alongside `fusion: "learned"`) and adjust only what you need — omitted fields fall back to these defaults:

```json theme={null}
"learning_config": {
  "context_features": ["INPUT.user_id"],
  "demographic_features": ["INPUT.user_segment"],
  "reward_map": {
    "click": 1.0,
    "add_to_cart": 2.0,
    "purchase": 3.0,
    "negative_feedback": -2.0
  },
  "min_interactions": 5,
  "exploration_bonus": 1.0,
  "exploration_decay": 0.99,
  "exploration_floor": 0.1,
  "decay_factor": 0.995,
  "decay_window_days": 365,
  "min_weight": 0.05,
  "max_weight": 0.95,
  "rollout_pct": 100.0,
  "shadow_mode": false,
  "algorithm": "thompson_sampling",
  "prior_alpha": 1.0,
  "prior_beta": 1.0,
  "max_reward_per_interaction": 5.0,
  "circuit_breaker_timeout_ms": 1000,
  "fallback_strategy": "hierarchical"
}
```

<Warning>
  `min_weight` must be strictly less than `max_weight` (the API rejects the retriever otherwise), and `reward_map` keys must be valid [interaction types](/retrieval/interactions#signal-types).
</Warning>

## Hierarchical Fallback

Not every user has enough interaction history for personalized weights. The system uses a four-level fallback:

| Level           | Context         |     Min Interactions     | When Used                                                                |
| --------------- | --------------- | :----------------------: | ------------------------------------------------------------------------ |
| **Personal**    | Individual user | Configurable (default 5) | User has enough interactions for reliable personal weights               |
| **Demographic** | User segment    |             1            | User is new, but their segment (e.g., "enterprise", "consumer") has data |
| **Global**      | All users       |             1            | No segment data available; uses aggregate behavior across all users      |
| **Prior**       | Uniform         |             0            | No interactions at all; falls back to equal weights (equivalent to RRF)  |

The `context_features` field controls personal-level resolution (typically `["INPUT.user_id"]`). The `demographic_features` field enables segment-level fallback (e.g., `["INPUT.plan_tier"]`).

<Tip>
  A new user starts at the Global or Prior level and automatically graduates to Personal as they interact with results — no configuration changes needed. The transition happens transparently at query time.
</Tip>

## Related

* [Interactions](/retrieval/interactions) — capturing the user behavior that powers learning
* [Feature Search stage](/retrieval/stages/feature-search) — where fusion and `learning_config` are configured
* [Fusion Strategies](/relevance/fusion-strategies) — comparison of all 5 fusion strategies
