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

# Migrate from Langfuse

> Step-by-step guide to migrate your tracing, evaluations, and historical data from Langfuse to HoneyHive

HoneyHive replaces Langfuse with a purpose-built observability platform for production AI. This guide covers both **live instrumentation migration** (updating your code) and the **data field mapping** for migrating historical Langfuse traces.

## Why teams switch to HoneyHive

<CardGroup cols={2}>
  <Card title="Built for agentic AI" icon="robot">
    Multi-agent workflows, tool orchestration, and reasoning traces - HoneyHive handles the complexity that Langfuse was not designed for. Get per-span latency, per-agent costs, and full reasoning paths out of the box.
  </Card>

  <Card title="Zero-dependency SDK (BYOI)" icon="shield-check">
    Bring Your Own Instrumentor. Use any version of `openai`, `anthropic`, or `langchain` with no SDK conflicts. HoneyHive never patches your clients.
  </Card>

  <Card title="Enterprise-grade security" icon="lock">
    SOC 2, SSO, and RBAC built in. Dedicated cloud and self-hosted deployment options for regulated industries.
  </Card>

  <Card title="Production monitoring" icon="chart-line">
    Real-time alerts, anomaly detection, and custom dashboards purpose-built for AI workloads, not retrofitted from generic APM.
  </Card>
</CardGroup>

<Accordion title="Feature comparison at a glance">
  | Capability              | Langfuse                      | HoneyHive                                                     |
  | ----------------------- | ----------------------------- | ------------------------------------------------------------- |
  | Agentic trace depth     | Basic trace trees             | Per-span latency, per-agent cost, reasoning paths             |
  | SDK dependency model    | Bundled deps, client patching | Zero dependencies (BYOI)                                      |
  | Security and compliance | Community-focused             | SOC 2, SSO, RBAC                                              |
  | Production monitoring   | Basic dashboards              | Real-time alerts, anomaly detection                           |
  | Evaluations             | Client-side scoring only      | Server-side evaluators (auto-run on all traces) + client-side |
  | Auto-flush              | Manual `flush()` required     | Automatic                                                     |
</Accordion>

***

## Migration overview

<Steps>
  <Step title="Install HoneyHive SDK">
    Add HoneyHive alongside Langfuse with no conflicts.
  </Step>

  <Step title="Configure API keys">
    Set up your HoneyHive project and credentials.
  </Step>

  <Step title="Update tracing code">
    Replace Langfuse decorators and callbacks with HoneyHive equivalents.
  </Step>

  <Step title="Migrate evaluations">
    Move scoring logic to HoneyHive evaluators.
  </Step>

  <Step title="Validate and remove Langfuse">
    Confirm traces appear correctly, then uninstall Langfuse.
  </Step>
</Steps>

***

## Step 1: Install HoneyHive SDK

HoneyHive installs cleanly alongside your existing stack:

```bash theme={null}
# Your existing Langfuse setup
pip install langfuse openai==1.40.0

# Add HoneyHive (no conflicts)
pip install honeyhive

# Verify no dependency conflicts
pip check
```

<Check>
  Run `pip list | grep -E "honeyhive|langfuse|openai"` to verify all packages coexist.
</Check>

***

## Step 2: Configure API keys

### Get your HoneyHive API key

