Skip to main content
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. 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.

How to Enrich

There are three functions, each scoped to a different level of your trace.
Set attributes once and apply them to all traces in the session. Ideal for tenant context, app version, or user tier.
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"
})
You can enrich a specific session by ID, which is useful in distributed systems:
tracer.enrich_session(
    session_id="sess_abc123",
    metadata={"completed": True}
)

Global vs Instance Methods

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.
enrich_span({
    "user_id": "user_12345",
    "feature": "chat"
})
enrich_span(
    user_id="user_12345",
    feature="chat"
)
Organize data by type using named parameters:
enrich_span(
    metadata={"user_id": "user_12345", "feature": "chat"},
    metrics={"latency_ms": 150, "score": 0.95},
    user_properties={"plan": "premium"},
    feedback={"rating": 5}
)
Extra keyword arguments are added to metadata automatically:
enrich_span(
    metadata={"user_id": "user_12345"},
    metrics={"score": 0.95},
    feature="chat",      # → metadata.feature
    priority="high"      # → metadata.priority
)

Common Use Cases

Attach user identity and message metadata to every traced call.
@trace
def generate_response(user_id: str, message: str):
    enrich_span(
        user_properties={"user_id": user_id},
        metadata={"message_length": len(message)}
    )
    # LLM call...
Tag traces by feature to compare performance across different parts of your app.
@trace
def summarize_document(document: str, feature: str):
    enrich_span({
        "feature": feature,
        "document_length": len(document)
    })
    # LLM call...
Capture HTTP request context for correlating traces with API traffic.
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...
Log scores and experiment variants alongside your traces.
@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...
Record custom timing and throughput alongside auto-captured latency.
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
Enrich spans with success/failure status and error details for debugging.
@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

Putting It All Together

This example combines session-level and span-level enrichment in a single application.
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")

Viewing Enriched Data

1

Open the Session view

Go to your project in the HoneyHive dashboard and select Log Store.
2

Inspect a session

Click any session to expand it. Your enriched data appears in the Metadata and Attributes panels.
3

Filter and analyze

Use enriched fields to filter events, build custom charts, or set up alerts.

Best Practices

DoDon’t
Use consistent key names across your appInclude sensitive data (passwords, API keys, PII)
Add user/session IDs for debuggingAttach large values (>1KB per field)
Include feature and endpoint identifiersUse random or generated key names
Use descriptive names (user_id not uid)Duplicate data already captured by auto-instrumentation

SDK Reference