Vercel AI SDK

Trace the Vercel AI SDK with Neatlogs (TypeScript).

Wrap the ai module with wrapAISDK to capture generateText, streamText, generateObject, streamObject, embed, embedMany, and rerank — including tool calls, token counts, and model names.

Prerequisites

npm install neatlogs@latest ai @ai-sdk/openai

Usage

import { init, shutdown } from 'neatlogs';
import { wrapAISDK } from 'neatlogs/ai';
import * as ai from 'ai';
import { openai } from '@ai-sdk/openai';

async function main() {
  await init({ apiKey: process.env.NEATLOGS_API_KEY, workflowName: 'ai-sdk-demo' });

  const { generateText } = wrapAISDK(ai);

  const { text } = await generateText({
    model: openai('gpt-4o-mini'),
    prompt: 'In one sentence, what is the Vercel AI SDK?',
  });
  console.log(text);

  await shutdown();
}

main().catch(console.error);

wrapAISDK returns the same module shape, so you destructure and call the functions exactly as before. generateText / generateObject / streamText / streamObject get a parent WORKFLOW span with LLM and TOOL spans nested under it; embed / embedMany / rerank are captured as CHAIN spans.

Note

wrapAISDK works regardless of which provider you plug into the AI SDK (OpenAI, Azure, Anthropic, Google, …) — it instruments the SDK functions, not the provider.

Sessions & end-user

Say you're building a chatbot on the Vercel AI SDK and want every turn's generateText or streamText generation to roll up under one session and point back to the person chatting. Since wrapAISDK already opens the trace root, wrap the turn in identify() and the generation picks up the session and end-user:

// Same sessionId every turn → one session; endUserId attributes it to your user.
await identify(
  { sessionId: `conv_${conversationId}`, endUserId: userId },
  async () => {
    await generateText({
      model: openai('gpt-4o-mini'),
      prompt: message,
    });
  },
);

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a sessionId. Omit it entirely — Neatlogs sets sessionId = traceId, so the run is its own single-turn session, fully attributed to the end-user. Reusing one sessionId across unrelated runs wrongly folds them into a single multi-turn conversation.

async function summarize(document, user) {
  // Standalone workflow — attribute to the end-user; do NOT pass sessionId.
  return identify(
    { endUserId: String(user.id), endUserMetadata: { plan: user.plan } },
    async () => {
      const { text } = await generateText({
        model: openai('gpt-4o-mini'),
        prompt: `Summarize:\n${document}`,
      });
      return text;
    },
  );
}

Sessions and End-User Identity walk through the complete picture.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.