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

# Overview

> Add custom attributes to your traces for filtering, debugging, and evaluation

Auto-instrumentation captures what your agent *did* - which models it called, how long each step took, how many tokens it used. Enrichments capture *why* it matters - which user triggered the run, what feature they were using, whether the result was any good, and what config produced it.

## Types of Enrichments

HoneyHive organizes custom attributes into typed namespaces. Each has a dedicated guide.

<CardGroup cols={2}>
  <Card title="User Properties" icon="user" href="/v2/tracing/setting-user-properties">
    Attach user IDs, account tiers, and other user context for per-user filtering and analysis.
  </Card>

  <Card title="User Feedback" icon="comment" href="/v2/tracing/setting-user-feedback">
    Capture thumbs-up/down ratings, comments, and implicit signals directly on traces.
  </Card>

  <Card title="Custom Metrics" icon="chart-line" href="/v2/tracing/client-side-evals">
    Log evaluation scores, guardrail results, and other numeric measurements computed in your app.
  </Card>

  <Card title="Config" icon="gear" href="/v2/tracing/configuration-details">
    Log prompt versions, model parameters, and other configuration context on traces.
  </Card>

  <Card title="Online Experiments" icon="flask" href="/v2/tracing/online-experimentation">
    Tag traces with experiment IDs and variant names to analyze A/B tests in HoneyHive.
  </Card>
</CardGroup>

You can also attach arbitrary key-value pairs as **metadata** (e.g. feature flags, request IDs, environment tags). Any key that doesn't belong to a typed namespace lands in `metadata` by default.

For the full list of namespaces and data types, see the [Schema Reference](/v2/tracing/enrichment-schema).

## How to Enrich

There are three functions, each scoped to a different level of your trace.

<Tabs>
  <Tab title="enrich_session()">
    Set attributes once and apply them to all traces in the session. Ideal for tenant context, app version, or user tier.

    ```python theme={null}
    import os
    from honeyhive import HoneyHiveTracer

    tracer = HoneyHiveTracer.init(
        api_key=os.getenv("HH_API_KEY"),
        project=os.getenv("HH_PROJECT")
    )

    tracer.enrich_session({
        "tenant_id": "acme_corp",
        "user_tier": "premium",
        "app_version": "2.1.0"
    })
    ```

    <Tip>
      You can enrich a specific session by ID, which is useful in distributed systems:

      ```python theme={null}
      tracer.enrich_session(
          session_id="sess_abc123",
          metadata={"completed": True}
      )
      ```
    </Tip>
  </Tab>

  <Tab title="enrich_span()">
    Add attributes to the current active span. Use inside any `@trace`-decorated function for per-call context.

    ```python theme={null}
    import os
    from honeyhive import HoneyHiveTracer, enrich_span, trace

    HoneyHiveTracer.init(
        api_key=os.getenv("HH_API_KEY"),
        project=os.getenv("HH_PROJECT")
    )

    @trace
    def process_query(query: str, user_id: str):
        enrich_span({
            "user_id": user_id,
            "query_length": len(query),
            "feature": "search"
        })
        # Your logic here...
    ```
  </Tab>

  <Tab title="using_attributes">
    Enrich auto-instrumented LLM spans (like OpenAI `ChatCompletion`) without wrapping them in `@trace`. Use the `using_attributes` context manager from OpenInference.

    ```python theme={null}
    import os
    from honeyhive import HoneyHiveTracer
    from openinference.instrumentation import using_attributes
    from openinference.instrumentation.openai import OpenAIInstrumentor
    import openai

    tracer = HoneyHiveTracer.init(
        api_key=os.getenv("HH_API_KEY"),
        project=os.getenv("HH_PROJECT")
    )
    OpenAIInstrumentor().instrument(tracer_provider=tracer.provider)

    client = openai.OpenAI()

    with using_attributes(
        user_id="user_12345",
        metadata={"feature": "chat_support"}
    ):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": "Hello!"}]
        )
    ```

    <Note>
      `using_attributes` attaches metadata directly to auto-instrumented spans. For your own functions, use `@trace` with `enrich_span()`.
    </Note>
  </Tab>
</Tabs>

### Global vs Instance Methods

```python theme={null}
from honeyhive import HoneyHiveTracer, enrich_span

# Global function — works within any @trace context
enrich_span({"user_id": "user_123"})

# Instance method — recommended for production
tracer = HoneyHiveTracer.init(...)
tracer.enrich_span({"user_id": "user_123"})
tracer.enrich_session({"tenant_id": "acme"})
```

Use instance methods in production for an explicit tracer reference.

## Invocation Patterns

Both `enrich_span()` and `enrich_session()` accept data in several forms.

<AccordionGroup>
  <Accordion title="Simple dictionary">
    ```python theme={null}
    enrich_span({
        "user_id": "user_12345",
        "feature": "chat"
    })
    ```
  </Accordion>

  <Accordion title="Keyword arguments">
    ```python theme={null}
    enrich_span(
        user_id="user_12345",
        feature="chat"
    )
    ```
  </Accordion>

  <Accordion title="Explicit namespaces">
    Organize data by type using named parameters:

    ```python theme={null}
    enrich_span(
        metadata={"user_id": "user_12345", "feature": "chat"},
        metrics={"latency_ms": 150, "score": 0.95},
        user_properties={"plan": "premium"},
        feedback={"rating": 5}
    )
    ```
  </Accordion>

  <Accordion title="Mixed (namespaces + keywords)">
    Extra keyword arguments are added to `metadata` automatically:

    ```python theme={null}
    enrich_span(
        metadata={"user_id": "user_12345"},
        metrics={"score": 0.95},
        feature="chat",      # → metadata.feature
        priority="high"      # → metadata.priority
    )
    ```
  </Accordion>
