Skip to main content
The Vercel AI SDK includes built-in telemetry support that works seamlessly with Lemma’s OpenTelemetry-based tracing. This guide shows you how to set up tracing for your AI SDK applications.

How It Works

Once the tracer provider is registered, the AI SDK automatically captures spans for all generateText and streamText calls with telemetry enabled. Lemma receives:
  • Model calls and responses
  • Token usage (prompt tokens, completion tokens)
  • Tool invocations
  • Streaming events
  • Timing and latency

Getting Started

Install Dependencies

npm install @uselemma/tracing

Set Up the Tracer Provider

Create an instrumentation.ts (or instrumentation.js) file in your project root. Next.js will automatically run this code when your application starts:
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerOTel } = await import('@uselemma/tracing');
    registerOTel();
  }
}
Make sure to enable instrumentation in your next.config.js:
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

Node.js (Non-Next.js)

Create a tracer setup file and import it at the top of your entry point:
// tracer.ts
import { registerOTel } from '@uselemma/tracing';
registerOTel();
Then import it at the top of your application entry point:
// index.ts or server.ts
import './tracer'; // Must be first!
// ... rest of your imports

Options

registerOTel reads from LEMMA_API_KEY and LEMMA_PROJECT_ID environment variables by default. You can also pass options explicitly:
registerOTel({
  apiKey: 'sk_...',
  projectId: '...',
  baseUrl: 'https://api.uselemma.ai', // default
});
Set the LEMMA_API_KEY and LEMMA_PROJECT_ID environment variables in your application. You can find these in your Lemma project settings.

Enable Telemetry in AI SDK

To capture traces from the Vercel AI SDK, enable telemetry in your streamText or generateText calls:
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = await streamText({
  model: anthropic('claude-sonnet-4'),
  messages: [
    { role: 'user', content: 'What is the capital of France?' }
  ],
  experimental_telemetry: {
    isEnabled: true, // required
  },
});

Examples

Non-Streaming

import { wrapAgent } from "@uselemma/tracing";
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

export const callAgent = async (userMessage: string) => {
  const wrappedFn = wrapAgent(
    "my-agent",
    async ({ onComplete }, input) => {
      const result = await generateText({
        model: anthropic('claude-sonnet-4'),
        messages: [{ role: 'user', content: input.userMessage }],
        experimental_telemetry: {
          isEnabled: true,
        },
      });

      onComplete(result.text);
      return result.text;
    }
  );

  const { result, runId } = await wrappedFn({ userMessage });
  return { result, runId };
};

Streaming

For streaming responses, set autoEndRoot: true so the RunBatchSpanProcessor automatically ends the root span when all direct child spans have finished. Call onComplete to record the output once the stream finishes:
import { wrapAgent } from "@uselemma/tracing";
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

export const callAgent = async (userMessage: string) => {
  const wrappedFn = wrapAgent(
    "my-agent",
    async ({ onComplete, recordError }, input) => {
      return streamText({
        model: anthropic('claude-sonnet-4'),
        messages: [{ role: 'user', content: input.userMessage }],
        experimental_telemetry: {
          isEnabled: true,
        },
        onFinish: (result) => {
          onComplete(result);
        },
        onError: recordError,
      });
    },
    { autoEndRoot: true }
  );

  const { result, runId } = await wrappedFn({ userMessage });
  return { result, runId };
};

Next Steps