Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

Vercel AI SDK

Trace AI SDK calls, model steps, tools, and decisions with @telemetry-dev/ai-sdk.

Add telemetryDev() to an AI SDK call and each call becomes an OpenTelemetry trace in telemetry.dev, with one span per model step and tool call, plus token usage, finish reasons, and errors.

The package plugs into the AI SDK telemetry integration API. It exports through its own tracer provider and does not change your global OpenTelemetry setup.

Install

npm install @telemetry-dev/ai-sdk ai
pnpm add @telemetry-dev/ai-sdk ai
yarn add @telemetry-dev/ai-sdk ai
bun add @telemetry-dev/ai-sdk ai

The package supports ai 6.0.111 and later 6.x and 7.x releases. Import from the entry that matches your AI SDK major version:

AI SDK Import from
7 @telemetry-dev/ai-sdk
6 @telemetry-dev/ai-sdk/v6

Set the API key

Create a project at telemetry.dev and copy an API key from the project setup page. Keys look like td_live_....

export TELEMETRY_DEV_API_KEY=td_live_...

Without an API key, telemetryDev() returns an empty integration and sends nothing. Your AI SDK calls run as usual.

Trace a call

With AI SDK 7, add telemetryDev() to the telemetry.integrations array of the call:

import { generateText } from "ai";
import { telemetryDev } from "@telemetry-dev/ai-sdk";

const { text } = await generateText({
  model,
  prompt: "Summarize the incident report.",
  runtimeContext: { userId: "u_123", sessionId: "s_456", tenant: "acme" },
  telemetry: {
    functionId: "summarize-incident",
    includeRuntimeContext: { userId: true, sessionId: true, tenant: true },
    integrations: [telemetryDev()],
  },
});

functionId names the root span. Without it, the root span is named chat (or embeddings, rerank, or evaluate for those operations).

The AI SDK does not pass runtimeContext keys to integrations unless you list them in telemetry.includeRuntimeContext. The integration records the keys you include like this:

Context key Span attribute
userId user.id
sessionId gen_ai.conversation.id
Any other included key td.metadata.<key>

AI SDK 7 still accepts experimental_telemetry as a deprecated alias for telemetry.

Register once for every call

AI SDK 7 can also register one integration for all calls:

import { registerTelemetry } from "ai";
import { telemetryDev } from "@telemetry-dev/ai-sdk";

registerTelemetry(telemetryDev());

One instance is safe to share. The integration keeps state per AI SDK callId, so overlapping calls get separate spans.

Check the trace

Run the call, then open Traces in your project. A text generation shows a root span, one chat span per model step, and an execute_tool span for each tool call.

For a stream, read the stream to the end or await result.response. The AI SDK calls the final telemetry hook only after the stream settles, so an unread stream sends no trace.

AI SDK 6

Import from /v6 and add the integration to experimental_telemetry.integrations. AI SDK 6 has no runtime context, so the integration reads identity from experimental_telemetry.metadata:

import { generateText } from "ai";
import { telemetryDev } from "@telemetry-dev/ai-sdk/v6";

const { text } = await generateText({
  model,
  prompt: "Summarize the incident report.",
  experimental_telemetry: {
    functionId: "summarize-incident",
    metadata: { userId: "u_123", sessionId: "s_456", tenant: "acme" },
    integrations: [telemetryDev()],
  },
});

metadata keys map to the same attributes as the AI SDK 7 context keys above. There is no opt-in filter: every metadata key is recorded.

Create a new telemetryDev() instance for each AI SDK 6 call. An AI SDK 6 instance holds state for one generation at a time, so a single shared instance mixes up overlapping calls.

Behavior AI SDK 7 AI SDK 6
Configuration telemetry.integrations or registerTelemetry() experimental_telemetry.integrations
Traced operations Text, object, embed, rerank, and decide operations generateText, streamText, and Agent only
Time to first chunk Recorded for streamed text and object operations Not available
One shared instance Safe Not safe for overlapping calls
Content controls Honors recordInputs and recordOutputs Always records messages and output
A call that throws Sends a trace marked as an error Sends no trace, because onFinish never runs
Provider and tool span context Provider requests and nested calls join the call’s trace No context wrappers

What is traced

AI SDK operation What telemetry.dev records
generateText, streamText, and Agent A root span with operation chat, or invoke_agent once the call uses tools. One chat span per model step, with messages, model, finish reason, token usage, sampling settings, and errors.
generateObject and streamObject A chat operation with gen_ai.output.type: "json", the JSON input and output, usage, and errors
Local tools with an execute function An execute_tool span with the tool name, call ID, arguments, result, duration, and errors
Provider-executed tools A zero-duration execute_tool span with gen_ai.tool.type: "extension", recorded when the result arrives. The AI SDK does not report how long these tools ran.
embed, embedMany, and rerank An embeddings or rerank operation with model and usage
experimental_decide (formerly experimental_evaluate) An evaluate operation with a root span and model span. Requires ai 7.0.111 or later.
Streams gen_ai.client.operation.time_to_first_chunk for streamed text and object operations

