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();
}
}
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