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

# Interactions

> Turn user behavior into better retrieval through automated feedback loops

Interactions capture how users engage with search results—clicks, purchases, skips, dwell time. This feedback automatically improves your retrievers through fine-tuning, personalization, and analytics.

<Frame>
  <img src="https://mintcdn.com/mixpeek/TwtTrae3Fi3EFJ72/assets/mixpeek-interactions.svg?fit=max&auto=format&n=TwtTrae3Fi3EFJ72&q=85&s=90d32424716608350755e20c6344ffab" alt="Mixpeek Interactions Flow" width="1200" height="650" data-path="assets/mixpeek-interactions.svg" />
</Frame>

## Why Track Interactions

**Automatic Quality Improvement**: User behavior becomes training data for fine-tuning embedding models and ranking algorithms—no manual labeling required.

**Measure Real Performance**: Traditional IR metrics (precision, recall) use test sets. Interactions reveal actual user satisfaction through click-through rates, conversion, and engagement.

**Personalization at Scale**: Build user preference profiles from interaction history to deliver personalized results without managing separate recommendation systems.

**Identify Blind Spots**: Find queries with low engagement, results that users skip despite high rankings, and content gaps causing zero-result queries.

## What You Get

<CardGroup cols={2}>
  <Card title="Training Data Generation" icon="brain">
    Interactions format into contrastive pairs (query, clicked\_doc) for embedding fine-tuning. Position is recorded for future bias correction.
  </Card>

  <Card title="Relevance Analytics" icon="chart-line">
    CTR by position, documents with high skip rates, queries needing tuning, time-to-first-click—all without custom infrastructure.
  </Card>

  <Card title="Result Deduplication" icon="filter">
    Exclude previously purchased, viewed, or consumed content automatically by filtering on past interactions.
  </Card>

  <Card title="Popularity Boosting" icon="arrow-trend-up">
    Rerank results based on recent interaction signals (clicks, conversions) to surface trending content.
  </Card>
</CardGroup>

## Capture Interactions

Record user behavior with a single API call:

```bash theme={null}
POST /v1/retrievers/interactions
{
  "feature_id": "doc_xyz789",        # Document ID from retriever results
  "interaction_type": ["click"],     # One or more signal types
  "position": 3,                      # 0-based position in results (critical for bias correction)
  "metadata": {
    "duration_ms": 4500,              # Optional: dwell time, device, query text
    "query": "wireless earbuds"
  },
  "user_id": "user_123",              # For personalization and session tracking
  "session_id": "sess_456"
}
```

<Note>
  **`interaction_type` is always a JSON array — even for a single signal** (`["click"]`, not `"click"`). The array captures the **co-occurring signals of one user action**, not several separate events: a single result that was shown, clicked, and dwelt on is `["view", "click", "long_view"]` in **one** request. Send a separate request per discrete user action (each click, each purchase) — don't batch unrelated events into one array.
</Note>

### Signal Types

| Type                | Strength          | Use Case                                       |
| ------------------- | ----------------- | ---------------------------------------------- |
| `impression`        | Neutral           | Result was rendered on screen                  |
| `view`              | Weak positive     | User viewed a result                           |
| `click`             | Moderate positive | User clicked result                            |
| `dwell`             | Weak positive     | User lingered on a result                      |
| `long_view`         | Strong positive   | Sustained engagement (track via `duration_ms`) |
| `purchase`          | Strong positive   | Conversion event                               |
| `add_to_cart`       | Positive          | Intent to purchase                             |
| `wishlist`          | Positive          | User added to wishlist                         |
| `positive_feedback` | Explicit positive | Thumbs up vote                                 |
| `negative_feedback` | Explicit negative | Thumbs down vote                               |
| `share`             | Strong positive   | User shared the result                         |
| `bookmark`          | Moderate positive | User saved for later                           |
| `query_refinement`  | Neutral           | User modified their search                     |
| `zero_results`      | Neutral           | Query yielded no results                       |
| `filter_toggle`     | Neutral           | User modified filters                          |
| `skip`              | Weak negative     | User ignored result                            |
| `return_to_results` | Moderate negative | User bounced back quickly                      |

<Tip>
  Combine multiple types per event: `["click", "long_view"]` when a user clicks *and* stays engaged.
</Tip>

## Signal Strategy

Which signals to capture, when, and why. Not all signals carry equal weight.

### Signal Strength Matrix

This table maps each interaction type to how it's used across the platform:

