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

# From Langfuse

> Mechanical translation from the Langfuse Python SDK to Bento.

Both SDKs are built on OpenTelemetry, so most of your code maps over directly.

## Migrate with an AI coding tool

Install Bento's Agent Skills so your AI coding tool knows how to do the migration. Copy this prompt into Claude Code, Cursor, Codex, OpenCode, or Windsurf:

````markdown theme={null}
Migrate this project from Langfuse to Bento.

```bash
npx skills add BentoLabs-ai/skills
```

Use the `bentolabs-migrate` skill. Walk me through it step by step and confirm before editing.
````

The skill carries the full playbook: it finds your Langfuse call sites, installs Bento alongside, translates each construct, verifies traces land, then removes Langfuse. The rest of this page is the same translation guide, by hand.

## The three paths

Pick the smoothest path that applies and fall through to the next. Most migrations land on Path B for the bulk and Path C for a handful of bespoke decorators.

If your app runs Google ADK agents, take [Path A](#path-a-google-adk-integration): three lines at startup. If your call sites use `langfuse.openai`, `langfuse.langchain`, or Anthropic through an instrumentor, take [Path B](#path-b-auto-capture-with-openinference): drop one import and register one instrumentor. For bespoke `@observe`, `start_as_current_observation`, or custom spans, [Path C](#path-c-manual-translation) renames them call site by call site.

***

## Path A: Google ADK integration

If your app runs Google ADK agents, this captures every model call, tool call, and agent step automatically.

```bash theme={null}
pip install "bentolabs-sdk[adk]"
```

```python theme={null}
import bentolabs_sdk as bento

bento.init(
    user_id=lambda: get_current_user_id(),
    session_id=lambda: get_current_session_id(),
)
bento.instrument()
```

That's the whole change. See [Integrations](/python/integrations) for the full reference.

***

## Path B: Auto-capture with OpenInference

Replace Langfuse's `from langfuse.openai import OpenAI` (and the `langfuse.langchain.CallbackHandler`) with an OpenInference instrumentor registered against a `BentoLabsSpanProcessor`. Your call sites stay untouched, since Bento captures every LLM call at the SDK level.

```bash theme={null}
pip install openinference-instrumentation-openai
# Other libs as needed: -anthropic, -langchain, -bedrock, -llama-index
```

```python theme={null}
from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry import trace

from bentolabs_sdk import BentoLabsSpanProcessor

provider = TracerProvider()
provider.add_span_processor(BentoLabsSpanProcessor())
trace.set_tracer_provider(provider)

OpenAIInstrumentor().instrument(tracer_provider=provider)
```

Now swap `from langfuse.openai import OpenAI` for the stock import. The instrumentor wraps the client for you:

```python theme={null}
from openai import OpenAI

client = OpenAI()
resp = client.chat.completions.create(model="gpt-4o", messages=[...])
# Bento captures the call. Nothing else to do.
```

For LangChain, drop the `CallbackHandler` and register `LangChainInstrumentor()` the same way. For Anthropic or Bedrock, install the matching `openinference-instrumentation-*` and register it.

See [OTel transport](/python/otel-transport) for the full reference.

***

## Path C: Manual translation

For bespoke `@observe()` decorators, manual context managers, and identity helpers, translate per the tables.

### Setup

| Langfuse                                                      | Bento                       |
| ------------------------------------------------------------- | --------------------------- |
| `pip install langfuse`                                        | `pip install bentolabs-sdk` |
| `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST` | `BENTOLABS_API_KEY`         |
| `Langfuse(...)` / `get_client()`                              | `bento.init(api_key=...)`   |
| `langfuse.flush()`                                            | `bento.flush()`             |

### Decorators

| Langfuse                              | Bento                                   |
| ------------------------------------- | --------------------------------------- |
| `@observe()` on a handler             | `@bento.interaction`                    |
| `@observe()` on a tool                | `@bento.tool`                           |
| `@observe(as_type="generation", ...)` | Replace body with `bento.track_ai(...)` |

### Multi-step / context managers

| Langfuse                                            | Bento                         |
| --------------------------------------------------- | ----------------------------- |
| `start_as_current_observation(as_type="span", ...)` | `with bento.begin(...) as t:` |
| Nested `as_type="span"` (tool)                      | `t.tool_span(...)`            |

### Identity

| Langfuse                          | Bento                             |
| --------------------------------- | --------------------------------- |
| `update_current_trace(...)`       | `bento.update_current_trace(...)` |
| `update_current_observation(...)` | `bento.update_current_span(...)`  |
| `propagate_attributes(...)`       | `bento.propagate_attributes(...)` |

### What's gone

| Langfuse                   | Where it goes                                                    |
| -------------------------- | ---------------------------------------------------------------- |
| `langfuse.score(...)`      | `bento.update_current_trace(properties={"score_<name>": value})` |
| `langfuse.get_prompt(...)` | Move prompts to code or config; stash version in `properties`    |
| Datasets / dataset runs    | No primitive yet                                                 |

***

## Watch out for

A few renames don't map one to one. Bento uses `session_id` everywhere except `bento.track_ai`, which uses `convo_id`; it's the same attribute under the hood. Langfuse `metadata` becomes `properties`, a direct rename, but the magic keys like `langfuse_user_id` don't exist, so pass the real kwargs instead. And there's no OpenAI drop-in: reach for Path B (OpenInference) rather than hunting for a `from bento.openai import OpenAI` equivalent.

## Side-by-side

```python Langfuse theme={null}
from langfuse import observe, propagate_attributes
from langfuse.openai import OpenAI

client = OpenAI()

@observe()
def answer(user_id, session_id, question):
    with propagate_attributes(user_id=user_id, session_id=session_id, tags=["prod"]):
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": question}],
            name="answer-llm",
        )
        return resp.choices[0].message.content
```

```python Bento theme={null}
import bentolabs_sdk as bento
from openai import OpenAI

bento.init(api_key="bl_pk_...")
client = OpenAI()

@bento.interaction
def answer(user_id, session_id, question):
    with bento.propagate_attributes(user_id=user_id, session_id=session_id, tags=["prod"]):
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": question}],
        )
        reply = resp.choices[0].message.content
        bento.track_ai(
            event="answer-llm",
            model="gpt-4o-mini",
            provider="openai",
            input=question,
            output=reply,
        )
        return reply
```

## See also

<CardGroup cols={2}>
  <Card title="Configuration" icon="gear" href="/python/configuration">
    `init()`, identity getters, env vars.
  </Card>

  <Card title="Tracking events" icon="code" href="/python/track-ai">
    The `bento.track_ai` reference.
  </Card>
</CardGroup>
