Skip to main content
RunAgain ingests OpenTelemetry. To see the model, tokens, cost, and prompt/response from your Vercel AI SDK calls, you need two things: an OTLP exporter pointed at RunAgain, and — on AI SDK v7+ — the @runagain/ai-sdk telemetry bridge.
Why the bridge? AI SDK v5 and earlier wrote OpenTelemetry spans directly when experimental_telemetry was enabled. v7 changed this: the SDK no longer emits OTel spans itself — it publishes telemetry events to an in-process channel and expects a registered telemetry integration to turn them into spans. @runagain/ai-sdk is that integration; it converts each call into a gen_ai.* span RunAgain reads. Without it (on v7) you get a trace with only HTTP spans and no model/tokens/content.

Install

@opentelemetry/api is a peer of both @vercel/otel and @runagain/ai-sdk — install it explicitly (Vercel’s own setup does too).

Configure

.env.local

Register the exporter and the bridge

instrumentation.ts
Not on Next.js / @vercel/otel? In a plain Node service, skip registerOTel and call initRunAgain() from runagain once at startup — it registers the tracer provider and the RunAgain exporter for you (reads RUNAGAIN_API_KEY / RUNAGAIN_INGEST_URL). Still call registerTelemetry(runAgainAiTelemetry) on AI SDK v7. runAgainAttributes / initRunMetadata are exported from runagain as well as @runagain/ai-sdk.
In a serverless handler, spans are batched on a ~2s timer — await ra.flush() before you return the response (or wrap it in the platform’s waitUntil) so the function doesn’t freeze before they’re sent. @vercel/otel handles this for you, so it’s only the plain-initRunAgain path that needs the explicit flush.
Run your AI routes on the Node runtime, not Edge. The bridge emits custom OpenTelemetry spans, and per Vercel, “custom spans from functions using the Edge runtime are not supported.” On Edge the spans silently never appear (no error) and you’re back to HTTP-only traces. Next.js route handlers default to Node; just don’t set export const runtime = "edge" on routes that make AI calls.
The AI Gateway / provider call is also picked up by @vercel/otel’s fetch auto-instrumentation as a separate fetch POST … trace. Now that the gen_ai.* spans carry the real data, you can silence that noise by ignoring the host:
instrumentation.ts

Enable telemetry on your calls

app/api/chat/route.ts
functionId is surfaced as gen_ai.agent.name on the model/embedding span, so you can attribute individual calls to a named agent. (Session/user/tags identity is separate — attach it with runAgainAttributes(...), below.)
embed / embedMany work the same way — opt each call in and you’ll get embeddings spans with token usage.

Attach identity & custom metadata

Rather than hand-typing OTel attribute keys, @runagain/ai-sdk ships typed helpers that emit the canonical keys RunAgain normalizes — session, user, agent, environment, version, release, tags, and a free-form metadata bag.
Not on the Vercel AI SDK? setRunMetadata({ session, user }) writes the same attributes onto the active OpenTelemetry span, and runAgainAttributes(...) returns a plain attribute map you can pass to span.setAttributes(...). All fields are optional — only what you set is sent.

Run

You should see chat <model> and embeddings <model> spans (scope @runagain/ai-sdk) with model, tokens, and — when recording is on — the prompt and response.
Failed and aborted calls show up as errors, not gaps. When a model call throws or a stream is aborted, the AI SDK fires no completion event — the bridge closes the span from the SDK’s error/abort callbacks instead, marks it as an error, and records the exception. So a rate limit or timeout appears as a failed span rather than silently disappearing. (A single embedMany failure closes every parallel embedding span under that call.)

Troubleshooting

Nothing arrives at all? Export failures — a bad key (401), a wrong RUNAGAIN_INGEST_URL, a blocked network — are silent by default. Set RUNAGAIN_LOG_LEVEL=error (or the standard OTEL_LOG_LEVEL) to print OpenTelemetry diagnostics, including the failing export, to your console.
Traces show up, but model, tokens, and cost are all / $0.0000.Your spans are named like POST /api/chat or fetch POST https://…/v1/… and carry no model. That’s @vercel/otel’s HTTP auto-instrumentation — the AI SDK’s own telemetry never became spans.On AI SDK v7+ this means the telemetry bridge isn’t registered. Setting experimental_telemetry: { isEnabled: true } alone is not enough on v7 (unlike v5) — the SDK emits events to an in-process channel, not OTel. Install @runagain/ai-sdk and add registerTelemetry(runAgainAiTelemetry) in instrumentation.ts (see above). Keep isEnabled: true on each call so the events fire.
Output shows unknown / empty, but the input is captured.Something is emitting gen_ai.output.messages as bare content parts with no role. RunAgain labels a message’s author from its role, so roleless content reads as unknown. @runagain/ai-sdk tags the response with role: "assistant" for you — upgrade to the current version if you hand-rolled the bridge.
Deployed on Vercel? The exporter and bridge run anywhere Node/Edge OpenTelemetry runs — set RUNAGAIN_API_KEY and RUNAGAIN_INGEST_URL as project environment variables.