# Langfuse URL: /docs/integrations/observability/langfuse Trace AI SDK calls into Langfuse via OpenTelemetry for tracing, evals, and prompt management. [Langfuse](https://langfuse.com/) is an open-source LLM observability platform. It gives you a hierarchical trace per request (planner → tool calls → final LLM step), prompt-level analytics, datasets, and LLM-as-judge evals. Self-hostable, OpenTelemetry-native. Pick Langfuse when you want to see the agent's full call tree inside a single turn. It's complementary to [Helicone](/docs/integrations/observability/helicone), which proxies and logs individual provider calls; many teams run both, with Helicone capturing the request log and Langfuse capturing the trace. ## How it works \[#how-it-works] ``` your route ──► AI SDK streamText (with experimental_telemetry) │ ▼ OpenTelemetry SDK ──► LangfuseSpanProcessor ──► Langfuse ``` Langfuse subscribes to OpenTelemetry spans the AI SDK already emits when telemetry is enabled. No proxy, no wrapping; the SDK ships spans and Langfuse renders them. ## Setup \[#setup] ### Get Langfuse credentials \[#get-langfuse-credentials] Sign up at [langfuse.com](https://langfuse.com/) (or self-host) and create a project. Copy the public and secret keys from project settings. ```sh title=".env.local" LANGFUSE_PUBLIC_KEY=pk-lf-... LANGFUSE_SECRET_KEY=sk-lf-... LANGFUSE_BASE_URL=https://cloud.langfuse.com ``` The base URL is region-specific: `https://cloud.langfuse.com` (EU), `https://us.cloud.langfuse.com`, or your self-hosted URL. ### Install the OTel and Langfuse packages \[#install-the-otel-and-langfuse-packages] `@langfuse/tracing` provides the helpers that label traces with user, session, and trace name. `@langfuse/otel` provides the span processor. `@opentelemetry/sdk-node` is the OTel SDK. ### Initialize OpenTelemetry once at startup \[#initialize-opentelemetry-once-at-startup] Create an instrumentation file that boots the OTel SDK and registers the Langfuse span processor. In Next.js this goes in `instrumentation.ts` so it runs once per server process. Export the processor at module scope so other code can call `forceFlush()` on it. ```ts title="instrumentation.ts" import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor } from "@langfuse/otel"; export const langfuseSpanProcessor = new LangfuseSpanProcessor(); export async function register() { if (process.env.NEXT_RUNTIME !== "nodejs") return; const sdk = new NodeSDK({ spanProcessors: [langfuseSpanProcessor], }); sdk.start(); } ``` `NEXT_RUNTIME !== "nodejs"` skips the edge runtime, where OTel doesn't run. On Next.js 14 or earlier, also set `experimental.instrumentationHook = true` in `next.config.mjs`: ```js title="next.config.mjs" const nextConfig = { experimental: { instrumentationHook: true }, }; export default nextConfig; ``` ### Wrap AI SDK calls with `propagateAttributes` \[#wrap-ai-sdk-calls-with-propagateattributes] Enable telemetry with `experimental_telemetry: { isEnabled: true }`, and wrap each call in `propagateAttributes` from `@langfuse/tracing` to set the trace name and group traces by user / session. ```ts title="app/api/chat/route.ts" import { openai } from "@ai-sdk/openai"; import { streamText, convertToModelMessages } from "ai"; import type { UIMessage } from "ai"; import { propagateAttributes } from "@langfuse/tracing"; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const userId = ""; const sessionId = ""; const result = await propagateAttributes( { traceName: "chat-completion", userId, sessionId }, async () => streamText({ model: openai("gpt-4o"), messages: await convertToModelMessages(messages), experimental_telemetry: { isEnabled: true }, }), ); return result.toUIMessageStreamResponse(); } ``` `traceName` becomes the trace label in the Langfuse dashboard. `userId` and `sessionId` are the canonical Langfuse filter dimensions; pass real values from your auth and thread state, not literal strings. ### Run and verify \[#run-and-verify] Send a message through your assistant. Within a few seconds, a trace should appear in your Langfuse dashboard with: * The trace name set by `traceName`. * A child span per LLM call and per tool call. * Full prompt, completion, and token usage on each span. * The metadata you passed (user, session, custom keys) as filters. If nothing appears, check the server logs for OTel errors and confirm `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` are loaded in the runtime that handles the request. ## Notes \[#notes] * **Serverless flush.** On serverless platforms the function exits before OTel flushes its buffer, dropping traces. Import the processor exported in the previous step and call `await langfuseSpanProcessor.forceFlush()` before responding, or use the runtime's `waitUntil` API. Langfuse's docs cover the deployment-specific patterns. * **Self-hosting.** Point `LANGFUSE_BASE_URL` at your self-hosted instance. The integration is otherwise identical. * **Sampling.** For high-traffic apps, configure OTel sampling on `NodeSDK` to keep cost predictable. Langfuse can also sample at the project level. * **Pairing with Helicone.** They are complementary: Helicone proxies and logs every request; Langfuse traces the agent. Many teams use both. ## Related \[#related]