Skip to main content

Overview

Laminar is OpenTelemetry-native, so it captures full traces of every Vercel AI SDK call: generateText, streamText, embed, tool calls, and provider-level request and response payloads. What Laminar captures:
  • Prompts, messages, and system instructions sent to the model.
  • Model output, reasoning, and structured object results.
  • Tool calls, arguments, and tool results.
  • Token counts, latency, and cost per call.
  • Provider identity (OpenAI, Anthropic, Google, Mistral, and others) and model name.
The AI SDK changed its telemetry API in v7. The setup below shows both styles. On AI SDK v7 you register Laminar once and every call is traced. On v5 and v6 you initialize Laminar and pass its tracer per call. Pick the tab that matches your ai version.

Getting Started

1

Install

Swap @ai-sdk/openai for any provider adapter you use (full list).
2

Set environment variables

To get the project API key, go to the Laminar dashboard, click the project settings, and generate a project API key. This is available both in the cloud and in the self-hosted version of Laminar.Specify the key at Laminar initialization. If not specified, Laminar will look for the key in the LMNR_PROJECT_API_KEY environment variable.
3

Register Laminar telemetry

Register Laminar once at the entry point of your application, before the first AI SDK call. This one line initializes all of Laminar’s tracing and wires it into the AI SDK.
new LaminarAiSdkTelemetry() initializes Laminar for you (it calls Laminar.initialize() internally if Laminar isn’t already initialized), so you don’t need a separate Laminar.initialize() call. Pass options to the constructor to configure it: see Configure the integration.
@lmnr-ai/lmnr also exports registerAiSdkTelemetry(), a one-line equivalent that registers the same integration without importing registerTelemetry from ai:
It takes the same options object as LaminarAiSdkTelemetry (including laminarOptions). Reach for it when you want to register telemetry before the AI SDK is loaded, or to keep the import surface to a single package.
4

Use the AI SDK as usual

No per-call configuration. Every generate* and stream* call is traced automatically.
The same applies to streamText and embed.

Turn telemetry off for one call

With registerTelemetry, tracing is on for every call by default. To disable it for a single call site, pass a telemetry block with isEnabled: false. This overrides the global registration for that call only.
The same telemetry block also carries functionId and metadata if you want to name a span or attach metadata to one call:
On AI SDK v5 and v6, the equivalent control is the experimental_telemetry block: omit it (or set isEnabled: false) to skip tracing for a call, and pass functionId / metadata there.

Configure the integration

LaminarAiSdkTelemetry takes an options object. All options are optional.
laminarOptions accepts exactly what you would pass to Laminar.initialize(). Use it to set the project API key, base URL, or any other init option in one place, so you don’t need a separate Laminar.initialize() call.
If Laminar is already initialized when the integration is constructed, laminarOptions is ignored.
Both default to true. Set either to false to keep prompt or response content off your spans (for sensitive data or to reduce payload size). Token counts, finish reasons, latency, and cost are always recorded, regardless of these flags.
Defaults to false. Set it to true to emit an extra span for each step of a multi-step generation, so the trace shows the step boundaries explicitly in addition to the LLM and tool spans.

Flush spans before exit

In short-lived scripts and serverless runtimes, the process can exit before Laminar finishes sending its last batch of spans. Call Laminar.shutdown() at the end of a script, or Laminar.flush() at a checkpoint you want to force out, to make sure the final spans land.
This applies to both integration styles. Long-running servers don’t need it: the batch processor flushes on its own schedule.

Next.js setup

In Next.js, Laminar lives in instrumentation.ts, which Next.js auto-loads before any route or server component runs.
1

Install

2

Set environment variables

3

Update next.config.ts

Tell Next.js to treat Laminar as an external server package. Laminar depends on OpenTelemetry, which uses Node-specific APIs Next.js cannot bundle.
next.config.ts
More on this option in the Next.js docs.
4

Initialize in instrumentation.ts

Create instrumentation.ts at the project root.
instrumentation.ts
Laminar only runs in the nodejs runtime. The guard skips Edge runtime routes automatically.
If you already use @vercel/otel or @sentry/nextjs, initialize them first, then Laminar. See Coexisting with @vercel/otel below.
5

Use the AI SDK in a route handler

app/api/chat/route.ts

Tracing a multi-step agent

The AI SDK runs the tool-calling loop for you: the model calls a tool, the SDK runs the tool’s execute, feeds the result back, and repeats until the model stops calling tools or a stop condition trips. Laminar records every step as a span under one trace, so the transcript shows each tool call and its result in order. Two shapes are common. generateText with tools and stopWhen is the workhorse. ToolLoopAgent is a thin wrapper if you want to reuse the same tools and instructions across many calls.

generateText with tools

stopWhen: stepCountIs(5) caps the loop at five model-and-tool rounds. You can combine conditions (stopWhen: [stepCountIs(20), hasToolCall('finalize')]) or pass a custom predicate.
On AI SDK v5 and v6, replace the telemetry block with experimental_telemetry: { isEnabled: true, tracer: getTracer(), functionId: 'travel-assistant', metadata: { env: 'production' } }.

ToolLoopAgent

Reuse the same configuration across calls by constructing a ToolLoopAgent once:
Both shapes produce the same trace structure in Laminar: one parent span per call, nested tool-call spans, and the final model output in the transcript.

Debugging with the replay debugger

