Musterby Elitery
Integrations

Vercel AI SDK

Trace Vercel AI SDK calls in Muster via OpenTelemetry, capturing generateText, streamText, generateObject, and tool calls.

This guide explains how to integrate Muster with the Vercel AI SDK to monitor and debug LLM-powered applications.

The integration leverages OpenTelemetry, an observability standard. When you enable telemetry on the AI SDK and add the LangfuseSpanProcessor, your AI calls automatically flow into Muster for analysis.

Integration Steps

1. Install Dependencies

npm install ai
npm install @ai-sdk/openai
npm install @langfuse/tracing @langfuse/otel @opentelemetry/sdk-node

Any Vercel AI SDK provider (Anthropic, Google, Mistral, etc.) works with this integration.

2. Configure Environment Variables

LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_BASE_URL="https://app.getmuster.io"   # or your self-hosted Muster URL
OPENAI_API_KEY="sk-proj-..."

API keys come from your Muster project's Settings → API Keys page.

3. Initialize OpenTelemetry with LangfuseSpanProcessor

import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";

const sdk = new NodeSDK({
  spanProcessors: [new LangfuseSpanProcessor()],
});

sdk.start();

4. Enable Telemetry on AI SDK Calls

import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const { text } = await generateText({
  model: openai("gpt-5.1"),
  prompt: "What is the weather like today in San Francisco?",
  tools: {
    getWeather: tool({
      description: "Get the weather in a location",
      inputSchema: z.object({
        location: z.string().describe("The location to get the weather for"),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  experimental_telemetry: { isEnabled: true },
});

Works with generateText, streamText, generateObject, and tool calls.

5. View Traces in Muster

After execution, traces appear in your Muster dashboard showing complete model calls, tool usage, and latency metrics.

Combining with Prompt Management

Link prompts to traces by passing prompt metadata:

import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { LangfuseClient } from "@langfuse/client";

const langfuse = new LangfuseClient();
const prompt = await langfuse.prompt.get("movie-critic");

const compiledPrompt = prompt.compile({
  someVariable: "example-variable",
});

const { text } = await generateText({
  model: openai("gpt-5"),
  prompt: compiledPrompt,
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      langfusePrompt: prompt.toJSON(),
    },
  },
});

Next.js Setup

For production Next.js applications, create an instrumentation.ts file:

// instrumentation.ts

import { LangfuseSpanProcessor } from "@langfuse/otel";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";

export const langfuseSpanProcessor = new LangfuseSpanProcessor();

const tracerProvider = new NodeTracerProvider({
  spanProcessors: [langfuseSpanProcessor],
});

tracerProvider.register();

Important: Use manual OpenTelemetry setup via NodeTracerProvider rather than @vercel/otel — the latter does not yet support OpenTelemetry JS SDK v2.

Example Chat API Route

// app/api/chat/route.ts

import { streamText } from "ai";
import { after } from "next/server";
import { openai } from "@ai-sdk/openai";
import {
  observe,
  propagateAttributes,
  setActiveTraceIO,
} from "@langfuse/tracing";
import { trace } from "@opentelemetry/api";
import { langfuseSpanProcessor } from "@/src/instrumentation";

const handler = async (req: Request) => {
  const { messages, chatId, userId } = await req.json();

  setActiveTraceIO({
    input: messages[messages.length - 1].parts.find(
      (part) => part.type === "text"
    )?.text,
  });

  await propagateAttributes(
    {
      traceName: "chat-message",
      sessionId: chatId,
      userId,
    },
    async () => {
      const result = streamText({
        model: openai("gpt-5.1"),
        messages,
        experimental_telemetry: {
          isEnabled: true,
        },
        onFinish: async (result) => {
          setActiveTraceIO({
            output: result.content,
          });
          trace.getActiveSpan().end();
        },
        onError: async (error) => {
          setActiveTraceIO({
            output: error,
          });
          trace.getActiveSpan()?.end();
        },
      });

      after(async () => await langfuseSpanProcessor.forceFlush());
      return result.toUIMessageStreamResponse();
    }
  );
};

export const POST = observe(handler, {
  name: "handle-chat-message",
  endOnExit: false,
});

Key points:

  • observe() creates a Muster trace around the handler.
  • propagateAttributes() adds session and user metadata.
  • forceFlush() ensures traces are sent before serverless termination.
  • endOnExit: false keeps the observation open until streaming completes.

Using With Other Observability Tools

The Vercel AI SDK uses OpenTelemetry under the instrumentation scope ai. When using Sentry, Datadog, or other OTel-based tools, additional configuration may be needed. See Using Langfuse with an Existing OpenTelemetry Setup upstream.

Interoperability with the JS/TS SDK

Context Manager Approach

import { startActiveObservation, propagateAttributes } from "@langfuse/tracing";

await startActiveObservation("context-manager", async (span) => {
  span.update({
    input: { query: "What is the capital of France?" },
  });

  await propagateAttributes(
    {
      userId: "user-123",
      sessionId: "session-123",
      metadata: {
        source: "api",
        region: "us-east-1",
      },
      tags: ["api", "user"],
      version: "1.0.0",
    },
    async () => {
      const { text } = await generateText({
        model: openai("gpt-5"),
        prompt: "What is the capital of France?",
        experimental_telemetry: { isEnabled: true },
      });
    }
  );
  span.update({ output: "Paris" });
});

observe Wrapper Approach

import { observe, propagateAttributes } from "@langfuse/tracing";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

const processUserRequest = observe(
  async (userQuery: string) => {
    return await propagateAttributes(
      {
        userId: "user-123",
        sessionId: "session-123",
        metadata: {
          source: "api",
          region: "us-east-1",
        },
        tags: ["api", "user"],
        version: "1.0.0",
      },
      async () => {
        const { text } = await generateText({
          model: openai("gpt-5"),
          prompt: userQuery,
          experimental_telemetry: { isEnabled: true },
        });
        return text;
      }
    );
  },
  { name: "process-user-request" }
);

const result = await processUserRequest("some query");

Troubleshooting

No Traces Appearing

Enable debug mode:

export LANGFUSE_LOG_LEVEL="DEBUG"

If OTel spans appear in the logs but traces do not reach Muster:

  1. Call forceFlush() at application end (critical for serverless).
  2. Verify API keys and base URL.

If no OTel spans appear at all, instrumentation is likely not running before your application code.

Unwanted Observations

Other libraries may emit irrelevant OTel spans. Filter them out to avoid billable-unit charges.

Missing Attributes

Some attributes are stored in metadata rather than mapped Muster fields. Raise an issue with your operator if mappings do not work as expected.

See also