The integration also writes logs as span events, so they appear in the trace:

  • Each text generation adds a Generation completed log to its root span, with token counts when the model reports them. The log reads Generation failed when the finish reason is error.
  • Each failed tool call adds an exception event.
  • Each model warning adds a warning log.

On AI SDK 7, provider HTTP requests and AI SDK calls made inside a tool run in the span context of the outer call. Auto-instrumented provider spans and nested calls join the same trace instead of starting new ones.

How calls are grouped into traces

  • A call that starts inside an active span, such as an HTTP request span, joins that span’s trace.
  • A call with no active parent and no sessionId starts its own trace.
  • Calls with a sessionId and no active parent share one trace per session and API key. Each call’s root span sits under a session parent that is never exported, so the calls appear as siblings in start order.

The sessionId is also set as gen_ai.conversation.id on every span.

What is not traced

  • Image, speech, transcription, video, and batch calls. The AI SDK telemetry hooks do not cover them. Use the media and batch wrappers instead.
  • Experimental realtime APIs.
  • Provider-tool calls that are waiting for approval or were denied, because no tool ran.
  • Cost. The integration does not send cost. The server estimates it from token usage when it has pricing for the model; without pricing, cost stays empty rather than zero.

Media and batch wrappers

Wrap the media functions from your installed ai package:

import {
  experimental_generateSpeech,
  experimental_transcribe,
  generateImage,
} from "ai";
import {
  wrapGenerateImage,
  wrapGenerateSpeech,
  wrapTranscribe,
} from "@telemetry-dev/ai-sdk";

const options = { apiKey: process.env.TELEMETRY_DEV_API_KEY };

const tracedGenerateImage = wrapGenerateImage(generateImage, options);
const tracedGenerateSpeech = wrapGenerateSpeech(
  experimental_generateSpeech,
  options,
);
const tracedTranscribe = wrapTranscribe(experimental_transcribe, options);

wrapGenerateVideo wraps experimental_generateVideo the same way. With AI SDK 6, import the media wrappers from @telemetry-dev/ai-sdk/v6.

Each wrapped call makes a generate_content span with output type image, speech, text (for transcription), or video. The span records the model and provider, image token usage when the AI SDK reports it, the image or video count, the transcription duration, and the speech input character count. Binary inputs and outputs are never captured.

The wrappers do not inherit options from telemetryDev(). Pass the same options as the second argument, or set the environment variables.

wrapStartBatch, wrapGetBatchStatus, wrapCancelBatch, and wrapGetBatchResults wrap the experimental AI SDK batch functions the same way. They require ai 7.0.55 or later and are exported only from @telemetry-dev/ai-sdk.

Each batch network call gets its own span, with operation batch.submit, batch.status, batch.cancel, or batch.results. No span stays open for the life of the batch job. A results span stays open while you read the returned stream so it can count the items. If you never read or cancel the results stream, its span never ends.

Control captured content

AI SDK 7 records inputs and outputs by default. This includes provider-tool arguments and results, which can contain shell commands, code, search results, or MCP payloads. To leave payloads out, turn off the AI SDK recording options:

telemetry: {
  recordInputs: false,
  recordOutputs: false,
  integrations: [telemetryDev()],
}

These options leave out prompts, responses, tool arguments, tool results, and detailed error messages. Tool names, status, error types, token usage, and other metadata are still recorded. Model warning messages are replaced with a generic Model warning unless both options are on.

AI SDK 6 ignores these options and always records content.

Options

Pass options to telemetryDev(). Explicit options take precedence over environment variables, which take precedence over defaults.

Option Type Environment variable Default
apiKey string TELEMETRY_DEV_API_KEY None. Without a key, the integration does nothing.
baseUrl string TELEMETRY_DEV_BASE_URL https://ingest.telemetry.dev
environment string TELEMETRY_DEV_ENVIRONMENT production
serviceName string OTEL_SERVICE_NAME unknown_service
sampler OpenTelemetry Sampler OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG Parent-based, always on
fetch typeof fetch None globalThis.fetch
waitUntil (promise: Promise<unknown>) => void None None. The final hook waits for the export.
onError (error) => void None None

The sampler also decides whether session traces are kept. A real active parent span keeps its own sampling decision.

The integration reports export and instrumentation errors to onError. These errors never reach your AI SDK call.

Serverless runtimes

By default, the final hook of each call waits for the export to finish, so the runtime does not stop before the data is sent. To return sooner, pass the export promise to your platform’s lifetime function:

telemetryDev({ waitUntil: (promise) => ctx.waitUntil(promise) });

You do not need an instrumentation.ts file for this integration. On runtimes without process.env, pass apiKey directly.

Troubleshooting

If no trace appears:

  • Check that TELEMETRY_DEV_API_KEY is set in the process that makes the AI SDK call, or pass apiKey.
  • For streams, read the stream to the end or await result.response.
  • On AI SDK 6, check that the call itself does not throw. AI SDK 6 sends no trace for a call that throws.
  • Pass onError: console.error to see export errors, such as a rejected API key.

Last updated on October 18, 2026

Was this page helpful?