---
title: Telemetry
description: Using OpenTelemetry with AI SDK Core
url: "https://ai-sdk.dev/docs/ai-sdk-core/telemetry"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

<span id="telemetry" />

The AI SDK uses [OpenTelemetry](https://opentelemetry.io/) to collect telemetry data.
OpenTelemetry is an open-source observability framework designed to provide
standardized instrumentation for collecting telemetry data.

Check out the [AI SDK Observability Integrations](/providers/observability)
to see providers that offer monitoring and tracing for AI SDK applications.

## Enabling telemetry

### Step 1: Register the OpenTelemetry integration

OpenTelemetry collection requires the `@ai-sdk/otel` package. Install it and register the integration once at application startup:

```bash
pnpm install @ai-sdk/otel
```

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());
```

#### Next.js

For Next.js applications, create an `instrumentation.ts` file in your project root and register the AI SDK telemetry integration alongside your OpenTelemetry provider setup:

```ts title="instrumentation.ts"
import { registerOTel } from '@vercel/otel';
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

export function register() {
  registerOTel({
    serviceName: 'my-ai-app',
  });

  registerTelemetry(new OpenTelemetry());
}
```

See the [Next.js OpenTelemetry guide](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry) for more details on setting up OpenTelemetry in Next.js.

For Node.js applications (without Next.js), register the integration at the top level of your entry file.

### Step 2: Enabling Telemetry

Once a telemetry integration is registered, supported AI SDK calls emit
telemetry events by default. This includes text and object generation,
embeddings, reranking, speech generation, and both non-streaming and streaming
transcription.
You can still pass `telemetry` to attach metadata (like `functionId`) or to opt out of a specific call:

```ts {4-6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  telemetry: {
    functionId: `story-agent`,
  },
});
```

By default, both inputs and outputs are recorded. You can disable them by setting the `recordInputs` and `recordOutputs` options to `false`.

Disabling the recording of inputs and outputs can be useful for privacy, data transfer, and performance reasons.
You might for example want to disable recording inputs if they contain sensitive information.

These settings filter content before lifecycle events reach telemetry integrations
and Node.js `ai:telemetry` subscribers. Raw provider bodies and provider metadata
are omitted when either recording option is disabled. Raw tracing span results
are also omitted in that case because they can contain both inputs and outputs.
Tracing span errors use a generic failure message.
Application lifecycle callbacks and returned results retain their full content.

OpenAI Code Interpreter and shell domain secrets, and MCP authorization and
header values, are redacted from tool definitions and object request/response
bodies before they reach telemetry integrations or tracing-channel subscribers,
including when recording is enabled. Redaction uses telemetry-only copies;
provider requests and application results retain their original values.
The OpenAI provider supplies these rules, so update `@ai-sdk/openai` together
with `ai` to use this protection. Older provider versions do not supply these
telemetry views.

For `generateSpeech`, input telemetry contains the source text and output
telemetry contains the generated audio byte length, media type, and format.
For `transcribe` and `experimental_streamTranscribe`, telemetry contains the
input audio byte length and media type plus the output transcript. Raw audio is
never included in telemetry events. Streaming transcription reports its final
byte length when the audio stream has been consumed and the transcription
finishes.

Custom integrations receive stable `generateSpeech` and `transcribe`
operations through `onStart` and `onEnd`. To keep the experimental API isolated,
`experimental_streamTranscribe` uses
`experimental_onStreamTranscriptionStart` and
`experimental_onStreamTranscriptionEnd`; all three operations report failures
through `onError`.

An integration that wraps or delegates to another integration must explicitly
forward the experimental streaming transcription callbacks. Forwarding only
the stable `onStart` and `onEnd` callbacks does not observe
`experimental_streamTranscribe`.

The built-in OpenTelemetry integrations record numeric provider usage by
default, preserving each reported unit. The modern integration uses attributes
such as `gen_ai.usage.characters`, `gen_ai.usage.seconds`, and
`gen_ai.usage.input_tokens`; `LegacyOpenTelemetry` uses the corresponding
`ai.usage.*` attributes. The provider-native usage object remains available to
callbacks, and can also be included as the supplemental `ai.response.usage`
attribute by enabling `usage: true`.

### Provider endpoints

For completed language-model calls, providers can report the known HTTP request
endpoint. `OpenTelemetry` records its host as `server.address` and its port as
`server.port` on the `chat` span. This includes default ports (443 for HTTPS and
80 for HTTP) and works when input and output recording are disabled.

OpenAI Chat reports this information for both `generateText` and `streamText`,
including custom `baseURL` settings. Azure Chat and Bedrock Mantle Chat reuse
this adapter and also report their configured endpoint. Other providers leave these attributes
unset until they report their endpoint; no destination is inferred from the
provider or model name, including an unknown backend behind an intermediary.

The model-call end event's optional `request.url` contains only the HTTP origin.
Credentials, paths, queries, and fragments are removed before telemetry events
are emitted. The OpenTelemetry attributes contain only host and port.

### Opting out

Telemetry is opt-out. To disable telemetry for a specific call, set `isEnabled: false`:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  telemetry: { isEnabled: false },
});
```