| Signal Type         | Strength          |      Fusion Learning      |    Personalization   |      Analytics      |   Fine-Tuning   |
| ------------------- | ----------------- | :-----------------------: | :------------------: | :-----------------: | :-------------: |
| `click`             | Moderate          |  Primary positive signal  |    Session history   |     CTR metrics     |  Positive pairs |
| `long_view`         | Strong            |    High-weight positive   | Preference indicator |   Engagement depth  | Strong positive |
| `purchase`          | Strongest         |     Conversion signal     |  Purchase exclusion  | Revenue attribution |  Gold standard  |
| `add_to_cart`       | Strong            |       Intent signal       |    Cart-based recs   |    Funnel metrics   |  Near-positive  |
| `positive_feedback` | Explicit          |       Direct reward       |   Profile building   |    Quality score    |   Ground truth  |
| `negative_feedback` | Explicit          |       Direct penalty      |    Anti-preference   |   Issue detection   |  Hard negative  |
| `skip`              | Weak negative     |      Implicit penalty     |           —          |      Skip rate      |  Soft negative  |
| `return_to_results` | Moderate negative |       Bounce signal       |           —          |     Bounce rate     |  Negative pair  |
| `share`             | Strong            |        Social proof       |   Sharing patterns   |       Virality      | Strong positive |
| `bookmark`          | Moderate          |        Save intent        |      Saved items     |      Save rate      |     Positive    |
| `dwell`             | Variable          |     Duration-weighted     |           —          |   Engagement depth  |     Weighted    |
| `query_refinement`  | Neutral (0.0)     | Not in default reward map |           —          |    Query analysis   |        —        |

### Use Case Patterns

<Tabs>
  <Tab title="E-commerce">
    **Primary signals:** `purchase`, `add_to_cart`, `click` — conversion is the strongest relevance indicator.

    **Key patterns:**

    * Track `add_to_cart` separately from `purchase` to measure funnel drop-off
    * Use `position` for learning-to-rank bias correction
    * Store `order_value` in metadata for revenue-weighted metrics
  </Tab>

  <Tab title="Media & Entertainment">
    **Primary signals:** `long_view`, `click`, `share` — engagement depth matters more than the click.

    **Key patterns:**

    * Combine `click` and `long_view` in one event when engagement is sustained
    * Use `duration_ms` and `completion_pct` to distinguish genuine views from bounces
    * Track `share` as a strong positive signal for content quality
  </Tab>

  <Tab title="Enterprise Search">
    **Primary signals:** `click`, `positive_feedback`, `bookmark` — clicks alone may indicate obligation, not satisfaction.

    **Key patterns:**

    * Add thumbs-up/down UI to capture explicit feedback
    * Store `department` or `role` in metadata for demographic-level learned fusion
    * Track `query_refinement` to find queries where results fall short
  </Tab>

  <Tab title="Recommendations">
    **Primary signals:** `click`, `purchase`, `negative_feedback` — you need negative signals to avoid recommending disliked content.

    **Key patterns:**

    * Capture "not interested" / "hide" as `negative_feedback` to suppress similar content
    * Use purchase history to filter already-bought items from results
    * Track position carefully — recommendation position bias is stronger than search
  </Tab>
</Tabs>

### Position Tracking

<Warning>
  **Always capture `position`.** Although position-based reward weighting is not yet implemented, `position` is stored for analytics and will be used for future position-aware modeling. Recording it now avoids a costly backfill later.
</Warning>

Position bias is a well-known problem — users tend to click higher-ranked results regardless of relevance. The system records `position` on every interaction for analytics and audit purposes. However, position-based reward weighting is **not yet implemented** — a click at position 8 currently receives the same reward as a click at position 0. Record position now so you won't need to backfill when position-aware modeling is added.

### Cold Start

