divergent/docs

This guide sends traces from a Node.js application that uses the Vercel AI SDK. Each generateText, streamText or agent call becomes a trace, with a span for each model call and tool call.

Before you start

You need an API key with the traces:write scope. Create one under Settings in the dashboard, or run div login.

Install the OpenTelemetry SDK and the OTLP exporter:

npm install @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources

Set the exporter’s endpoint and key:

export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.divergentlabs.xyz/api/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20$DIVERGENT_API_KEY"
export OTEL_SERVICE_NAME=my-app

Register a tracer provider at startup, before any AI SDK call:

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'

const provider = new NodeTracerProvider({
  spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
})
provider.register()

In a Next.js app, registerOTel from @vercel/otel in instrumentation.ts does the same.

AI SDK 7

  1. Install the AI SDK’s OpenTelemetry integration.

    npm install @ai-sdk/otel
  2. Register it once, after the tracer provider. Every AI SDK call is then traced.

    import { registerTelemetry } from 'ai'
    import { OpenTelemetry } from '@ai-sdk/otel'
    
    registerTelemetry(new OpenTelemetry())
  3. Name the call with functionId. The trace takes the name of its first span, invoke_agent <model>, and functionId is kept as gen_ai.agent.name.

    const result = await generateText({
      model,
      prompt: 'What is the weather in Amsterdam?',
      telemetry: { functionId: 'answer-weather' },
    })

To filter traces by session or user, run the call inside a span of your own that sets session.id and user.id. The AI SDK’s spans become its children:

import { trace } from '@opentelemetry/api'

await trace.getTracer('my-app').startActiveSpan('answer question', async (span) => {
  span.setAttributes({ 'session.id': sessionId, 'user.id': userId })
  await generateText({ model, prompt })
  span.end()
})

AI SDK 5 and 6

Enable telemetry on each call. metadata.sessionId and metadata.userId become the trace’s session and user:

const result = await generateText({
  model,
  prompt: 'What is the weather in Amsterdam?',
  experimental_telemetry: {
    isEnabled: true,
    functionId: 'answer-weather',
    metadata: { sessionId, userId },
  },
})

AI SDK 7’s LegacyOpenTelemetry integration writes the same attributes as AI SDK 5 and 6. Tracing reads both.

Keep content out of traces

Set recordInputs: false and recordOutputs: false in telemetry (AI SDK 7) or experimental_telemetry (AI SDK 5 and 6). Spans then carry the model, tokens and timings, and no prompts or completions.

AI SDK 5, 6 and 7’s LegacyOpenTelemetry record the headers passed to a call. Tracing drops them before a span is stored, except content-type, content-length and user-agent.

Tokens

An AI SDK trace has an outer span for the call and a span for each model call inside it, and the outer span repeats the usage of the calls inside it. Tracing counts a trace’s tokens from the model calls only, so a call that made two requests of 21 input tokens shows 42, not 84.

esc