OpenPanel

Vercel AI SDK

Record every model call made through the Vercel AI SDK with one middleware, for any provider it supports.

Beta

AI observability is in beta; the middleware options and the event payload may still change. See the overview for how to give feedback.

@openpanel/ai/vercel ships a middleware for wrapLanguageModel. The root of @openpanel/ai holds what every integration shares, such as withAiContext; provider wrappers for the OpenAI and Anthropic clients will arrive as their own subpaths. It sees every call to any provider the AI SDK supports, so one wrap covers OpenAI, Anthropic, Google, Bedrock, Mistral, OpenRouter and the rest, with no OpenTelemetry setup.

Installation

pnpm install @openpanel/sdk @openpanel/ai

Requires ai 7 or later.

Usage

import { OpenPanel } from '@openpanel/sdk';
import { openpanelAiMiddleware } from '@openpanel/ai/vercel';
import { wrapLanguageModel, generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const op = new OpenPanel({ clientId: 'xxx', clientSecret: 'xxx' });

const model = wrapLanguageModel({
  model: anthropic('claude-sonnet-5-5'),
  middleware: openpanelAiMiddleware({ op }),
});

await generateText({
  model,
  messages,
  providerOptions: {
    openpanel: {
      profileId: user.id,          // the same id you identify with in the web SDK
      conversationId: thread.id,   // groups this request's traces into one conversation
      name: 'inbox-summary',       // the feature this call belongs to
      groups: [user.orgId],
      properties: { plan: user.plan },
    },
  },
});

Every call becomes an ai_generation event with the model, provider, messages in and out, tools, token usage including cached and reasoning tokens, latency, time to first token on streams, the finish reason, and errors. A failed call is recorded with its error and then rethrown, so your own error handling is untouched. Sending never blocks the model call.

One trace per request

An agent loop with tools runs the model several times. Wrap the request in withAiContext and every call inside it shares one trace and the same identity, without repeating providerOptions:

import { withAiContext } from '@openpanel/ai';

await withAiContext(
  { profileId: user.id, conversationId: thread.id, name: 'support-agent', groups: [user.orgId] },
  () => runAgent()
);

Nested contexts merge over the outer one, so a step can override name while keeping the user and the trace.

Options

OptionWhereWhat it does
opmiddlewareThe OpenPanel client that sends the events.
privacyModemiddleware or per callNever send prompts, completions or tool definitions. Tokens, cost and latency are still recorded.
providermiddlewareReported instead of the model's provider id, for example openrouter behind a gateway.
defaultsmiddlewareContext applied to calls that have no withAiContext and no providerOptions.openpanel.
profileId, groups, deviceIdper callWho the call belongs to. deviceId binds it to a web session's device (the web SDK's getDeviceId()).
traceId, parentId, conversationId, name, propertiesper callWhere the call sits and what it is.

Linking to sessions

A logged-in user's server-side model call lands in the same session as their page views, because OpenPanel resolves a server event to the profile's live session. For anonymous users, forward the web SDK's device id to your backend and pass it as deviceId.

On this page