When you first deploy you have zero interactions. The system adapts automatically through the [hierarchical fallback](/relevance/learned-fusion#hierarchical-fallback) in Thompson Sampling (personal → demographic → global → uniform prior):

| Interactions | Fusion Behavior                          | What To Do                                 |
| ------------ | ---------------------------------------- | ------------------------------------------ |
| 0            | Falls back to `rrf` (uniform weights)    | Ship with `rrf`, start collecting signals  |
| 1–50         | Global-level learned weights only        | Monitor analytics for obvious issues       |
| 50–500       | Demographic-level personalization begins | Verify evaluation metrics are trending up  |
| 500+         | Per-user personalization kicks in        | Run benchmarks to compare against baseline |

### Client-Side Capture

Track clicks and dwell time directly from the browser, using `sendBeacon` so dwell events survive page unload:

```javascript theme={null}
// Track clicks with position
document.querySelectorAll('.search-result').forEach((el, index) => {
  el.addEventListener('click', () => {
    fetch('/api/interactions', {
      method: 'POST',
      body: JSON.stringify({
        feature_id: el.dataset.documentId,
        interaction_type: ['click'],
        position: index,
        metadata: { query: currentQuery },
        user_id: userId, session_id: sessionId
      })
    });
  });
});

// Track dwell time on result detail pages
let startTime = Date.now();
window.addEventListener('beforeunload', () => {
  const dwellMs = Date.now() - startTime;
  navigator.sendBeacon('/api/interactions', JSON.stringify({
    feature_id: currentDocId,
    interaction_type: dwellMs > 5000 ? ['click', 'long_view'] : ['click'],
    position: resultPosition,
    metadata: { duration_ms: dwellMs, query: originalQuery },
    user_id: userId
  }));
});
```

## Outcomes & Use Cases

### 1. Fine-Tune Embedding Models

```python theme={null}
# Collect 30 days of interaction data as training pairs
training_data = analytics.get_query_document_pairs(
    date_range="last_30_days",
    min_interactions=2  # Only docs with real engagement
)

# System formats as contrastive pairs:
# Positive: (query, clicked_doc)
# Hard negative: (query, high_ranked_but_skipped_doc)
# Position bias automatically corrected

fine_tuned_model = training.fine_tune(
    base_model="text-embedding-3-small",
    training_data=training_data
)
```

**Outcome**: 10-30% improvement in relevance metrics from domain-specific fine-tuning using real user preferences.

### 2. Identify & Fix Ranking Issues

```python theme={null}
# Find queries where top results are skipped
signals = analytics.get_retriever_signals(retriever_id="ret_123")
# Returns:
# - Documents ranked high but skipped (ranking issues)
# - Queries with &lt;5% CTR (need tuning)
# - Average position of first click (relevance proxy)
```

**Outcome**: Data-driven decisions on which retrievers need adjustment, which taxonomy mappings to update, or which filters to refine.

### 3. Personalize Without Recommendation Infrastructure

```python theme={null}
# Exclude content user already consumed
previous_purchases = interactions.list(
    user_id="user_123",
    interaction_type=["purchase"],
    days=90
)

retriever.execute(
    query="new arrivals",
    filters={
        "document_id": {"$nin": [i.feature_id for i in previous_purchases]}
    }
)
```

**Outcome**: Improved user experience (no duplicate suggestions) without building separate collaborative filtering or recommendation systems.

### 4. Continuously Tune Ranking from Interactions

Set `fusion: "learned"` and click/purchase signals automatically re-weight your search over time — no manual reranking rules. Weight higher-intent signals above clicks with the `reward_map`:

```json theme={null}
{
  "stages": [
    {
      "stage_name": "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": {
            "reward_map": { "click": 1.0, "add_to_cart": 2.0, "purchase": 3.0 },
            "decay_window_days": 30
          },
          "final_top_k": 25
        }
      }
    }
  ]
}
```

**Outcome**: click/purchase signals continuously re-tune feature weights via [auto-tune](/retrieval/auto-tune) — higher-intent interactions carry more weight — with no manual reranking rules. See [Reward Signals](/retrieval/reward-signals) for the full `reward_map`.

## Query & Export

Retrieve interactions for analysis or external ML pipelines:

```bash theme={null}
# Get all interactions for a document
POST /v1/retrievers/interactions/list
{
  "feature_id": "doc_xyz789"
}

# Get user interaction history
POST /v1/retrievers/interactions/list
{
  "user_id": "user_123",
  "limit": 50
}

# Export for training (bulk pagination)
POST /v1/retrievers/interactions/list
{
  "page": 1,
  "page_size": 1000
}
```

## Reward Values and Auto-Tune

When a retriever uses [Auto-Tune](/retrieval/auto-tune) (`fusion: "learned"`), every interaction automatically receives a computed `reward_value` stored in its metadata. This value is derived from the retriever's `reward_map` configuration at write time:

```json theme={null}
{
  "interaction_type": ["click", "purchase"],
  "metadata": {
    "reward_value": 3.0,
    "query": "wireless earbuds"
  }
}
```

In this example, the reward is determined by the interaction type with the largest absolute value: `purchase (3.0)` wins over `click (1.0)`, so `reward_value` is `3.0` (not summed). The reward value determines how much this interaction shifts the fusion weights for the associated feature.

**How it feeds into Auto-Tune:**

1. Each interaction records which feature URI surfaced the clicked result
2. The `reward_value` adjusts the Thompson Sampling Beta distribution for that feature
3. Positive rewards increase the feature's weight; negative rewards decrease it
4. The next search for this user reflects the updated weights

See [Reward Signals](/retrieval/reward-signals) for the full default reward map, customization options, and examples for different use cases (e-commerce, content, internal search).

### Feature URI

The `feature_uri` field identifies which embedding model/feature produced the result the user interacted with. This is **required for Auto-Tune** — without it, the interaction cannot contribute to weight learning because the system doesn't know which feature to reward.

```json theme={null}
{
  "feature_id": "doc_product_789",
  "interaction_type": ["click"],
  "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
  "user_id": "user_456"
}
```

You can find the `feature_uri` for each result in the execution response's `learned_fusion_context.feature_uris` array, or extract it from the document's metadata. If `feature_uri` is set as a top-level field, it is automatically promoted into `metadata.feature_uri` for backward compatibility.

<Tip>
  The `session_id` field enables within-session adaptation. When provided, clicks in the current session influence the next search immediately via a session cache — without waiting for the ClickHouse write-then-read cycle.
</Tip>

## Privacy & Compliance

**GDPR Deletion**: Remove all interactions for a user to honor right-to-deletion requests.

```bash theme={null}
DELETE /v1/retrievers/interactions/<interaction_id>
```

**Anonymization**: Hash `user_id` before sending if you need consistent tracking without PII.

## Python SDK

<CodeGroup>
  ```python Create Interaction theme={null}
  from mixpeek import Mixpeek

  client = Mixpeek(api_key="sk_...")

  # After executing a retriever search
  results = client.retrievers.execute(
      "ret_abc123",
      inputs={
          "query": "wireless headphones",
          "user_id": "user_456",
          "session_id": "sess_789",
      },
  )

  # Record a click on the 3rd result
  client.retrievers.create_interaction(
      feature_id=results["documents"][2]["feature_id"],
      interaction_type=["click"],
      position=2,
      user_id="user_456",
      session_id="sess_789",
      retriever_id="ret_abc123"
  )
  ```

  ```python Manage Interactions theme={null}
  # List interactions for a retriever
  interactions = client.retrievers.list_interactions("ret_abc123", page=1, page_size=50)

  # Get a specific interaction
  interaction = client.retrievers.get_interaction("int_abc123")

  # Delete an interaction (GDPR right-to-deletion)
  client.retrievers.delete_interaction("int_abc123")
  ```
</CodeGroup>

## Bulk Backfill

Migrate existing interaction history into Mixpeek using the batch endpoint. Send up to 1,000 interactions per call with `occurred_at` timestamps so temporal decay weights them by their true age.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "$MP_API_URL/v1/retrievers/interactions/batch" \
    -H "Authorization: Bearer $MP_API_KEY" \
    -H "X-Namespace: $MP_NAMESPACE" \
    -H "Content-Type: application/json" \
    -d '{
      "interactions": [
        {
          "feature_id": "doc_001",
          "interaction_type": ["click"],
          "position": 0,
          "user_id": "user_123",
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "occurred_at": "2026-01-15T10:30:00Z"
        },
        {
          "feature_id": "doc_002",
          "interaction_type": ["purchase"],
          "position": 2,
          "user_id": "user_123",
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "occurred_at": "2026-01-16T14:00:00Z"
        }
      ]
    }'
  ```

  ```python Python SDK theme={null}
  client.retrievers.backfill_interactions([
      {
          "feature_id": "doc_001",
          "interaction_type": ["click"],
          "position": 0,
          "user_id": "user_123",
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "occurred_at": "2026-01-15T10:30:00Z",
      },
      {
          "feature_id": "doc_002",
          "interaction_type": ["purchase"],
          "position": 2,
          "user_id": "user_123",
          "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
          "occurred_at": "2026-01-16T14:00:00Z",
      },
  ])
  ```
</CodeGroup>

The response includes per-item results: `{created: 2, failed: 0, results: [{index: 0, interaction_id: "...", status: "created"}, ...]}`. Backfilled interactions bypass the real-time session cache — they affect learned fusion weights only after the next ClickHouse aggregation.

## Best Practices

1. **Always capture position** -- recorded for analytics and future position-aware modeling
2. **Store query text in metadata** -- enables query-document pair export for fine-tuning
3. **Track dwell time** -- separates genuine engagement (`long_view`) from accidental clicks
4. **Backfill with timestamps** -- use the [batch endpoint](#bulk-backfill) with `occurred_at` so temporal decay weights by true age
5. **Monitor weekly** -- set up dashboards to track CTR trends and catch relevance regressions early

<Info>
  See [Improve Relevance → Analytics](/platform/improve-relevance#analytics) for dashboards and alerting on interaction-driven metrics like CTR, engagement rates, and query quality.
</Info>
