Langfuse is the open-source LLM engineering platform: tracing & evaluation for LLM and agent applications, prompt management, datasets & experiments, and evaluation (scores). This package provides the tracing instrumentation primitives of the Langfuse JS SDK, built on OpenTelemetry: startObservation, startActiveObservation, the observe() wrapper, and propagateAttributes for user/session attribution and prompt linking. It pairs with the LangfuseSpanProcessor from @langfuse/otel, which exports the spans to Langfuse. Prompt management, datasets/experiments, evals/scores, and the full REST API live in @langfuse/client.
This is the current SDK generation (@langfuse/* scoped packages). The unscoped langfuse npm package is the legacy v3 SDK β for new integrations use @langfuse/tracing + @langfuse/otel. Migration guides: v3 β v4, v4 β v5.
npm install @langfuse/tracing @langfuse/otel @opentelemetry/sdk-trace-node
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_BASE_URL="https://cloud.langfuse.com" # πͺπΊ EU region. πΊπΈ US: https://us.cloud.langfuse.com
The most common setup: trace streamText / generateText calls in a Next.js app, with user and session attribution, and reliable span delivery on serverless.
1. Register the span processor in instrumentation.ts:
// instrumentation.ts (project root)
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
// Exported so route handlers can flush it before the serverless
// function is frozen or terminated.
export const langfuseSpanProcessor = new LangfuseSpanProcessor();
export function register() {
const tracerProvider = new NodeTracerProvider({
spanProcessors: [langfuseSpanProcessor],
});
tracerProvider.register();
}
2. Trace the route handler with user/session attribution:
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import {
observe,
propagateAttributes,
updateActiveObservation,
} from "@langfuse/tracing";
import { trace } from "@opentelemetry/api";
import { streamText, type UIMessage } from "ai";
import { after } from "next/server";
import { langfuseSpanProcessor } from "@/instrumentation";
const handler = async (req: Request) => {
const {
messages,
chatId,
userId,
}: { messages: UIMessage[]; chatId: string; userId: string } =
await req.json();
updateActiveObservation({ input: messages });
// userId / sessionId / tags / metadata set here are applied to every
// span created inside the callback β call this as early as possible.
return propagateAttributes(
{ traceName: "chat-message", userId, sessionId: chatId },
async () => {
const result = streamText({
model: openai("gpt-5.1"),
messages,
// AI SDK β€ 6 only: experimental_telemetry: { isEnabled: true },
onFinish: async (result) => {
updateActiveObservation({ output: result.content });
// End the root observation once the stream has finished
trace.getActiveSpan()?.end();
},
onError: async (error) => {
updateActiveObservation({ output: error });
trace.getActiveSpan()?.end();
},
});
// Critical on serverless: export spans before the function freezes
after(async () => await langfuseSpanProcessor.forceFlush());
return result.toUIMessageStreamResponse();
},
);
};
// observe() wraps the handler in a root observation
export const POST = observe(handler, {
name: "handle-chat-message",
endOnExit: false, // ended manually in onFinish after the stream completes
});
See the full guide at https://langfuse.com/integrations/frameworks/vercel-ai-sdk.
import { startActiveObservation, startObservation } from "@langfuse/tracing";
await startActiveObservation("user-request", async (span) => {
span.update({ input: { query: "What is Langfuse?" } });
// Nested observation, e.g. an LLM call, typed as a generation
const generation = startObservation(
"llm-call",
{
model: "gpt-5.1",
input: [{ role: "user", content: "What is Langfuse?" }],
},
{ asType: "generation" },
);
// ... call your LLM ...
generation.update({
output: { role: "assistant", content: "..." },
usageDetails: { input: 12, output: 156 },
});
generation.end();
span.update({ output: "done" });
});
Key exports:
startObservation / startActiveObservation β create spans, generations, agents, tools, and other observation typesobserve() β wrap any existing function with tracingpropagateAttributes() β set userId, sessionId, environment, tags, metadata, version, and prompt links on all spans created within a callbackcreateTraceId() β deterministic trace IDs for correlating external IDsupdateActiveObservation, getActiveTraceId, setActiveTraceAsPublicnew LangfuseSpanProcessor({ exportMode: "immediate" }) so spans are not held in a batch.await langfuseSpanProcessor.forceFlush() before the function instance is frozen (e.g. Vercel after(), waitUntil()).onFinish (see the recipe above) so it is included in the flush.| Package | NPM | Description | Environments |
|---|---|---|---|
| @langfuse/tracing | OpenTelemetry-based tracing instrumentation | Node.js 20+ | |
| @langfuse/otel | LangfuseSpanProcessor to export OpenTelemetry spans to Langfuse |
Node.js 20+ | |
| @langfuse/client | Prompt management, datasets, experiments, scores, full REST API | Universal JS | |
| @langfuse/openai | observeOpenAI wrapper for tracing the OpenAI SDK |
Universal JS | |
| @langfuse/langchain | CallbackHandler for LangChain / LangGraph tracing |
Universal JS | |
| @langfuse/vercel-ai-sdk | Telemetry integration for Vercel AI SDK v7 | Universal JS | |
| @langfuse/browser | Browser score ingestion with public-key auth | Browser | |
| @langfuse/core | Shared core: generated API client, logger, utilities | Universal JS |