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
Next.js (Recommended)
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();
}
}
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 { agent } from "@uselemma/tracing";
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export const callAgent = async (userMessage: string) => {
const wrappedFn = agent(
"my-agent",
async (input) => {
const { text } = await generateText({
model: anthropic('claude-sonnet-4'),
messages: [{ role: 'user', content: input.userMessage }],
experimental_telemetry: { isEnabled: true },
});
return text; // wrapper auto-captures output and closes the span
}
);
const { result, runId } = await wrappedFn({ userMessage });
return { result, runId };
};
Streaming
Pass { streaming: true } to opt into manual span lifecycle, then call ctx.complete() inside the onFinish callback once the full output is assembled:
import { agent } from "@uselemma/tracing";
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export const callAgent = async (userMessage: string) => {
const wrappedFn = agent(
"my-agent",
async (input, ctx) => {
const result = await streamText({
model: anthropic('claude-sonnet-4'),
messages: [{ role: 'user', content: input.userMessage }],
experimental_telemetry: { isEnabled: true },
onFinish({ text }) {
ctx.complete(text); // closes the run span once the full output is known
},
});
return result.toDataStreamResponse();
},
{ streaming: true }
);
const { result, runId } = await wrappedFn({ userMessage });
return { result, runId };
};
Next Steps