1. Log in to [HoneyHive](https://app.us.honeyhive.ai)
2. Go to **Settings** > **API Keys**
3. Click **Create New Key** and copy it

### Set environment variables

```bash theme={null}
# HoneyHive
export HH_API_KEY="your-honeyhive-api-key"
export HH_PROJECT="your-project-name"

# Keep Langfuse active during migration (optional)
export LANGFUSE_SECRET_KEY="your-langfuse-secret"
export LANGFUSE_PUBLIC_KEY="your-langfuse-public"
```

***

## Step 3: Update tracing code

### Basic decorator tracing

<CodeGroup>
  ```python Langfuse (before) theme={null}
  from langfuse.decorators import observe, langfuse_context
  from openai import OpenAI

  client = OpenAI()

  @observe()
  def generate_response(prompt: str) -> str:
      response = client.chat.completions.create(
          model="gpt-4",
          messages=[{"role": "user", "content": prompt}]
      )
      return response.choices[0].message.content

  # Must flush manually
  langfuse_context.flush()
  ```

  ```python HoneyHive (after) theme={null}
  from honeyhive import trace, HoneyHiveTracer
  from openai import OpenAI
  import os

  # Initialize once at startup
  HoneyHiveTracer.init(
      api_key=os.environ["HH_API_KEY"],
      project=os.environ["HH_PROJECT"]
  )

  client = OpenAI()

  @trace(event_name="generate_response")
  def generate_response(prompt: str) -> str:
      response = client.chat.completions.create(
          model="gpt-4",
          messages=[{"role": "user", "content": prompt}]
      )
      return response.choices[0].message.content

  # Traces flush automatically
  ```
</CodeGroup>

### Nested spans with metadata

<CodeGroup>
  ```python Langfuse (before) theme={null}
  from langfuse.decorators import observe, langfuse_context

  @observe()
  def process_document(doc: str):
      langfuse_context.update_current_observation(
          metadata={"doc_length": len(doc)}
      )

      with langfuse_context.observe(name="extract_entities") as span:
          entities = extract(doc)
          span.update(output={"entities": entities})

      with langfuse_context.observe(name="summarize") as span:
          summary = summarize(doc, entities)
          span.update(output={"summary": summary})

      return summary
  ```

  ```python HoneyHive (after) theme={null}
  from honeyhive import trace, enrich_span

  @trace(event_name="process_document")
  def process_document(doc: str):
      enrich_span(metadata={"doc_length": len(doc)})

      @trace(event_name="extract_entities")
      def _extract():
          entities = extract(doc)
          enrich_span(outputs={"entities": entities})
          return entities

      @trace(event_name="summarize")
      def _summarize(entities):
          summary = summarize(doc, entities)
          enrich_span(outputs={"summary": summary})
          return summary

      entities = _extract()
      return _summarize(entities)
  ```
</CodeGroup>

### OpenAI integration (BYOI pattern)

Langfuse patches the OpenAI client. HoneyHive uses the **BYOI pattern** instead: you choose the instrumentor, and your OpenAI client stays standard.

<CodeGroup>
  ```python Langfuse (before) theme={null}
  from langfuse import Langfuse
  from langfuse.openai import openai  # Patched client

  langfuse = Langfuse()

  # Must use the patched client
  response = openai.chat.completions.create(
      model="gpt-4",
      messages=[{"role": "user", "content": "Hello!"}],
      name="greeting"  # Langfuse-specific param
  )

  langfuse.flush()
  ```

  ```python HoneyHive (after) theme={null}
  from honeyhive import HoneyHiveTracer
  from openinference.instrumentation.openai import OpenAIInstrumentor
  from openai import OpenAI  # Standard, unpatched client
  import os

  # 1. Initialize HoneyHive
  tracer = HoneyHiveTracer.init(
      api_key=os.environ["HH_API_KEY"],
      project=os.environ["HH_PROJECT"]
  )

  # 2. Attach your chosen instrumentor
  OpenAIInstrumentor().instrument(tracer_provider=tracer.provider)

  # 3. Use the standard OpenAI client
  client = OpenAI()
  response = client.chat.completions.create(
      model="gpt-4",
      messages=[{"role": "user", "content": "Hello!"}]
  )
  # All LLM calls are auto-traced
  ```
</CodeGroup>

<Tip>
  **BYOI means freedom.** Use any OpenAI SDK version, swap instrumentors without changing application code, and never worry about SDK conflicts.
</Tip>

***

## Step 4: Migrate evaluations

Langfuse uses client-side scoring. HoneyHive supports both client-side enrichment **and** server-side evaluators that run automatically on every trace.

<CodeGroup>
  ```python Langfuse (before) theme={null}
  from langfuse import Langfuse

  langfuse = Langfuse()

  langfuse.score(
      trace_id="...",
      name="quality",
      value=0.8
  )
  ```

  ```python HoneyHive (after) theme={null}
  from honeyhive import enrich_span

  # Option 1: Client-side enrichment
  enrich_span(metrics={"quality": 0.8})

  # Option 2: Configure server-side evaluators in the dashboard.
  # They run on all traces automatically, no client code needed.
  ```
</CodeGroup>

<Tip>
  Server-side evaluators run asynchronously on all traces. Configure them once in the HoneyHive dashboard and they apply to every session with no code changes required.
</Tip>

***

## Step 5: Validate and remove Langfuse

### Validate traces

1. Run your application with typical workloads
2. Compare traces side by side:
   * **Langfuse:** your self-hosted or cloud instance
   * **HoneyHive:** [app.honeyhive.ai](https://app.us.honeyhive.ai)
3. Verify that spans, metadata, and timing match

### Complete the switch

```bash theme={null}
# Remove Langfuse env vars
unset LANGFUSE_SECRET_KEY
unset LANGFUSE_PUBLIC_KEY

# Uninstall Langfuse
pip uninstall langfuse
```

```python theme={null}
# Clean up Langfuse imports and calls:
# - from langfuse import ...
# - from langfuse.decorators import observe
# - langfuse_context.flush()
# - langfuse.score(...)
```

<Check>
  Confirm traces and evaluations appear correctly in HoneyHive after removing Langfuse.
</Check>

***

## API mapping quick reference

| Langfuse                                        | HoneyHive                   | Notes                                 |
| ----------------------------------------------- | --------------------------- | ------------------------------------- |
| `@observe()`                                    | `@trace()`                  | Function decorator for creating spans |
| `langfuse_context.observe()`                    | `@trace()` (nested)         | Child spans via nested decorators     |
| `span.update(output=...)`                       | `enrich_span(outputs=...)`  | Set span output                       |
| `langfuse_context.update_current_observation()` | `enrich_span(metadata=...)` | Attach metadata                       |
| `langfuse.score()`                              | `enrich_span(metrics=...)`  | Record metrics and scores             |
| `langfuse.flush()`                              | Not needed                  | HoneyHive auto-flushes                |
| `from langfuse.openai import openai`            | `OpenAI()` + instrumentor   | BYOI pattern, no patching             |

***

## Data migration field reference

When migrating historical Langfuse data to HoneyHive, each Langfuse object type maps to a HoneyHive equivalent. Use this reference alongside your migration script.

### Object type mapping

| Langfuse object | HoneyHive object | API endpoint     |
| --------------- | ---------------- | ---------------- |
| Trace           | Session          | `/session/start` |
| Generation      | Event (model)    | `/events`        |
| Span            | Event (chain)    | `/events`        |
| Event           | Event (tool)     | `/events`        |
| Score           | Session metadata | `/session/start` |

### Event type mapping

| Langfuse type | HoneyHive `event_type` |
| ------------- | ---------------------- |
| Trace         | `session`              |
| GENERATION    | `model`                |
| SPAN          | `chain`                |
| EVENT         | `tool`                 |

***

### Trace to Session

Langfuse traces become HoneyHive sessions.

| Langfuse field | HoneyHive field                 | Transformation      |
| -------------- | ------------------------------- | ------------------- |
| *(auto)*       | `session_id`                    | Generated UUID      |
| `id`           | `metadata.langfuse_trace_id`    | Direct              |
| `id`           | `metadata.langfuse_original_id` | Direct              |
| `name`         | `session_name`                  | Direct              |
| `userId`       | `user_properties.user_id`       | Wrapped             |
| `sessionId`    | `external_id`                   | Direct              |
| `input`        | `inputs`                        | Direct              |
| `output`       | `outputs`                       | Direct              |
| `metadata`     | `metadata`                      | Merged              |
| `tags`         | `metadata.tags`                 | Moved               |
| `version`      | `metadata.version`              | Moved               |
| `release`      | `metadata.release`              | Moved               |
| *(auto)*       | `source`                        | Set to `"langfuse"` |
| *(auto)*       | `event_type`                    | Set to `"session"`  |
| *(auto)*       | `project`                       | From config         |
| *(auto)*       | `metadata.is_session`           | Set to `true`       |

***

### Generation to Event (model)

Langfuse generations become HoneyHive model events. LLM-specific fields like model name, token counts, and cost are preserved.

| Langfuse field             | HoneyHive field                    | Transformation                                |
| -------------------------- | ---------------------------------- | --------------------------------------------- |
| *(auto)*                   | `event_id`                         | Generated UUID                                |
| `traceId`                  | `session_id`                       | Mapped via `trace_id_to_session_id`           |
| `id`                       | `metadata.langfuse_observation_id` | Direct                                        |
| `name`                     | `event_name`                       | Direct (default: `"generation"`)              |
| *(auto)*                   | `event_type`                       | Set to `"model"`                              |
| `model`                    | `config.model`                     | Moved                                         |
| `modelParameters`          | `config.hyperparameters`           | Moved                                         |
| `modelParameters.provider` | `config.provider`                  | Extracted                                     |
| `input`                    | `inputs.messages`                  | String converted to message array             |
| `prompt`                   | `inputs.messages`                  | String converted to message array             |
| `output`                   | `outputs.text`                     | String extraction                             |
| `completion`               | `outputs.text`                     | Direct                                        |
| `promptTokens`             | `usage.prompt_tokens`              | Renamed                                       |
| `completionTokens`         | `usage.completion_tokens`          | Renamed                                       |
| `totalTokens`              | `usage.total_tokens`               | Renamed                                       |
| `calculatedTotalCost`      | `cost`                             | Direct                                        |
| `startTime`                | `start_time`                       | ISO 8601 to Unix ms                           |
| `endTime`                  | `end_time`                         | ISO 8601 to Unix ms                           |
| *(auto)*                   | `latency_ms`                       | Calculated: `end_time - start_time`           |
| *(auto)*                   | `duration`                         | Same as `latency_ms`                          |
| `level`                    | `status`                           | `"ERROR"` becomes `"error"`, else `"success"` |
| `statusMessage`            | `error`                            | When `level = "ERROR"`                        |
| `metadata`                 | `metadata`                         | Merged                                        |
| `traceId`                  | `metadata.langfuse_trace_id`       | Direct                                        |
| *(auto)*                   | `metadata.observation_type`        | Set to `"GENERATION"`                         |
| `level`                    | `metadata.level`                   | Direct                                        |
| *(auto)*                   | `source`                           | Set to `"langfuse"`                           |
| *(auto)*                   | `project`                          | From config                                   |

<Accordion title="Input and output transformations">
  **Input transformation:**

  | Langfuse input type  | HoneyHive output                                          |
  | -------------------- | --------------------------------------------------------- |
  | String               | `{"messages": [{"role": "user", "content": "<string>"}]}` |
  | Array of messages    | `{"messages": <array>}`                                   |
  | Object with messages | Direct passthrough                                        |
  | null or missing      | `{}`                                                      |

  **Output transformation:**

  | Langfuse output type | HoneyHive output       |
  | -------------------- | ---------------------- |
  | String               | `{"text": "<string>"}` |
  | Object               | Direct passthrough     |
  | null or missing      | `{}`                   |
</Accordion>

***

### Span / Event to Event (chain or tool)

Langfuse spans become HoneyHive chain events; Langfuse events become tool events.

| Langfuse field  | HoneyHive field                    | Transformation                                         |
| --------------- | ---------------------------------- | ------------------------------------------------------ |
| *(auto)*        | `event_id`                         | Generated UUID                                         |
| `traceId`       | `session_id`                       | Mapped via `trace_id_to_session_id`                    |
| `id`            | `metadata.langfuse_observation_id` | Direct                                                 |
| `name`          | `event_name`                       | Direct (default: `"span"`)                             |
| `type`          | `event_type`                       | `"SPAN"` becomes `"chain"`, `"EVENT"` becomes `"tool"` |
| `input`         | `inputs`                           | Direct                                                 |
| `output`        | `outputs`                          | Direct                                                 |
| `startTime`     | `start_time`                       | ISO 8601 to Unix ms                                    |
| `endTime`       | `end_time`                         | ISO 8601 to Unix ms                                    |
| `level`         | `status`                           | `"ERROR"` becomes `"error"`, else `"success"`          |
| `statusMessage` | `error`                            | When `level = "ERROR"`                                 |
| `metadata`      | `metadata`                         | Merged                                                 |
| `traceId`       | `metadata.langfuse_trace_id`       | Direct                                                 |
| *(auto)*        | `metadata.observation_type`        | Set to `"SPAN"` or `"EVENT"`                           |
| `level`         | `metadata.level`                   | Direct                                                 |
| *(auto)*        | `source`                           | Set to `"langfuse"`                                    |
| *(auto)*        | `project`                          | From config                                            |

***

### Score to Session metadata

Langfuse scores are stored as session metadata. HoneyHive does not persist top-level `feedback` or `metrics` via `/session/start`, so scores are placed in the `metadata` object.

| Langfuse field  | HoneyHive field                             | Transformation                        |
| --------------- | ------------------------------------------- | ------------------------------------- |
| `traceId`       | *(lookup)*                                  | Used to find `session_id` via mapping |
| `id`            | `metadata.langfuse_scores[].id`             | Direct                                |
| `observationId` | `metadata.langfuse_scores[].observation_id` | Direct                                |
| `name`          | `metadata.langfuse_scores[].name`           | Direct                                |
| `value`         | `metadata.langfuse_scores[].value`          | Direct                                |
| `source`        | `metadata.langfuse_scores[].source`         | Direct                                |
| `comment`       | `metadata.langfuse_scores[].comment`        | Direct                                |
| `traceId`       | `metadata.langfuse_scores[].trace_id`       | Direct                                |

**Derived fields:**

| HoneyHive field                    | Derivation                                  |
| ---------------------------------- | ------------------------------------------- |
| `metadata.langfuse_score_feedback` | Object with `{score_name: {value, source}}` |
| `metadata.langfuse_score_metrics`  | Object with `{score_name: numeric_value}`   |
| `metadata.langfuse_score_count`    | Count of scores                             |
| `metadata.has_feedback`            | `true` if scores exist                      |

***

### Data type transformations

**Timestamps:** ISO 8601 strings are converted to Unix milliseconds.

**Token fields (camelCase to snake\_case):**

| Langfuse           | HoneyHive           |
| ------------------ | ------------------- |
| `promptTokens`     | `prompt_tokens`     |
| `completionTokens` | `completion_tokens` |
| `totalTokens`      | `total_tokens`      |

**Status mapping:**

| Langfuse `level` | HoneyHive `status` |
| ---------------- | ------------------ |
| `"ERROR"`        | `"error"`          |
| Any other value  | `"success"`        |

***

### ID management

| ID type               | Strategy                       |
| --------------------- | ------------------------------ |
| `session_id`          | Generated UUID                 |
| `event_id`            | Generated UUID                 |
| Original Langfuse IDs | Preserved in `metadata` fields |

The migration script maintains an in-memory `trace_id_to_session_id` dictionary that maps each Langfuse `trace_id` to its generated HoneyHive `session_id`. This mapping links events to sessions and attaches scores to the correct sessions.

***

### Known limitations

<Accordion title="Score migration limitations">
  * **In-memory mapping:** Scores can only attach to sessions migrated in the same run.
  * **Separate pagination:** Traces and scores use independent pagination. A trace on page 19 may have scores on page 39.
  * **No native score storage:** Scores are stored in `metadata` since HoneyHive does not persist `feedback`/`metrics` at the top level via `/session/start`.
</Accordion>

<Accordion title="Data not migrated">
  | Langfuse field            | Reason                            |
  | ------------------------- | --------------------------------- |
  | `public`                  | No equivalent in HoneyHive        |
  | `bookmarked`              | No equivalent in HoneyHive        |
  | `createdAt` / `updatedAt` | HoneyHive uses its own timestamps |
  | Prompt templates          | Different architecture            |
  | Dataset items             | Different architecture            |
  | Annotation queues         | Not supported in migration script |
</Accordion>

***

## Troubleshooting

### Traces not appearing

**Symptom:** Application runs but no traces show in HoneyHive.

1. Verify your API key is set:
   ```python theme={null}
   import os
   print(os.getenv("HH_API_KEY"))
   ```

2. Check initialization order. HoneyHive must initialize before other imports:
   ```python theme={null}
   # Initialize HoneyHive FIRST
   from honeyhive import HoneyHiveTracer
   HoneyHiveTracer.init(api_key="...", project="...")

   # Then import other libraries
   from openai import OpenAI
   ```

### Missing child spans

**Symptom:** Parent traces appear but nested spans are missing.

Use nested `@trace` decorators:

```python theme={null}
@trace(event_name="parent")
def parent():
    child()

@trace(event_name="child")
def child():
    do_work()
```

### Evaluation scores not syncing

**Symptom:** Langfuse scores do not appear in HoneyHive.

Langfuse scores are **not** migrated automatically. Recreate them using one of these approaches:

1. **Simple metrics:** `enrich_span(metrics={...})` in your code
2. **LLM evaluations:** Configure server-side evaluators in the HoneyHive dashboard
3. **Human review:** Set up [annotation queues](/evaluators/human) in HoneyHive

***

## Next steps

<CardGroup cols={2}>
  <Card title="LLM evaluators" icon="robot" href="/evaluators/llm">
    Set up LLM-as-judge evaluators for automated quality scoring
  </Card>

  <Card title="Annotation queues" icon="users" href="/evaluators/human">
    Create human review workflows for expert evaluation
  </Card>

  <Card title="Alerts and monitoring" icon="bell" href="/monitoring/alerts/alerts_overview">
    Configure production alerts for quality degradation
  </Card>

  <Card title="Custom dashboards" icon="chart-line" href="/monitoring/charts">
    Build custom metrics dashboards
  </Card>
</CardGroup>

<Note>
  **Self-hosting Langfuse?** HoneyHive offers [dedicated cloud](/setup/dedicated) and [self-hosted](/setup/self-hosted) options with enterprise support. Contact [sales@honeyhive.ai](mailto:sales@honeyhive.ai) for migration assistance.
</Note>
