---
title: "Python v2 → v3"
description: Migration guide for upgrading the Langfuse Python SDK from v2 to v3.
category: SDKs
---

# Python v2 → v3

If you are on Python SDK v2, we recommend upgrading directly to v4 (the latest major). See the [Python v3 → v4 migration guide](/docs/observability/sdk/upgrade-path/python-v3-to-v4). The v2 → v3 changes below still apply — complete them first, then follow the v3 → v4 guide.

The Python SDK v3 introduces significant improvements and changes compared to the legacy v2 SDK. It is **not fully backward compatible**. This comprehensive guide will help you migrate based on your current integration.

  You can find a snapshot of the v2 SDK documentation [here](https://python-sdk-v2.docs-snapshot.langfuse.com/docs/observability/sdk/python/decorators).

  Langfuse SDKs are now OpenTelemetry-native. After upgrading, Langfuse can also capture spans emitted by other OpenTelemetry instrumentation libraries in your application, such as database, HTTP, or framework instrumentation.

This can add many infrastructure spans that are not relevant for LLM observability and may significantly increase your Langfuse bill. Before rolling out the upgrade broadly, review your traces and filter out unwanted spans by instrumentation scope using the [filtering by instrumentation scope guide](/docs/observability/sdk/advanced-features#filtering-by-instrumentation-scope).

**Core Changes to SDK v2:**

- **OpenTelemetry Foundation**: v3 is built on OpenTelemetry standards
- **Trace Input/Output**: Now derived from root observation by default
- **Trace Attributes** (`user_id`, `session_id`, etc.) Can be set via enclosing spans OR directly on integrations using metadata fields (OpenAI call, Langchain invocation)
- **Context Management**: Automatic OTEL [context propagation](https://opentelemetry.io/docs/concepts/context-propagation/)

## Migration Path by Integration Type

**`@observe` Decorator Users**

**v2 Pattern:**

```python
from langfuse.decorators import langfuse_context, observe

@observe()
def my_function():
    # This was the trace
    langfuse_context.update_current_trace(user_id="user_123")
    return "result"
```

**v3 Migration:**

```python
from langfuse import observe, get_client # new import

@observe()
def my_function():
    # This is now the root span, not the trace
    langfuse = get_client()

    # Update trace explicitly
    langfuse.update_current_trace(user_id="user_123")
    return "result"
```

**[OpenAI Integration](/integrations/model-providers/openai-py)**

**v2 Pattern:**

```python
from langfuse.openai import openai

response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    # Trace attributes directly on the call
    user_id="user_123",
    session_id="session_456",
    tags=["chat"],
    metadata={"source": "app"}
)
```

**v3 Migration:**

If you do not set additional trace attributes, no changes are needed.

If you set additional trace attributes, you have two options:

**Option 1: Use metadata fields (simplest migration):**

```python
from langfuse.openai import openai

response = openai.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    metadata={
        "langfuse_user_id": "user_123",
        "langfuse_session_id": "session_456",
        "langfuse_tags": ["chat"],
        "source": "app"  # Regular metadata still works
    }
)
```

**Option 2: Use enclosing span (for more control):**

```python
from langfuse import get_client, propagate_attributes
from langfuse.openai import openai

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="chat-request") as span:

    with propagate_attributes(
        user_id="user_123",
        session_id="session_456",
        tags=["chat"],
    ):

        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": "Hello"}],
            metadata={"source": "app"}
        )

        # Set trace input and output explicitly
        span.update_trace(
            output={"response": response.choices[0].message.content},
            input={"query": "Hello"},
            )
```

  `update_trace()` is deprecated in Python SDK v4. See the [v3 → v4 migration
  guide](/docs/observability/sdk/upgrade-path/python-v3-to-v4).

**[LangChain Integration](/integrations/frameworks/langchain)** [#langchain-integration]

**v2 Pattern:**

```python
from langfuse.callback import CallbackHandler

handler = CallbackHandler(
    user_id="user_123",
    session_id="session_456",
    tags=["langchain"]
)

response = chain.invoke({"input": "Hello"}, config={"callbacks": [handler]})
```

**v3 Migration:**

You have two options for setting trace attributes:

**Option 1: Use metadata fields in chain invocation (simplest migration):**

```python
from langfuse.langchain import CallbackHandler

handler = CallbackHandler()

response = chain.invoke(
    {"input": "Hello"},
    config={
        "callbacks": [handler],
        "metadata": {
            "langfuse_user_id": "user_123",
            "langfuse_session_id": "session_456",
            "langfuse_tags": ["langchain"]
        }
    }
)
```

**Option 2: Use enclosing span (for more control):**

```python
from langfuse import get_client, propagate_attributes
from langfuse.langchain import CallbackHandler

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="langchain-request") as span:

    with propagate_attributes(
        user_id="user_123",
        session_id="session_456",
        tags=["langchain"],
    ):

        handler = CallbackHandler()
        response = chain.invoke({"input": "Hello"}, config={"callbacks": [handler]})

        # Set trace input and output explicitly
        span.update_trace(
            input={"query": "Hello"},
            output={"response": response}
            )
```

  `update_trace()` is deprecated in Python SDK v4. See the [v3 → v4 migration
  guide](/docs/observability/sdk/upgrade-path/python-v3-to-v4).

**[LlamaIndex Integration](/integrations/frameworks/llamaindex) Users**

**v2 Pattern:**

```python
from langfuse.llama_index import LlamaIndexCallbackHandler

handler = LlamaIndexCallbackHandler()
Settings.callback_manager = CallbackManager([handler])

response = index.as_query_engine().query("Hello")
```

**v3 Migration:**

```python
from langfuse import get_client, propagate_attributes
from openinference.instrumentation.llama_index import LlamaIndexInstrumentor

# Use third-party OTEL instrumentation
LlamaIndexInstrumentor().instrument()

langfuse = get_client()

with langfuse.start_as_current_observation(as_type="span", name="llamaindex-query") as span:

    with propagate_attributes(
        user_id="user_123",
    ):
        response = index.as_query_engine().query("Hello")

    span.update_trace(
        input={"query": "Hello"},
        output={"response": str(response)}
        )
```

  `update_trace()` is deprecated in Python SDK v4. See the [v3 → v4 migration
  guide](/docs/observability/sdk/upgrade-path/python-v3-to-v4).

**Low-Level SDK Users**

**v2 Pattern:**

```python
from langfuse import Langfuse

langfuse = Langfuse()

trace = langfuse.trace(
    name="my-trace",
    user_id="user_123",
    input={"query": "Hello"}
)

generation = trace.generation(
    name="llm-call",
    model="gpt-4o"
)
generation.end(output="Response")
```

**v3 Migration:**

  In v3, all spans / generations must be ended by calling `.end()` on the
  returned object.

```python
from langfuse import get_client, propagate_attributes

langfuse = get_client()

# Use context managers instead of manual objects
with langfuse.start_as_current_observation(
    as_type="span",
    name="my-trace",
    input={"query": "Hello"}  # Becomes trace input automatically
) as root_span:

    # Propagate trace attributes to all child observations
    with propagate_attributes(
        user_id="user_123",
    ):

        with langfuse.start_as_current_observation(
            as_type="generation",
            name="llm-call",
            model="gpt-4o"
        ) as generation:
            generation.update(output="Response")

        # If needed, override trace output
        root_span.update_trace(
            input={"query": "Hello"},
            output={"response": "Response"}
            )
```

  `update_trace()` is deprecated in Python SDK v4. See the [v3 → v4 migration
  guide](/docs/observability/sdk/upgrade-path/python-v3-to-v4).

## Key Migration Checklist

1. **Update Imports**:
   - Use `from langfuse import get_client` to access global client instance configured via environment variables
   - Use `from langfuse import Langfuse` to create a new client instance configured via constructor parameters
   - Use `from langfuse import observe` to import the observe decorator
   - Update integration imports: `from langfuse.langchain import CallbackHandler`

2. **Trace Attributes Pattern**:
   - **Option 1**: Use metadata fields (`langfuse_user_id`, `langfuse_session_id`, `langfuse_tags`) directly in integration calls
   - **Option 2**: Move `user_id`, `session_id`, `tags` to `propagate_attributes()`

3. **Trace Input/Output**:
   - **Critical for [LLM-as-a-judge](/docs/evaluation/evaluation-methods/llm-as-a-judge)**: Explicitly set trace input/output
   - Don't rely on automatic derivation from root observation if you need specific values

4. **Context Managers**:
   - Replace manual `langfuse.trace()`, `trace.span()` with context managers if you want to use them
   - Use [`with langfuse.start_as_current_observation()`](https://python.reference.langfuse.com/langfuse#Langfuse.start_as_current_observation) instead

5. **LlamaIndex Migration**:
   - Replace Langfuse callback with third-party OTEL instrumentation
   - Install: `pip install openinference-instrumentation-llama-index`

6. **ID Management**:
   - **No Custom Observation IDs**: v3 uses W3C Trace Context standard - you cannot set custom observation IDs
   - **Trace ID Format**: Must be 32-character lowercase hexadecimal (16 bytes)
   - **External ID Correlation**: Use [`Langfuse.create_trace_id(seed=external_id)`](https://python.reference.langfuse.com/langfuse#Langfuse.create_trace_id) to generate deterministic trace IDs from external systems

   ```python
   from langfuse import Langfuse, observe

   # v3: Generate deterministic trace ID from external system
   external_request_id = "req_12345"
   trace_id = Langfuse.create_trace_id(seed=external_request_id)

   @observe(langfuse_trace_id=trace_id)
   def my_function():
       # This trace will have the deterministic ID
       pass
   ```

7. **Initialization**:
   - Replace constructor parameters:
     - `enabled` → `tracing_enabled`
     - `threads` → `media_upload_thread_count`

8. **Datasets**

The `link` method on the dataset item objects has been replaced by a context manager that can be accessed via the `run` method on the dataset items. This is a higher level abstraction that manages trace creation and linking of the dataset item with the resulting trace.

See the [datasets documentation](/docs/evaluation/dataset-runs/remote-run) for more details.

## Detailed Change Summary

1.  **Core Change: OpenTelemetry Foundation**
    - Built on OpenTelemetry standards for better ecosystem compatibility

2.  **Trace Input/Output Behavior**
    - **v2**: Integrations could set trace input/output directly
    - **v3**: Trace input/output derived from root observation by default
    - **Migration**: Explicitly set via `span.update_trace(input=..., output=...)`

3.  **Trace Attributes Location**
    - **v2**: Could be set directly on integration calls
    - **v3**: Must be set on enclosing spans
    - **Migration**: Wrap integration calls with [`langfuse.start_as_current_observation()`](https://python.reference.langfuse.com/langfuse#Langfuse.start_as_current_observation)

4.  **Creating Observations**:
    - **v2**: `langfuse.trace()`, `langfuse.span()`, `langfuse.generation()`
    - **v3**: `langfuse.start_as_current_observation()`
    - **Migration**: Use context managers, ensure `.end()` is called or use `with` statements

5.  **IDs and Context**:

- **v3**: W3C Trace Context format, automatic [context propagation](https://opentelemetry.io/docs/concepts/context-propagation/)
- **Migration**: Use [`langfuse.get_current_trace_id()`](https://python.reference.langfuse.com/langfuse#Langfuse.get_current_trace_id) instead of `get_trace_id()`

6.  **Event Size Limitations**:
    - **v2**: Events were limited to 1MB in size
    - **v3**: No size limits enforced on the SDK-side for events

## Future support for v2

We will continue to support the v2 SDK for the foreseeable future with critical bug fixes and security patches. We will not be adding any new features to the v2 SDK. You can find a snapshot of the v2 SDK documentation [here](https://python-sdk-v2.docs-snapshot.langfuse.com/docs/observability/sdk/python/decorators).

## JS/TS SDK v3 → v4

Please follow each section below to upgrade your application from v3 to v4.

If you encounter any questions or issues while upgrading, please raise an [issue](/issues) on GitHub.

  Langfuse SDKs are now OpenTelemetry-native. After upgrading, Langfuse can also capture spans emitted by other OpenTelemetry instrumentation libraries in your application, such as database, HTTP, or framework instrumentation.

This can add many infrastructure spans that are not relevant for LLM observability and may significantly increase your Langfuse bill. Before rolling out the upgrade broadly, review your traces and filter out unwanted spans by instrumentation scope using the [filtering by instrumentation scope guide](/docs/observability/sdk/advanced-features#filtering-by-instrumentation-scope).

### Initialization

The Langfuse base URL environment variable is now `LANGFUSE_BASE_URL` and no longer `LANGFUSE_BASEURL`. For backward compatibility however, the latter will still work in v4 but not in future versions.

### Tracing

The v4 SDK tracing is a major rewrite based on OpenTelemetry and introduces several breaking changes.

1.  **OTEL-based Architecture**: The SDK is now built on top of OpenTelemetry. An OpenTelemetry Setup is required now and done by registering the [`LangfuseSpanProcessor`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html) with an OpenTelemetry `NodeSDK`.
2.  **New Tracing Functions**: The `langfuse.trace()`, `langfuse.span()`, and `langfuse.generation()` methods have been replaced by [`startObservation`](https://langfuse-js-git-main-langfuse.vercel.app/functions/_langfuse_tracing.startObservation.html), [`startActiveObservation`](https://langfuse-js-git-main-langfuse.vercel.app/functions/_langfuse_tracing.startActiveObservation.html), etc., from the `@langfuse/tracing` package.
3.  **Separation of Concerns**:
    - The **`@langfuse/tracing`** and **`@langfuse/otel`** packages are for tracing.
    - The **`@langfuse/client`** package and the [`LangfuseClient`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_client.LangfuseClient.html) class are now only for non-tracing features like scoring, prompt management, and datasets.

See the [SDK v4 docs](/docs/observability/sdk/overview) for details on each.

### Prompt Management

- **Import**: The import of the Langfuse client is now:

  ```typescript
  import { LangfuseClient } from "@langfuse/client";
  ```

- **Usage**: The usage of the Langfuse client is now:

  ```typescript
  const langfuse = new LangfuseClient();

  const prompt = await langfuse.prompt.get("my-prompt");

  const compiledPrompt = prompt.compile({ topic: "developers" });

  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: compiledPrompt }],
  });
  ```

- `version` is now an optional property of the options object of `langfuse.prompt.get()` instead of a positional argument.

  ```typescript
  const prompt = await langfuse.prompt.get("my-prompt", { version: "1.0" });
  ```

### OpenAI integration

- **Import**: The import of the OpenAI integration is now:

  ```typescript
  import { observeOpenAI } from "@langfuse/openai";
  ```

- You can set the `environment` and `release` now via the `LANGFUSE_TRACING_ENVIRONMENT` and `LANGFUSE_TRACING_RELEASE` environment variables.

### Vercel AI SDK

Works very similarly to v3, but replaces `LangfuseExporter` from `langfuse-vercel` with the regular `LangfuseSpanProcessor` from `@langfuse/otel`.

Please see [full example on usage with the AI SDK](/docs/observability/sdk/instrumentation#framework-third-party-telemetry) for more details.

Please note that provided tool definitions to the LLM are now mapped to `metadata.tools` and no longer in `input.tools`. This is relevant in case you are running evaluations on your generations.

### Langchain integration

- **Import**: The import of the Langchain integration is now:

  ```typescript
  import { CallbackHandler } from "@langfuse/langchain";
  ```

- You can set the `environment` and `release` now via the `LANGFUSE_TRACING_ENVIRONMENT` and `LANGFUSE_TRACING_RELEASE` environment variables.

### `langfuseClient.getTraceUrl`

- method is now asynchronous and returns a promise

  ```typescript
  const traceUrl = await langfuseClient.getTraceUrl(traceId);
  ```

### Scoring

- **Import**: The import of the Langfuse client is now:

  ```typescript
  import { LangfuseClient } from "@langfuse/client";
  ```

- **Usage**: The usage of the Langfuse client is now:

  ```typescript
  const langfuse = new LangfuseClient();

  await langfuse.score.create({
    traceId: "trace_id_here",
    name: "accuracy",
    value: 0.9,
  });
  ```

See [custom scores documentation](/docs/evaluation/evaluation-methods/custom-scores) for new scoring methods.

### Datasets

See [datasets documentation](/docs/evaluation/dataset-runs/remote-run#setup--run-via-sdk) for new dataset methods.

<!-- agent-instructions -->

---

## Agent Instructions

This page is part of the [Langfuse](https://langfuse.com) documentation, published as plain Markdown for AI agents. Every page is available as Markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` header. This page: `https://langfuse.com/docs/observability/sdk/upgrade-path/python-v2-to-v3.md`.

### Querying these docs

If the answer is not on this page, query the documentation instead of guessing:

- **Semantic search** across all Langfuse docs, returning an answer with the relevant pages and excerpts. Ask a specific, self-contained question:

  ```bash
  curl -sG "https://langfuse.com/api/search-docs" --data-urlencode "query=How do I trace a LangGraph agent?"
  ```

- **Index of every page**: <https://langfuse.com/llms.txt>, with per-section indexes [llms-docs.txt](https://langfuse.com/llms-docs.txt), [llms-integrations.txt](https://langfuse.com/llms-integrations.txt), and [llms-self-hosting.txt](https://langfuse.com/llms-self-hosting.txt).

### Before writing Langfuse code

- **Install the [Langfuse Agent Skill](https://langfuse.com/docs/api-and-data-platform/features/agent-skill).** It encodes Langfuse's own best practices for instrumentation, prompt management, and evaluation, and materially improves results.
- **Read [What does a good trace look like?](https://langfuse.com/docs/observability/best-practices.md)** before instrumenting an application.
- **Verify endpoints, parameters, and response fields** against the [API reference](https://api.reference.langfuse.com) instead of inferring them from code examples.
- **Use the [Langfuse CLI](https://langfuse.com/docs/api-and-data-platform/features/cli)** (`npx langfuse-cli api <resource> <action>`) to read or write traces, prompts, datasets, and scores from the terminal.

Found an error in these docs? Please open an issue at <https://github.com/langfuse/langfuse-docs/issues>.