Not registering integrations with `registerTelemetry()` prevents delivery to globally registered integrations, but does not disable per-call integrations or Node.js tracing-channel subscribers. To disable telemetry events for a call, including tracing-channel events, pass `telemetry: { isEnabled: false }`. Apply this setting to every call if you want to opt out throughout your application.

## Telemetry Metadata

You can provide a `functionId` to identify the function that the telemetry data is for,
and `runtimeContext` to include additional information in the telemetry data.
For the broader context model, see
[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context).

```ts {4-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  runtimeContext: {
    userId: 'user_123',
    requestId: 'req_abc',
  },
  telemetry: {
    functionId: 'my-awesome-function',
  },
});
```

### Runtime context telemetry inclusion

`runtimeContext` can contain values that are useful inside your application but should not be sent to telemetry providers, such as user identifiers, tenant IDs, or credentials. Use `telemetry.includeRuntimeContext` to mark the top-level `runtimeContext` properties that should be included in telemetry:

```ts {8-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  runtimeContext: {
    userId: 'user_123',
    requestId: 'req_abc',
  },
  telemetry: {
    includeRuntimeContext: {
      requestId: true,
    },
  },
});
```

In this example, telemetry integrations receive `runtimeContext` as `{ requestId: 'req_abc' }`. Properties set to `false` or omitted are excluded. If `telemetry.includeRuntimeContext` is omitted, no runtime context properties are included. `telemetry.includeRuntimeContext` is supported by `generateText`, `streamText`, `ToolLoopAgent`, `embed`, `embedMany`, `rerank`, and `experimental_decide`.

`telemetry.includeRuntimeContext` only filters telemetry integrations,
including OpenTelemetry integrations. Lifecycle callbacks and returned results
still receive the full `runtimeContext`.

### Tool context

Tool-specific context can also contain values that are useful during execution but should not be sent to telemetry providers, such as API keys or access tokens.
Use `telemetry.includeToolsContext` to include selected top-level properties from a tool's `context` in telemetry:

```ts {16-20,23-28}
const weatherTool = tool({
  inputSchema: z.object({
    location: z.string(),
  }),
  contextSchema: z.object({
    weatherApiKey: z.string(),
    defaultUnit: z.enum(['celsius', 'fahrenheit']),
  }),
  execute: async ({ location }, { context }) =>
    fetchWeather(location, context.weatherApiKey, context.defaultUnit),
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool },
  toolsContext: {
    weather: {
      weatherApiKey: 'weather-123',
      defaultUnit: 'fahrenheit',
    },
  },
  prompt: 'What is the weather in San Francisco?',
  telemetry: {
    includeToolsContext: {
      weather: {
        defaultUnit: true,
      },
    },
  },
});
```

In this example, telemetry integrations receive `toolsContext.weather` and tool execution telemetry event `toolContext` as `{ defaultUnit: 'fahrenheit' }`.
Properties set to `false` or omitted are excluded. If `telemetry.includeToolsContext` is omitted, no tool context properties are included. `telemetry.includeToolsContext` is supported by `generateText`, `streamText`, and `ToolLoopAgent`.

`telemetry.includeToolsContext` only filters telemetry integrations, including
OpenTelemetry integrations. Tool execution, lifecycle callbacks, and returned
results still receive the full tool context. See [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context) for the difference
between tool execution context and telemetry event context.

## Agent span names

`agentSpanName` controls root text/object generation span names. The strings
`'modelId'` (default) and `'functionId'` select a naming strategy, producing
`invoke_agent {modelId}` or `invoke_agent {telemetry.functionId}`:

```ts
registerTelemetry(new OpenTelemetry({ agentSpanName: 'functionId' }));
// With telemetry: { functionId: 'travel-planner' }, the span is:
// invoke_agent travel-planner
```

For a literal custom name, use a callback returning the complete span name:

```ts
registerTelemetry(new OpenTelemetry({ agentSpanName: () => 'my-custom-span' }));
```

The callback receives `{ modelId, functionId }` for dynamic names. If it returns
`undefined` or throws, the default name is used. The `'functionId'` strategy also
falls back to `modelId` when `functionId` is absent. Child span names and
attributes are unchanged.

## Custom Tracer

If you want your traces to use a `TracerProvider` other than the one provided by the `@opentelemetry/api` singleton, pass a custom `Tracer` to the `OpenTelemetry` constructor:

```ts {6-8}
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

const tracerProvider = new NodeTracerProvider();
registerTelemetry(
  new OpenTelemetry({
    tracer: tracerProvider.getTracer('gen_ai'),
  }),
);
```

## Durable WorkflowAgent runs

The steps of a durable `WorkflowAgent` run (`@ai-sdk/workflow`) can execute in
different processes. To report the run as one `invoke_agent` span tree, use the
same `OpenTelemetryIdGenerator` instance as your tracer provider's ID generator
and sampler, and pass it to the globally registered `OpenTelemetry` integration:

```ts
import { OpenTelemetry, OpenTelemetryIdGenerator } from '@ai-sdk/otel';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { registerTelemetry } from 'ai';

const durable = new OpenTelemetryIdGenerator();
new NodeSDK({ idGenerator: durable, sampler: durable }).start();
registerTelemetry(new OpenTelemetry({ durable }));
```

