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, consume the stream inside the wrapped function and call onComplete once all chunks have been collected:
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,
});
}
);
const { result, runId } = await wrappedFn({ userMessage });
return { result, runId };
};
Next Steps