Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 307 additions & 20 deletions content/docs/observability/sdk/upgrade-path/js-v3-to-v4.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,89 @@ 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.

## Overview of changes

The v4 SDK is a major rewrite built on top of [OpenTelemetry](https://opentelemetry.io/). Instead of a single `langfuse` package that both created traces and talked to the API, v4 splits responsibilities across a set of modular packages:

| v3 package / import | v4 package | Used for |
| ---------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------- |
| `langfuse` (tracing: `langfuse.trace()`, etc.) | `@langfuse/tracing` + `@langfuse/otel` | Creating traces and observations via OpenTelemetry |
| `langfuse` (prompts, scores, datasets) | `@langfuse/client` | Non-tracing features (prompt management, scoring, datasets) |
| `langfuse` (`observeOpenAI`) | `@langfuse/openai` | OpenAI SDK integration |
| `langfuse-langchain` | `@langfuse/langchain` | LangChain.js integration |
| `langfuse-vercel` (`LangfuseExporter`) | `@langfuse/otel` (`LangfuseSpanProcessor`) | Vercel AI SDK integration |

The most important conceptual change: **tracing now requires an OpenTelemetry setup**. You register the [`LangfuseSpanProcessor`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html) with an OpenTelemetry `NodeSDK` once at startup, and then create observations with functions from `@langfuse/tracing` anywhere in your application.

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

## Installation

Replace the v3 packages with the modular v4 packages.

**v3:**

```bash
npm install langfuse langfuse-langchain
```

**v4:**

```bash
# Tracing (OpenTelemetry-based)
npm install @langfuse/tracing @langfuse/otel @opentelemetry/sdk-node

# Add only the packages you use:
npm install @langfuse/client # prompt management, scoring, datasets
npm install @langfuse/openai # OpenAI integration
npm install @langfuse/langchain # LangChain integration
```

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

In v3 you instantiated a single `Langfuse` client that handled everything. In v4 you set up OpenTelemetry once by registering the [`LangfuseSpanProcessor`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html), typically in a dedicated `instrumentation.ts` file that is imported at the very top of your application's entry point.

**v3:**

```typescript
import { Langfuse } from "langfuse";

const langfuse = new Langfuse({
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
secretKey: process.env.LANGFUSE_SECRET_KEY,
baseUrl: process.env.LANGFUSE_BASEURL,
});
```

**v4:**

```typescript filename="instrumentation.ts"
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";

// Export the processor so you can flush it later (see "Shutdown and flushing")
export const langfuseSpanProcessor = new LangfuseSpanProcessor();

export const sdk = new NodeSDK({
spanProcessors: [langfuseSpanProcessor],
});

sdk.start();
```

Import this file at the top of your app's entry point so it runs before any traced code:

```typescript filename="index.ts"
import "./instrumentation";
// ... rest of your application
```

<Callout type="info">
**Next.js users:** Use the OpenTelemetry setup via `NodeSDK` shown above rather than `registerOTel` from `@vercel/otel`. The [`@vercel/otel` package does not yet support the OpenTelemetry JS SDK v2](https://github.com/vercel/otel/issues/154) that `@langfuse/tracing` and `@langfuse/otel` are based on. See the [Platform-specific notes](#platform-specific-notes) below.
</Callout>

## Tracing

The v4 SDK tracing is a major rewrite based on OpenTelemetry and introduces several breaking changes.
Expand All @@ -28,7 +107,91 @@ The v4 SDK tracing is a major rewrite based on OpenTelemetry and introduces seve
- 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.
The methods that created traces and observations on the client are replaced by standalone functions:

| v3 | v4 |
| --------------------------------------------- | ----------------------------------------------------------------------------------- |
| `langfuse.trace({ name })` | `startObservation("name")` or `startActiveObservation("name", async (span) => ...)` |
| `trace.span({ name })` | `span.startObservation("name")` |
| `trace.generation({ name, model })` | `span.startObservation("name", { model }, { asType: "generation" })` |
| `obj.end({ output })` / `obj.update({ ... })` | `obj.update({ output }).end()` / `obj.update({ ... })` |

**v3:**

```typescript
import { Langfuse } from "langfuse";

const langfuse = new Langfuse();

const trace = langfuse.trace({ name: "user-request" });

const generation = trace.generation({
name: "llm-call",
model: "gpt-4o",
input: [{ role: "user", content: "What is the capital of France?" }],
});

generation.end({ output: "Paris is the capital of France." });
```

**v4:**

```typescript
import { startActiveObservation, startObservation } from "@langfuse/tracing";

await startActiveObservation("user-request", async (span) => {
span.update({ input: { query: "What is the capital of France?" } });

const generation = startObservation(
"llm-call",
{
model: "gpt-4o",
input: [{ role: "user", content: "What is the capital of France?" }],
},
{ asType: "generation" },
);

generation.update({ output: { content: "Paris is the capital of France." } }).end();
});
```

See the [instrumentation docs](/docs/observability/sdk/instrumentation) for the three available instrumentation styles ([context manager](/docs/observability/sdk/instrumentation#context-manager), [observe wrapper](/docs/observability/sdk/instrumentation#observe-wrapper), and [manual observations](/docs/observability/sdk/instrumentation#manual-observations)).

## Shutdown and flushing

Because the SDK exports spans in the background, you must flush before short-lived processes (scripts, serverless functions, workers) exit, otherwise traces can be lost. In v3 this was `langfuse.shutdownAsync()` / `langfuse.flushAsync()`. In v4 you call [`shutdown()`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html) on the OpenTelemetry `NodeSDK`, or [`forceFlush()`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html#forceflush) on the exported `LangfuseSpanProcessor`.

**v3:**

```typescript
await langfuse.shutdownAsync();
// or, to flush without shutting down:
await langfuse.flushAsync();
```

**v4:**

```typescript
import { sdk } from "./instrumentation";

// Graceful shutdown (e.g. at the end of a script)
await sdk.shutdown();
```

For serverless functions that should keep running, flush the processor instead of shutting down the whole SDK:

```typescript filename="handler.ts" /forceFlush/
import { langfuseSpanProcessor } from "./instrumentation";

export async function handler(event, context) {
// ... your application logic ...

// Flush before the function freezes/exits
await langfuseSpanProcessor.forceFlush();
}
```

See [client lifecycle & flushing](/docs/observability/sdk/instrumentation#client-lifecycle--flushing) for serverless and Vercel Cloud Function patterns.

## [Prompt Management](/docs/prompt-management/overview)

Expand Down Expand Up @@ -59,6 +222,40 @@ See the [SDK v4 docs](/docs/observability/sdk/overview) for details on each.
const prompt = await langfuse.prompt.get("my-prompt", { version: "1.0" });
```

## Scoring

Scoring moves from the tracing client to the [`LangfuseClient`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_client.LangfuseClient.html) in `@langfuse/client`, and the method is now namespaced under `langfuse.score.create()`.

**v3:**

```typescript
import { Langfuse } from "langfuse";

const langfuse = new Langfuse();

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

**v4:**

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

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.

## OpenAI integration

- **Import**: The import of the OpenAI integration is now:
Expand All @@ -69,17 +266,36 @@ See the [SDK v4 docs](/docs/observability/sdk/overview) for details on each.

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

## Vercel AI SDK
Note that `observeOpenAI` relies on the OpenTelemetry setup from the [Initialization](#initialization) section, so make sure your `instrumentation.ts` is imported before any OpenAI calls.

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

Please see [full example on usage with the AI SDK](/docs/observability/sdk/instrumentation#framework-third-party-telemetry) for more details.
There is no dedicated Langfuse package for the Anthropic SDK. In v4 you instrument it with the OpenTelemetry-native [`AnthropicInstrumentation`](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-anthropic) from OpenInference, registered alongside the `LangfuseSpanProcessor`.

<Callout>
```bash
npm install @anthropic-ai/sdk @arizeai/openinference-instrumentation-anthropic @langfuse/otel @opentelemetry/sdk-node
```

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.
```typescript filename="instrumentation.ts"
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic";
import Anthropic from "@anthropic-ai/sdk";

</Callout>
const instrumentation = new AnthropicInstrumentation();
instrumentation.manuallyInstrument(Anthropic);

export const langfuseSpanProcessor = new LangfuseSpanProcessor();

export const sdk = new NodeSDK({
spanProcessors: [langfuseSpanProcessor],
instrumentations: [instrumentation],
});
Comment on lines +285 to +293

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Anthropic: double-instrumentation risk

instrumentation.manuallyInstrument(Anthropic) is called before sdk.start(), and the same instrumentation object is also passed in the instrumentations array. When NodeSDK.start() runs it calls enable() on every instrumentation in that array, which may wrap the Anthropic module a second time, producing duplicate spans.

The common OpenInference pattern for CJS is to pass the instrumentation to the instrumentations array only and let the SDK handle patching. manuallyInstrument is typically used instead of the array (e.g., for ESM where auto-patching is unreliable). Could you confirm which combination is correct for @arizeai/openinference-instrumentation-anthropic and update the snippet accordingly?

Prompt To Fix With AI
This is a comment left during a code review.
Path: content/docs/observability/sdk/upgrade-path/js-v3-to-v4.mdx
Line: 285-293

Comment:
**Anthropic: double-instrumentation risk**

`instrumentation.manuallyInstrument(Anthropic)` is called before `sdk.start()`, and the same `instrumentation` object is also passed in the `instrumentations` array. When `NodeSDK.start()` runs it calls `enable()` on every instrumentation in that array, which may wrap the Anthropic module a second time, producing duplicate spans.

The common OpenInference pattern for CJS is to pass the instrumentation to the `instrumentations` array only and let the SDK handle patching. `manuallyInstrument` is typically used *instead of* the array (e.g., for ESM where auto-patching is unreliable). Could you confirm which combination is correct for `@arizeai/openinference-instrumentation-anthropic` and update the snippet accordingly?

How can I resolve this? If you propose a fix, please make it concise.


sdk.start();
```

After this setup, all `anthropic.messages.create(...)` calls are traced automatically. See the [Anthropic JS/TS integration](/integrations/model-providers/anthropic-js) for a full example.

## [Langchain integration](/integrations/frameworks/langchain)

Expand All @@ -91,6 +307,34 @@ Please note that provided tool definitions to the LLM are now mapped to `metadat

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

The `CallbackHandler` is still passed via the `callbacks` array on invocation, and it relies on the OpenTelemetry setup from the [Initialization](#initialization) section.

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

const langfuseHandler = new CallbackHandler();

await chain.invoke({ input: "..." }, { callbacks: [langfuseHandler] });
```

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

<Callout>

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.

</Callout>

## Other framework integrations

Because v4 is built on OpenTelemetry, you can use **any** OpenTelemetry instrumentation for your stack (for example LlamaIndexTS or other OpenInference instrumentations) by registering it on the `NodeSDK` next to the `LangfuseSpanProcessor`, exactly as shown for the [Anthropic SDK](#anthropic-sdk) above. The spans those libraries emit are automatically picked up and sent to Langfuse — no Langfuse-specific wrapper is required.

See the [integrations overview](/integrations) for the list of supported libraries.

## `langfuseClient.getTraceUrl`

- method is now asynchronous and returns a promise
Expand All @@ -99,28 +343,71 @@ Please note that provided tool definitions to the LLM are now mapped to `metadat
const traceUrl = await langfuseClient.getTraceUrl(traceId);
```

## Scoring
## Datasets

- **Import**: The import of the Langfuse client is now:
See [datasets documentation](/docs/evaluation/dataset-runs/remote-run#setup--run-via-sdk) for new dataset methods.

```typescript
import { LangfuseClient } from "@langfuse/client";
## Platform-specific notes [#platform-specific-notes]

### Next.js edge runtime

The v4 tracing setup uses the OpenTelemetry `NodeSDK` and depends on Node.js APIs (such as `crypto`) that are **not available in the Next.js edge runtime**. Run any traced code (and the instrumentation setup) on the **Node.js runtime**.

- In Next.js, register the SDK from `instrumentation.ts` and guard it so it only runs on the Node.js runtime:

```typescript filename="instrumentation.ts"
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./instrumentation.node");
}
}
```

- **Usage**: The usage of the Langfuse client is now:
Put the `NodeSDK` / `LangfuseSpanProcessor` setup from the [Initialization](#initialization) section in `instrumentation.node.ts`.

- For route handlers or pages that perform traced LLM calls, opt into the Node.js runtime:

```typescript
const langfuse = new LangfuseClient();
export const runtime = "nodejs";
```

### Cloudflare Workers and other non-Node runtimes

Cloudflare Workers and similar runtimes are not full Node.js environments. To use the v4 SDK there:

- Enable the [`nodejs_compat`](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) compatibility flag so the required Node.js APIs are available.
- Configure the processor for short-lived execution and flush explicitly before the request finishes, since there is no long-running process to drain the buffer:

```typescript /exportMode: "immediate"/ /forceFlush/
import { LangfuseSpanProcessor } from "@langfuse/otel";

await langfuse.score.create({
traceId: "trace_id_here",
name: "accuracy",
value: 0.9,
export const langfuseSpanProcessor = new LangfuseSpanProcessor({
exportMode: "immediate",
});

// In your fetch handler, keep the worker alive until the flush completes:
// ctx.waitUntil(langfuseSpanProcessor.forceFlush());
```
Comment on lines +381 to 390

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cloudflare Workers snippet is incomplete

The snippet creates a LangfuseSpanProcessor but omits the surrounding NodeSDK setup (new NodeSDK({ spanProcessors: [langfuseSpanProcessor] }) / sdk.start()). Without it, the processor is never wired to an active tracer provider and no spans will be exported. The ctx.waitUntil(...) call is also commented out, so a reader who copy-pastes the example will silently drop all traces. Consider showing the full setup (processor creation → NodeSDK → sdk.start()ctx.waitUntil(langfuseSpanProcessor.forceFlush())) so the Cloudflare pattern is self-contained.

Prompt To Fix With AI
This is a comment left during a code review.
Path: content/docs/observability/sdk/upgrade-path/js-v3-to-v4.mdx
Line: 381-390

Comment:
**Cloudflare Workers snippet is incomplete**

The snippet creates a `LangfuseSpanProcessor` but omits the surrounding `NodeSDK` setup (`new NodeSDK({ spanProcessors: [langfuseSpanProcessor] })` / `sdk.start()`). Without it, the processor is never wired to an active tracer provider and no spans will be exported. The `ctx.waitUntil(...)` call is also commented out, so a reader who copy-pastes the example will silently drop all traces. Consider showing the full setup (processor creation → NodeSDK → `sdk.start()``ctx.waitUntil(langfuseSpanProcessor.forceFlush())`) so the Cloudflare pattern is self-contained.

How can I resolve this? If you propose a fix, please make it concise.


See [custom scores documentation](/docs/evaluation/evaluation-methods/custom-scores) for new scoring methods.
### Self-hosted minimum versions

## Datasets
If you self-host Langfuse, the OTel-based v4 SDK requires a recent platform version:

See [datasets documentation](/docs/evaluation/dataset-runs/remote-run#setup--run-via-sdk) for new dataset methods.
- **Minimum:** [Langfuse platform ≥ 3.63.0](https://github.com/langfuse/langfuse/releases/tag/v3.63.0) for the OTel-based SDKs.
- **Recommended:** [Langfuse platform ≥ 3.95.0](https://github.com/langfuse/langfuse/releases/tag/v3.95.0) so all v4 features work correctly.

Upgrade your deployment (e.g. by pulling the latest Docker images and redeploying) before rolling out the v4 SDK. See the [self-hosting upgrade guide](/self-hosting/upgrade) for details.

## Migration checklist

1. Replace the `langfuse` / `langfuse-langchain` / `langfuse-vercel` packages with the modular `@langfuse/*` packages you need.
2. Add an OpenTelemetry setup (`instrumentation.ts`) that registers the `LangfuseSpanProcessor`, and import it at your app's entry point.
3. Rename `LANGFUSE_BASEURL` to `LANGFUSE_BASE_URL`.
4. Replace `langfuse.trace()` / `.span()` / `.generation()` with `startObservation` / `startActiveObservation` from `@langfuse/tracing`.
5. Move scoring, prompt management, and datasets to `LangfuseClient` from `@langfuse/client`.
6. Replace `langfuse.shutdownAsync()` / `flushAsync()` with `sdk.shutdown()` or `langfuseSpanProcessor.forceFlush()`.
7. Update integration imports (`@langfuse/openai`, `@langfuse/langchain`) and swap `LangfuseExporter` for `LangfuseSpanProcessor` for the Vercel AI SDK.
8. If you run on Next.js edge, Cloudflare Workers, or another non-Node runtime, apply the [platform-specific notes](#platform-specific-notes).
9. If self-hosting, confirm your platform version meets the [minimum requirements](#self-hosted-minimum-versions).
</content>
</invoke>