-
Notifications
You must be signed in to change notification settings - Fork 246
docs(sdk): expand JS/TS v3 → v4 migration guide #3129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
@@ -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) | ||
|
|
||
|
|
@@ -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: | ||
|
|
@@ -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], | ||
| }); | ||
|
|
||
| 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) | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The snippet creates a Prompt To Fix With AIThis 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> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instrumentation.manuallyInstrument(Anthropic)is called beforesdk.start(), and the sameinstrumentationobject is also passed in theinstrumentationsarray. WhenNodeSDK.start()runs it callsenable()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
instrumentationsarray only and let the SDK handle patching.manuallyInstrumentis 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-anthropicand update the snippet accordingly?Prompt To Fix With AI