The Laminar debugger records a run, lets you edit your code, and replays from a cached checkpoint so unchanged LLM calls are served from cache. For the AI SDK, caching needs one extra step: wrap each model with wrapLanguageModel, which hashes the call’s input and asks the Laminar backend for a cached response. The telemetry wiring above is unchanged.
See AI SDK caching for the full debugger setup, including the v5 and v6 telemetry variant.

See what happened in a trace

Open the trace in Laminar and you get the transcript view: system prompt, user messages, model output, tool calls, and tool results laid out as a conversation. Sub-agents collapse to their input and final output so you read what mattered, not a tree of span names.
Vercel AI SDK trace in Laminar

Vercel AI SDK trace in Laminar: the generateText call with tool invocations, the model response, and each step shown as a transcript entry.

Tool calls appear as nested spans with their arguments and return values captured. More on the trace UX: Viewing traces.

Track outcomes with Signals

Traces answer what happened on this run. Signals answer the cross-trace question: how often does the agent recommend a product that wasn’t in stock, when does a tool call return an empty result, which generateText calls fan out into more steps than expected. A Signal pairs a plain-language prompt with a JSON output schema. Laminar runs it live on new traces (Triggers) or backfills it across history (Jobs) and records a structured event every time it matches. From there you query, cluster, and alert on events across every trace.
Every new project ships with a Failure Detector Signal that categorizes issues on any trace over 1000 tokens. Open it from the Signals sidebar to see events as soon as your AI SDK traces arrive.

Query across traces

  • SQL editor for ad-hoc queries across traces, spans, signals, and evals.
  • SQL API for programmatic access from scripts and pipelines.
  • CLI (lmnr-cli sql query) for terminal-driven queries and piping JSON into shell tools or coding agents.
  • MCP server to query Laminar from Claude Code, Cursor, or Codex.

Grouping calls inside one route

If a single route makes multiple LLM calls, wrap them in observe to group them under one parent span.
app/api/chat/route.ts
You also get grouping for free if another tracing library (for example @vercel/otel or @sentry/nextjs) is already wrapping your handler. See the full observe reference for session IDs, user IDs, metadata, and tags.

Coexisting with @vercel/otel

If you already register a tracer provider with @vercel/otel, do not call Laminar.initialize() (that would register a second provider). Plug Laminar’s span processor into the existing one instead:
instrumentation.ts
On AI SDK v7, register Laminar telemetry after this so calls are traced without a per-call tracer. On v5 and v6, keep passing getTracer() per call.

Migrating from v5 or v6 to v7

If you already trace the AI SDK with the experimental_telemetry + getTracer() pattern, here is how to move to the v7 style. The trace shapes are nearly identical, so your existing traces, Signals, and dashboards keep working.
1

Upgrade the AI SDK

Update ai, @lmnr-ai/lmnr, and every @ai-sdk/* provider package you use to the latest version.
2

Register Laminar once instead of initializing

Replace the Laminar.initialize() call at your entry point with a single registerTelemetry call. The integration initializes Laminar for you, so move any Laminar.initialize() options into laminarOptions.
3

Drop experimental_telemetry from your call sites

Once Laminar is registered, every call is traced. Remove the per-call experimental_telemetry block.
4

Move any per-call settings to the telemetry block

If a call passed functionId or metadata through experimental_telemetry, or you want to turn tracing off for it, use the telemetry block instead.
5

Keep wrapLanguageModel for the debugger

The debugger still needs wrapLanguageModel around each model for caching. That step is unchanged across versions.
6

Keep flushing at exit

await Laminar.shutdown() (or Laminar.flush()) is still required at the end of short-lived scripts and serverless runtimes so the last spans are sent.

Troubleshooting

  • Confirm LMNR_PROJECT_API_KEY is set in the runtime environment, not just your shell.
  • Laminar must be registered (v7: registerTelemetry(new LaminarAiSdkTelemetry())) or initialized (v5/v6: Laminar.initialize()) before the first AI SDK call. In Next.js, that means it lives in instrumentation.ts.
  • On v5 and v6, experimental_telemetry is opt-in per call. If you forget to pass { isEnabled: true, tracer: getTracer() }, the call is not traced. On v7, check you didn’t pass telemetry: { isEnabled: false }.
  • Short-lived scripts can exit before spans flush. Call await Laminar.shutdown() at the end.
  • Edge runtime is not supported. Make sure your route runs in nodejs.
Add @lmnr-ai/lmnr to serverExternalPackages in next.config.ts. OpenTelemetry uses Node-specific APIs that Next.js cannot bundle.
instrumentation.ts is experimental before Next.js 15. Enable it in next.config.js:
next.config.js
Direct SDK calls are auto-instrumented by Laminar.initialize() in Node.js. In Next.js, imports inside instrumentation.ts are not visible to the rest of the app, so auto-instrumentation may miss them. Call Laminar.patch({ OpenAI, anthropic }) where you construct the client:
lib/llm-clients.ts

What’s next

Viewing traces

Read the transcript view, filter, and search across traces.

Signals

Detect behaviors and failures across every run, then query, cluster, and alert on them.

SQL editor and MCP server

Query traces programmatically from the UI, API, or your IDE.

Tracing structure

Sessions, user IDs, metadata, and tags.

OpenAI

Mixing AI SDK with the OpenAI SDK directly? Trace it here.

Anthropic

Mixing AI SDK with the Anthropic SDK directly? Trace it here.