</AccordionGroup>

## Common Use Cases

<AccordionGroup>
  <Accordion title="User context" icon="user">
    Attach user identity and message metadata to every traced call.

    ```python theme={null}
    @trace
    def generate_response(user_id: str, message: str):
        enrich_span(
            user_properties={"user_id": user_id},
            metadata={"message_length": len(message)}
        )
        # LLM call...
    ```
  </Accordion>

  <Accordion title="Feature tracking" icon="tags">
    Tag traces by feature to compare performance across different parts of your app.

    ```python theme={null}
    @trace
    def summarize_document(document: str, feature: str):
        enrich_span({
            "feature": feature,
            "document_length": len(document)
        })
        # LLM call...
    ```
  </Accordion>

  <Accordion title="Request metadata (Flask)" icon="server">
    Capture HTTP request context for correlating traces with API traffic.

    ```python theme={null}
    from flask import request

    @app.route("/api/chat", methods=["POST"])
    @trace
    def chat_endpoint():
        enrich_span({
            "request_id": request.headers.get("X-Request-ID"),
            "endpoint": "/api/chat"
        })
        # Process request...
    ```
  </Accordion>

  <Accordion title="Business metrics" icon="chart-line">
    Log scores and experiment variants alongside your traces.

    ```python theme={null}
    @trace
    def generate_recommendation(product_id: str, user_id: str):
        enrich_span(
            user_properties={"user_id": user_id},
            metadata={"product_id": product_id, "ab_variant": "B"},
            metrics={"recommendation_score": 0.92}
        )
        # LLM call...
    ```
  </Accordion>

  <Accordion title="Performance monitoring" icon="gauge-high">
    Record custom timing and throughput alongside auto-captured latency.

    ```python theme={null}
    import time

    @trace
    def expensive_operation(data_size: int):
        start_time = time.perf_counter()

        result = process_data(data_size)

        duration_ms = (time.perf_counter() - start_time) * 1000
        enrich_span(metrics={
            "duration_ms": duration_ms,
            "data_size": data_size,
            "throughput": data_size / (duration_ms / 1000)
        })
        return result
    ```
  </Accordion>

  <Accordion title="Error context" icon="triangle-exclamation">
    Enrich spans with success/failure status and error details for debugging.

    ```python theme={null}
    @trace
    def risky_operation(operation_id: str, data: dict):
        enrich_span(metadata={"operation_id": operation_id})

        try:
            result = process(data)
            enrich_span(metadata={"success": True})
            return result
        except Exception as e:
            enrich_span(
                metadata={"success": False},
                error=str(e)
            )
            raise
    ```
  </Accordion>
</AccordionGroup>

## Putting It All Together

This example combines session-level and span-level enrichment in a single application.

<Accordion title="Full example: session + span enrichment" icon="code">
  ```python theme={null}
  import os
  from honeyhive import HoneyHiveTracer, enrich_span, trace
  from openinference.instrumentation.openai import OpenAIInstrumentor
  import openai

  tracer = HoneyHiveTracer.init(
      api_key=os.getenv("HH_API_KEY"),
      project=os.getenv("HH_PROJECT")
  )
  OpenAIInstrumentor().instrument(tracer_provider=tracer.provider)

  # Session-level context (set once)
  tracer.enrich_session({
      "tenant_id": "acme_corp",
      "user_tier": "premium",
      "app_version": "2.1.0"
  })

  client = openai.OpenAI()

  # Span-level context (per call)
  @trace
  def chat(message: str, user_id: str):
      enrich_span({
          "user_id": user_id,
          "message_length": len(message),
          "feature": "chat_support"
      })

      response = client.chat.completions.create(
          model="gpt-4o-mini",
          messages=[{"role": "user", "content": message}]
      )
      return response.choices[0].message.content

  # Each call inherits session context + gets its own span context
  chat("Hello!", user_id="user_123")
  chat("What's the weather?", user_id="user_456")
  ```
</Accordion>

## Viewing Enriched Data

<Steps>
  <Step title="Open the Session view">
    Go to your project in the HoneyHive dashboard and select **Traces**.
  </Step>

  <Step title="Inspect a session">
    Click any session to expand it. Your enriched data appears in the **Metadata** and **Attributes** panels.
  </Step>

  <Step title="Filter and analyze">
    Use enriched fields to filter events, build [custom charts](/v2/monitoring/charts), or set up [alerts](/v2/monitoring/alerts/alerts_overview).
  </Step>
</Steps>

## Best Practices

| Do                                          | Don't                                                   |
| ------------------------------------------- | ------------------------------------------------------- |
| Use consistent key names across your app    | Include sensitive data (passwords, API keys, PII)       |
| Add user/session IDs for debugging          | Attach large values (>1KB per field)                    |
| Include feature and endpoint identifiers    | Use random or generated key names                       |
| Use descriptive names (`user_id` not `uid`) | Duplicate data already captured by auto-instrumentation |

## SDK Reference

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="https://honeyhiveai.github.io/python-sdk/">
    `enrich_span()` and `enrich_session()`
  </Card>
</CardGroup>