Model calls are reported live from their durable steps, so spans started by the
provider call (for example auto-instrumented HTTP spans) nest under their `chat`
span. The root, step, and tool spans appear when the run ends or suspends (for
example on a tool approval), using the original event timestamps. The root
includes the run's prompt and final output, subject to `recordInputs` and
`recordOutputs`.

A preparation step captures the active parent trace and makes the run's sampling
decision once. That context is carried to subsequent steps, preserving the trace
ID, sampling decision, and trace state. By default, the bridge uses parent-based
sampling with roots enabled. To use another sampler, pass it through
`new OpenTelemetryIdGenerator({ sampler: yourSampler })` and keep the bridge as
the provider's sampler. Unrelated spans use random IDs and the configured sampler.

Install this setup in every process that executes workflow steps, with integrations
registered in the same order. Without the bridge, `OpenTelemetry` preserves live
per-model operation traces. Other globally registered telemetry integrations receive
the recorded run lifecycle when it ends or suspends.

## Telemetry Integrations

Telemetry integrations let you hook into the generation lifecycle to build custom observability — logging, analytics, DevTools, or any other monitoring system. Instead of wiring up individual callbacks on every call, you implement a `Telemetry` once and register it globally or pass it via `telemetry.integrations`.

The `OpenTelemetry` and `LegacyOpenTelemetry` from `@ai-sdk/otel` are the built-in integrations for collecting OpenTelemetry spans (see [Enabling telemetry](#enabling-telemetry) above).

### Registering integrations globally

Use `registerTelemetry` to register an integration once for all AI SDK calls:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());
```

You can also register multiple integrations in a single call by passing them as additional arguments. They all receive the same lifecycle events:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

registerTelemetry(new OpenTelemetry(), DevToolsTelemetry());
```

### Per-call integrations

You can also pass one or more integrations to individual `generateText` or `streamText` calls. When per-call integrations are provided, they replace the globally registered integrations for that call:

```ts {6-8}
import { streamText } from 'ai';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Hello!',
  telemetry: {
    integrations: [DevToolsTelemetry()],
  },
});
```

You can combine multiple integrations — they all receive the same lifecycle events:

```ts
telemetry: {
  integrations: [DevToolsTelemetry(), customLogger()],
},
```

Errors inside integrations are caught and do not break the generation flow.

### Tracing channel

In Node.js, AI SDK telemetry lifecycle and execution events are also traced on
the `ai:telemetry` [tracing channel](https://nodejs.org/api/diagnostics_channel.html#diagnostics_channeltracingchannelnameorchannels).
This lets observability providers subscribe to AI SDK telemetry events without
requiring users to register a separate telemetry integration, and bind async
context across provider calls and tool executions.

```ts
import { tracingChannel } from 'node:diagnostics_channel';
import {
  AI_SDK_TELEMETRY_TRACING_CHANNEL,
  type InferTelemetryEvent,
  type TelemetryTracingChannelMessage,
} from 'ai';

tracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL).subscribe({
  start(message) {
    const telemetryMessage = message as TelemetryTracingChannelMessage<
      InferTelemetryEvent<unknown>
    >;

    if (
      telemetryMessage.type === 'generateText' ||
      telemetryMessage.type === 'streamText'
    ) {
      console.log('AI SDK version:', telemetryMessage.event.aiVersion);
      // Inspect telemetryMessage.event and forward it to your provider.
    }
  },
});
```

Tracing-channel events follow the same per-call telemetry settings as other
telemetry integrations. Setting `telemetry: { isEnabled: false }` disables both
registered integrations and tracing-channel events for that call.

### Building a custom integration

Implement the `Telemetry` interface from the `ai` package. All methods are optional — implement only the lifecycle events you care about:

```ts
import type { Telemetry } from 'ai';

class MyIntegration implements Telemetry {
  async onStart(event) {
    console.log('Generation started:', event.modelId);
  }

  async onStepEnd(event) {
    console.log(
      `Step ${event.stepNumber} done:`,
      event.usage.totalTokens,
      'tokens',
    );
  }

  async onToolExecutionEnd(event) {
    if (event.toolOutput.type === 'tool-result') {
      console.log(
        `Tool "${event.toolCall.toolName}" took ${event.toolExecutionMs}ms`,
      );
    } else {
      console.error(
        `Tool "${event.toolCall.toolName}" failed:`,
        event.toolOutput.error,
      );
    }
  }

  async onEnd(event) {
    console.log('Done. Total tokens:', event.usage.totalTokens);
  }

  async onAbort(event) {
    console.log('Stream aborted after', event.steps.length, 'finished steps');
  }

  async onError(event) {
    console.error('Generation failed:', event.callId, event.error);
  }
}

export function myIntegration(): Telemetry {
  return new MyIntegration();
}
```

For durable workflows, integrations normally receive recorded events after the
run ends. Events can include `timestamp` (Unix milliseconds); use it for span
start and end times instead of the current time. Integrations that can reconstruct
traces across processes can implement the experimental
`experimental_durable.captureContext(event)` hook. It runs in a preparation step
and returns a serializable `DurableTelemetryContext`. Live model-start events
then carry that context in `event.durableContext` with `phase: 'live'`; the
recorded operation-start event carries it with `phase: 'replay'`. Such integrations
should skip replayed model events to avoid duplicate spans. Returning `undefined`
selects live per-model operations instead, with no deferred replay to that
integration.

### Available lifecycle methods

- `onStart` (`(event: GenerateTextStartEvent) => void | PromiseLike<void>`): Called when the generation operation begins, before any LLM calls.
- `onStepStart` (`(event: GenerateTextStepStartEvent) => void | PromiseLike<void>`): Called when a step (LLM call) begins, before the provider is called.
- `onLanguageModelCallStart` (`(event: LanguageModelCallStartEvent) => void | PromiseLike<void>`): Called immediately before the provider model call begins. Useful for model-only spans or logging.
- `onLanguageModelCallEnd` (`(event: LanguageModelCallEndEvent) => void | PromiseLike<void>`): Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Includes provider-specific metadata when available.
- `onToolExecutionStart` (`(event: ToolExecutionStartEvent) => void | PromiseLike<void>`): Called when a tool's execute function is about to run.
- `onToolExecutionEnd` (`(event: ToolExecutionEndEvent) => void | PromiseLike<void>`): Called when a tool's execute function completes or errors.
- `onStepEnd` (`(event: GenerateTextStepEndEvent) => void | PromiseLike<void>`): Called when a step (LLM call) completes.
- `onStepError` (`(event: GenerateTextStepErrorEvent) => void | PromiseLike<void>`): Called when a step that started fails before it produces a step result. Use it to close step-level spans. The call then ends with \`onError\`.
- `onEmbedEnd` (`(event: EmbeddingModelCallEndEvent) => void | PromiseLike<void>`): Called when an individual embedding model call completes.
- `onRerankEnd` (`(event: RerankingModelCallEndEvent) => void | PromiseLike<void>`): Called when an individual reranking model call completes.
- `experimental_onDecideStart` (`(event: Experimental_DecideStartEvent) => void | PromiseLike<void>`): Called when an experimental decision operation begins.
- `experimental_onDecisionModelCallStart` (`(event: Experimental_DecisionModelCallStartEvent) => void | PromiseLike<void>`): Called when the logical decision model call begins. The call includes any provider retries.
- `experimental_onDecisionModelCallEnd` (`(event: Experimental_DecisionModelCallEndEvent) => void | PromiseLike<void>`): Called when the logical decision model call, including any retries, completes.
- `experimental_onDecideEnd` (`(event: Experimental_DecideEndEvent) => void | PromiseLike<void>`): Called when an experimental decision operation completes.
- `onEnd` (`(event: GenerateTextEndEvent) => void | PromiseLike<void>`): Called when the entire generation completes (all steps finished).
- `onAbort` (`(event: GenerateTextAbortEvent) => void | PromiseLike<void>`): Called when a streaming text generation operation is aborted before it completes.
- `onError` (`(event: { callId: string; error: unknown }) => void | PromiseLike<void>`): Called when an unrecoverable error occurs. The event identifies the operation and contains the thrown value.

Experimental decision events use the four `experimental_*` callbacks above.
They are not included in the stable `onStart` and `onEnd` event unions.

The event types for each method are the same as the corresponding [lifecycle callbacks](/docs/ai-sdk-core/lifecycle-callbacks). See the lifecycle callbacks documentation for the full property reference of each event.

## Collected Data

The `@ai-sdk/otel` package provides two integrations that emit different span formats.
The `OpenTelemetry` follows the [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) and is the recommended integration.
The `LegacyOpenTelemetry` integration emits legacy AI SDK-specific spans.

### GenAI Semantic Conventions

The `OpenTelemetry` emits spans that follow the [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/).
By default, attributes use the `gen_ai.*` prefix. Provider names are mapped to well-known values (e.g. `openai`, `anthropic`, `gcp.vertex_ai`).

#### Inference duration metric

For `generateText` and `streamText`, `OpenTelemetry` also records the
[`gen_ai.client.inference.duration`](https://github.com/open-telemetry/semantic-conventions-genai/blob/3fe84604a581b3d50dd5b4bd29383b462d2a5018/docs/gen-ai/client-inference.md#metric-gen_aiclientinferenceduration)
histogram in seconds, following the development GenAI semantic conventions.
Configure a global OpenTelemetry `MeterProvider` with a metric reader and exporter
to collect it. A `TracerProvider` alone does not export metrics.

Each language model call contributes one observation, including calls that fail or
are cancelled. Streaming calls record the full response duration. In a workflow
with multiple model calls, each call is recorded separately; local tool execution
time is excluded. Each attempt of a retried model stream contributes its own
observation, including failed attempts that close without a finish chunk.

The histogram includes these attributes:

- `gen_ai.operation.name`: `"chat"`
- `gen_ai.provider.name`: the normalized provider name
- `gen_ai.request.model`: the requested model ID
- `gen_ai.response.model`: the response model ID, when available
- `error.type`: the error name for thrown errors, `"AbortError"` for cancellation,
  or `"_OTHER"` when a call ends with an error finish reason or an unknown error

The default bucket boundary advice is `[0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64,
1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92]` seconds. Your OpenTelemetry SDK
configuration can override aggregation and bucket boundaries.

Setting `telemetry.isEnabled` to `false` disables these metrics for a call.
Setting `recordInputs` or `recordOutputs` to `false` does not disable duration
metrics. The `LegacyOpenTelemetry` integration does not emit this histogram.

#### generateText / streamText

For `generateText` and `streamText`, the integration records 4 types of spans:

- **`invoke_agent {modelId}`** (default root span name, `INTERNAL`): covers the full operation including all steps and tool calls. Use [`agentSpanName`](#agent-span-names) to customize the name.

  Initial attributes:

  - `gen_ai.operation.name`: `"invoke_agent"`
  - `gen_ai.provider.name`: the provider (e.g. `"openai"`, `"anthropic"`)
  - `gen_ai.request.model`: the requested model ID
  - `gen_ai.agent.name`: the `functionId` from telemetry settings
  - `gen_ai.system_instructions`: system instructions formatted as a JSON array of parts (when `recordInputs` is enabled)
  - `gen_ai.input.messages`: the input messages in [GenAI SemConv message format](#genai-message-format) (when `recordInputs` is enabled)
  - `gen_ai.request.temperature`: the temperature setting
  - `gen_ai.request.max_tokens`: the maximum output tokens
  - `gen_ai.request.top_p`: the topP setting
  - `gen_ai.request.top_k`: the topK setting
  - `gen_ai.request.frequency_penalty`: the frequency penalty
  - `gen_ai.request.presence_penalty`: the presence penalty
  - `gen_ai.request.stop_sequences`: the stop sequences
  - `gen_ai.request.seed`: the seed value

  Attributes set on finish:

  - `gen_ai.response.finish_reasons`: array of finish reasons (e.g. `["stop"]`, `["tool_call"]`)
  - `gen_ai.usage.input_tokens`: the number of input tokens used
  - `gen_ai.usage.output_tokens`: the number of output tokens used
  - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read
  - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created
  - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled)

- **`step {n}`** (step span, `INTERNAL`): one span per step, nested under the root span. `n` is the step number, starting at 1. It contains the model call and tool spans of that step.

  Initial attributes:

  - `gen_ai.operation.name`: `"agent_step"`

  `agent_step` is an AI SDK-defined custom operation name, not a well-known
  OpenTelemetry GenAI operation. It marks an orchestration step, while the
  nested `chat` span marks the model call.

- **`chat {modelId}`** (model call span, `CLIENT`): one span per LLM provider call, nested under the step span.

  Initial attributes:

  - `gen_ai.operation.name`: `"chat"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID
  - `gen_ai.request.temperature`, `gen_ai.request.max_tokens`, `gen_ai.request.top_p`, `gen_ai.request.top_k`, `gen_ai.request.frequency_penalty`, `gen_ai.request.presence_penalty`, `gen_ai.request.stop_sequences`: request parameters
  - `gen_ai.system_instructions`: instructions supplied separately from the chat history, formatted as a JSON array of parts (when `recordInputs` is enabled)
  - `gen_ai.input.messages`: the prompt messages in [GenAI SemConv message format](#genai-message-format), including system messages that are part of the chat history in their original order (when `recordInputs` is enabled)
  - `gen_ai.tool.definitions`: the tool definitions as stringified JSON (when `recordInputs` is enabled)

  Attributes set on finish:

  - `gen_ai.response.finish_reasons`: array of finish reasons
  - `gen_ai.response.id`: the response ID from the provider
  - `gen_ai.response.model`: the model that generated the response (may differ from the requested model)
  - `gen_ai.usage.input_tokens`: input tokens used in this step
  - `gen_ai.usage.output_tokens`: output tokens used in this step
  - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read
  - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created
  - `gen_ai.client.operation.duration`: provider call duration, in seconds
  - `gen_ai.client.operation.time_to_first_chunk`: time to the first streamed output chunk, in seconds (streaming calls only)
  - `gen_ai.client.operation.time_per_output_chunk`: average time between streamed output chunks, in seconds (streaming calls with multiple output chunks only)
  - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled)

- **`execute_tool {toolName}`** (tool span, `INTERNAL`): one span per tool execution, nested under the step span. See [GenAI tool call spans](#genai-tool-call-spans) for details.

#### Deprecated object APIs (generateObject / streamObject)

`generateObject` and `streamObject` are deprecated. Use `generateText` and
`streamText` with the `output` property instead.

The deprecated object APIs emit the same spans as `generateText`/`streamText`, except that there is no `step {n}` span: the `chat {modelId}` span is nested directly under the root span. The root span has these additional attributes:

- `gen_ai.output.type`: `"json"`

The `chat {modelId}` spans also include `gen_ai.output.type: "json"`, and `gen_ai.output.messages` contains the generated object as a text part.

#### embed / embedMany

For `embed` and `embedMany`, the integration records spans with `CLIENT` kind:

- **`embeddings {modelId}`** (root span): covers the full embedding operation.

  Initial attributes:

  - `gen_ai.operation.name`: `"embeddings"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID

- **`embeddings {modelId}`** (inner span): one span per provider request,
  nested under the root span. `embed` creates one inner span. `embedMany`
  creates one inner span per provider batch call.

  Initial attributes:

  - `gen_ai.operation.name`: `"embeddings"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the model ID

  Attributes set on finish:

  - `gen_ai.usage.input_tokens`: the number of tokens used

Usage is recorded only on inner provider-request spans. For `embedMany`, sum
the usage from the inner spans to get the total usage for the operation.

#### rerank

For `rerank`, the integration records spans with `CLIENT` kind:

- **`rerank {modelId}`** (root span): covers the full rerank operation.

  Initial attributes:

  - `gen_ai.operation.name`: `"rerank"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID

- **`rerank {modelId}`** (inner span): one span per provider rerank call, nested under the root span.

  Initial attributes:

  - `gen_ai.operation.name`: `"rerank"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the model ID

#### generateSpeech / transcribe / experimental\_streamTranscribe

Each call records a single `CLIENT` span named `{operationId} {modelId}`, where
`operationId` is `ai.generateSpeech`, `ai.transcribe`, or `ai.streamTranscribe`.

- `gen_ai.output.type`: `"speech"` for speech generation and `"text"` for transcription.
- `gen_ai.request.stream`: `true` for streaming transcription and `false` otherwise.
- `gen_ai.input.messages`: speech generation input text as a user message with a text part, when `recordInputs` is enabled.
- `gen_ai.output.messages`: the completed transcript as an assistant message with a text part, when `recordOutputs` is enabled. Streaming transcription records this once on completion.

The existing `ai.request.text` and `ai.response.text` attributes are also retained.
Audio byte length, media type, and output format remain in the
`ai.request.audio.*` and `ai.response.audio.*` attributes, subject to the
corresponding input/output recording controls. Raw audio is not captured, so
transcription input and speech output do not populate message attributes with
audio content or placeholder audio parts.

#### experimental\_decide

For `experimental_decide`, the integration records two spans with `CLIENT`
kind: a **`decide {modelId}`** root span for the whole operation and one
**`decide {modelId}`** child span for decision model work, including
retries. Both include the provider and requested model with
`gen_ai.operation.name: "decide"`. Token
usage is recorded on the child span. When decision attributes are enabled,
answers are recorded on both spans so the operation span contains its output.

`decide` is an AI SDK-defined custom operation name, not a well-known
OpenTelemetry GenAI operation. OpenTelemetry permits custom operation names
when no well-known value describes the operation.

OpenTelemetry's `gen_ai.evaluation.result` event describes the result of
evaluating a GenAI output for quality or other characteristics. In contrast,
`experimental_decide` can use arbitrary JSON state for classification,
routing, scoring, and similar tasks. The integration therefore does not emit a
`gen_ai.evaluation.result` event.

Decision state, questions, and answers also do not have applicable GenAI
semantic-convention attributes. Enable the `experimental_decision`
supplemental option to record them under AI SDK-specific
`ai.decision.*` attributes.

#### GenAI span details

##### GenAI message format

The `gen_ai.input.messages` and `gen_ai.output.messages` attributes follow the [OpenTelemetry GenAI Semantic Conventions message format](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).
Messages are JSON arrays of objects with a `role` and a `parts` array. Each part has a `type` and type-specific fields:

- `text`: `{ type: "text", content: "..." }`
- `reasoning`: `{ type: "reasoning", content: "..." }`
- `tool_call`: `{ type: "tool_call", id: "...", name: "...", arguments: ... }`
- `tool_call_response`: `{ type: "tool_call_response", id: "...", response: ... }`
- `blob`: `{ type: "blob", modality: "image"|"video"|"audio", mime_type: "...", content: "..." }` (base64-encoded)
- `uri`: `{ type: "uri", modality: "image"|"video"|"audio", mime_type: "...", uri: "..." }` (for URL-based files)

Output messages also include a `finish_reason` field (e.g. `"stop"`, `"tool_call"`, `"length"`, `"content_filter"`).

Instructions supplied through `instructions` (or the deprecated `system` option) are recorded separately in `gen_ai.system_instructions` as a JSON array of `{ type: "text", content: "..." }` parts. System messages supplied as chat history with `allowSystemInMessages` remain in `gen_ai.input.messages` in their original positions.

##### GenAI tool call spans

Tool call spans (`execute_tool {toolName}`) are nested under the step span and contain:

- `gen_ai.operation.name`: `"execute_tool"`
- `gen_ai.tool.name`: the name of the tool
- `gen_ai.tool.call.id`: the tool call ID
- `gen_ai.tool.type`: `"function"`
- `gen_ai.tool.call.arguments`: the input arguments (stringified JSON, when `recordInputs` is enabled)
- `gen_ai.execute_tool.duration`: the tool execution duration, in seconds
- `gen_ai.tool.call.result`: the output result (stringified JSON, when `recordOutputs` is enabled). Only set when the tool call succeeds.

### Custom OpenTelemetry span attributes

Use `enrichSpan` to add custom attributes to spans created by the
`OpenTelemetry` integration. This is useful when an observability backend needs
vendor-specific attributes that are not AI SDK-owned semantics.

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(
  new OpenTelemetry({
    enrichSpan: ({ spanType, operationId, callId, runtimeContext }) => {
      return {
        ...getCustomAttributes(runtimeContext, spanType),
        'my_app.operation_id': operationId,
        'my_app.call_id': callId,
      };
    },
  }),
);
```

The callback runs when each span is created and receives:

- `spanType`: the type of span being created (`operation`, `step`, `languageModel`, `tool`, `embedding`, `reranking`, or `experimental_decision`).
- `operationId`: the AI SDK operation ID for the current call, such as `ai.generateText` or `ai.streamText`.
- `callId`: the unique ID for the current AI SDK call.
- `runtimeContext`: the telemetry-filtered runtime context for text generation, embedding, reranking, and decision spans. Text generation spans also reflect updates from `prepareStep`.

Custom attributes are merged with AI SDK attributes on the span. AI SDK-owned
attributes take precedence when a custom attribute uses the same key.

`telemetry.includeRuntimeContext` is applied before telemetry integrations receive
`runtimeContext`, so custom span enrichment only receives top-level runtime context
properties explicitly set to `true`. If the option is omitted, no runtime context
properties are included.

Embedding and reranking operations can share the same runtime context with text
generation to attribute their spans to the same application request:

```ts
await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  runtimeContext: {
    requestId: 'req_abc',
    userId: 'user_123',
  },
  telemetry: {
    includeRuntimeContext: { requestId: true },
  },
});
```

A globally registered `OpenTelemetry({ enrichSpan })` integration receives
`{ requestId: 'req_abc' }` for the operation span and its embedding model span.
The same applies to every chunk in `embedMany` and to the operation and model
spans in `rerank`. The `onStart` and `onEnd` callbacks receive the full context.

### Supplemental AI SDK attributes on OpenTelemetry spans

The GenAI semantic conventions cover the core model, prompt, response, tool call, and usage data. You can opt into additional AI SDK-specific attributes for data that is not represented by GenAI semantics:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(
  new OpenTelemetry({
    usage: true,
    providerMetadata: true,
    runtimeContext: true,
  }),
);
```

This does not create additional AI SDK-specific spans such as `ai.generateText`, `ai.generateText.doGenerate`, or `ai.toolCall`. Instead, it adds only the selected supplemental attributes to the spans emitted by `OpenTelemetry`, such as the default root span `invoke_agent {modelId}`, `step {n}`, `chat {modelId}`, and `execute_tool {toolName}`.

All supplemental attributes are disabled by default. Enable only the data you want to collect:

```ts
registerTelemetry(
  new OpenTelemetry({
    usage: true,
    providerMetadata: true,
    embedding: true,
    reranking: true,
    experimental_decision: true,
    runtimeContext: true,
    headers: true,
    toolChoice: true,
    schema: true,
  }),
);
```

The available options are:

- `usage`: detailed usage attributes that are not covered by GenAI usage attributes, such as uncached input tokens and output text/reasoning token details.
- `providerMetadata`: `ai.response.providerMetadata` on operation, step, and model-call spans.
- `embedding`: embedding inputs and outputs.
- `reranking`: rerank input documents and ranking output.
- `experimental_decision`: experimental decision state, questions, and answers.
- `runtimeContext`: `ai.settings.context.*`.
- `headers`: `ai.request.headers.*`.
- `toolChoice`: `ai.prompt.toolChoice`.
- `schema`: object generation schema and output mode attributes.

The `recordInputs` and `recordOutputs` telemetry options are still respected for supplemental input and output attributes.

### Legacy AI SDK Spans (`LegacyOpenTelemetry`)

The `LegacyOpenTelemetry` integration emits spans using AI SDK-specific `ai.*` prefixed attributes.
This is the legacy format. Consider migrating to the `OpenTelemetry` for better compatibility with observability platforms.

#### generateText function

`generateText` records 3 types of spans:

- `ai.generateText` (span): the full length of the generateText call. It contains 1 or more `ai.generateText.doGenerate` spans.
  It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes:
  - `operation.name`: `ai.generateText` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.generateText"`
  - `ai.prompt`: the prompt that was used when calling `generateText`
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished
  - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set

- `ai.generateText.doGenerate` (span): a provider doGenerate call. It can contain `ai.toolCall` spans.
  It contains the [call LLM span information](#call-llm-span-information) and the following attributes:
  - `operation.name`: `ai.generateText.doGenerate` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.generateText.doGenerate"`
  - `ai.prompt.messages`: the messages that were passed into the provider
  - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`.
    Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema).
    Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record).
  - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property
    (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool.
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished

- `ai.toolCall` (span): a tool call that is made as part of the generateText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details.

#### streamText function

`streamText` records 3 types of spans and 2 types of events:

- `ai.streamText` (span): the full length of the streamText call. It contains a `ai.streamText.doStream` span.
  It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes:
  - `operation.name`: `ai.streamText` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.streamText"`
  - `ai.prompt`: the prompt that was used when calling `streamText`
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished
  - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set

- `ai.streamText.doStream` (span): a provider doStream call.
  This span contains an `ai.stream.firstChunk` event and `ai.toolCall` spans.
  It contains the [call LLM span information](#call-llm-span-information) and the following attributes:
  - `operation.name`: `ai.streamText.doStream` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.streamText.doStream"`
  - `ai.prompt.messages`: the messages that were passed into the provider
  - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`.
    Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema).
    Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record).
  - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property
    (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool.
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.msToFirstChunk`: the time it took to receive the first chunk in milliseconds
  - `ai.response.msToFinish`: the time it took to receive the finish part of the LLM stream in milliseconds
  - `ai.response.avgCompletionTokensPerSecond`: the average number of completion tokens per second
  - `ai.response.finishReason`: the reason why the generation finished

- `ai.toolCall` (span): a tool call that is made as part of the streamText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details.

- `ai.stream.firstChunk` (event): an event that is emitted when the first chunk of the stream is received.
  - `ai.response.msToFirstChunk`: the time it took to receive the first chunk

- `ai.stream.finish` (event): an event that is emitted when the finish part of the LLM stream is received.

#### Deprecated object APIs

`generateObject` and `streamObject` are deprecated. Use `generateText` and
`streamText` with the `output` property instead.

If you still run deprecated object APIs, you will see legacy span names:

- `generateObject`: `ai.generateObject`, `ai.generateObject.doGenerate`
- `streamObject`: `ai.streamObject`, `ai.streamObject.doStream`, `ai.stream.firstChunk`

Legacy object spans include the same core metadata as other LLM spans, plus
object-specific attributes such as `ai.schema.*`, `ai.response.object`, and
`ai.settings.output`.

#### embed function

`embed` records 2 types of spans:

- `ai.embed` (span): the full length of the embed call. It contains 1 `ai.embed.doEmbed` spans.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embed"`
  - `ai.value`: the value that was passed into the `embed` function
  - `ai.embedding`: a JSON-stringified embedding

- `ai.embed.doEmbed` (span): a provider doEmbed call.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embed.doEmbed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embed.doEmbed"`
  - `ai.values`: the values that were passed into the provider (array)
  - `ai.embeddings`: an array of JSON-stringified embeddings

#### embedMany function

`embedMany` records 2 types of spans:

- `ai.embedMany` (span): the full length of the embedMany call. It contains 1 or more `ai.embedMany.doEmbed` spans.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embedMany` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embedMany"`
  - `ai.values`: the values that were passed into the `embedMany` function
  - `ai.embeddings`: an array of JSON-stringified embedding

- `ai.embedMany.doEmbed` (span): a provider doEmbed call.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embedMany.doEmbed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embedMany.doEmbed"`
  - `ai.values`: the values that were sent to the provider
  - `ai.embeddings`: an array of JSON-stringified embeddings for each value

#### Legacy span details

##### Basic LLM span information

Many spans that use LLMs (`ai.generateText`, `ai.generateText.doGenerate`, `ai.streamText`, `ai.streamText.doStream`) contain the following attributes:

- `resource.name`: the functionId that was set through `telemetry.functionId`
- `ai.model.id`: the id of the model
- `ai.model.provider`: the provider of the model
- `ai.request.headers.*`: the request headers that were passed in through `headers`
- `ai.response.providerMetadata`: provider specific metadata returned with the generation response
- `ai.settings.maxRetries`: the maximum number of retries that were set
- `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId`
- `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option, filtered to top-level properties marked with `telemetry.includeRuntimeContext` when configured
- `ai.usage.completionTokens`: the number of completion tokens that were used
- `ai.usage.promptTokens`: the number of prompt tokens that were used

##### Call LLM span information

Spans that correspond to individual LLM calls (`ai.generateText.doGenerate`, `ai.streamText.doStream`) contain
[basic LLM span information](#basic-llm-span-information) and the following attributes:

- `ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases.
- `ai.response.id`: the id of the response. Uses the ID from the provider when available.
- `ai.response.timestamp`: the timestamp of the response. Uses the timestamp from the provider when available.
- [Semantic Conventions for GenAI operations](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/)
  - `gen_ai.system`: the provider that was used
  - `gen_ai.request.model`: the model that was requested
  - `gen_ai.request.temperature`: the temperature that was set
  - `gen_ai.request.max_tokens`: the maximum number of tokens that were set
  - `gen_ai.request.frequency_penalty`: the frequency penalty that was set
  - `gen_ai.request.presence_penalty`: the presence penalty that was set
  - `gen_ai.request.top_k`: the topK parameter value that was set
  - `gen_ai.request.top_p`: the topP parameter value that was set
  - `gen_ai.request.stop_sequences`: the stop sequences
  - `gen_ai.response.finish_reasons`: the finish reasons that were returned by the provider
  - `gen_ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases.
  - `gen_ai.response.id`: the id of the response. Uses the ID from the provider when available.
  - `gen_ai.usage.input_tokens`: the number of prompt tokens that were used
  - `gen_ai.usage.output_tokens`: the number of completion tokens that were used

##### Basic embedding span information

Many spans that use embedding models (`ai.embed`, `ai.embed.doEmbed`, `ai.embedMany`, `ai.embedMany.doEmbed`) contain the following attributes:

- `ai.model.id`: the id of the model
- `ai.model.provider`: the provider of the model
- `ai.request.headers.*`: the request headers that were passed in through `headers`
- `ai.settings.maxRetries`: the maximum number of retries that were set
- `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId`
- `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option
- `ai.usage.tokens`: the number of tokens that were used
- `resource.name`: the functionId that was set through `telemetry.functionId`

##### Legacy tool call spans

Tool call spans (`ai.toolCall`) contain the following attributes:

- `operation.name`: `"ai.toolCall"`
- `ai.operationId`: `"ai.toolCall"`
- `ai.toolCall.name`: the name of the tool
- `ai.toolCall.id`: the id of the tool call
- `ai.toolCall.args`: the input parameters of the tool call
- `ai.toolCall.result`: the output result of the tool call. Only available if the tool call is successful and the result is serializable.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)