> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselemma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel AI SDK

> Instrument a Vercel AI SDK agent so each run is one nested trace in Lemma

The [Vercel AI SDK](https://sdk.vercel.dev) emits OpenTelemetry spans through `experimental_telemetry`. Those spans carry the exact attributes Lemma reads, so once you point Langfuse at Lemma and wrap each agent run in one root span, you get a complete nested trace: a root with input/output, generations for every model call, and tool spans for every tool.

<Note>
  **One agent execution = one trace.** Wrap a full multi-step run in a single root span so every model and tool call nests under it. See the [trace contract](/reference/trace-contract).
</Note>

Vercel AI SDK telemetry is a **fully supported shape** for Lemma's automated [issue detection](/reference/good-vs-bad-traces) (silent failures, bad tool calls, loops) today — no extra work beyond enabling `experimental_telemetry`.

## Recipe

<Steps>
  <Step title="Install">
    ```bash theme={null}
    npm install ai @langfuse/tracing @langfuse/otel @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto
    ```
  </Step>

  <Step title="Register the Langfuse → Lemma exporter">
    Register the exporter once, before any agent or model runs. This is the same setup used across the docs — see [Setup](/tracing/instrumentation/setup).

    ```typescript theme={null}
    // instrumentation.ts — imported first, before your app code
    import { LangfuseSpanProcessor } from "@langfuse/otel";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";

    export const lemmaProcessor = new LangfuseSpanProcessor({
      exporter: new OTLPTraceExporter({
        url: process.env.LEMMA_BASE_URL,
        headers: {
          Authorization: `Bearer ${process.env.LEMMA_API_KEY}`,
          "X-Lemma-Project-ID": process.env.LEMMA_PROJECT_ID,
        },
      }),
    });

    new NodeTracerProvider({ spanProcessors: [lemmaProcessor] }).register();
    ```

    Set the environment variables. Lemma-only export needs no `LANGFUSE_*` credentials.

    ```bash theme={null}
    export LEMMA_BASE_URL="https://api.uselemma.ai/otel/v1/traces"
    export LEMMA_API_KEY="lma_..."
    export LEMMA_PROJECT_ID="proj_..."
    ```
  </Step>

  <Step title="Enable AI SDK telemetry">
    Turn on `experimental_telemetry` for every `generateText` / `streamText` / `generateObject` call. The AI SDK natively emits the model, token-usage, and tool-call attributes Lemma reads. Set `gen_ai.agent.name` so traces group by workflow.

    ```typescript theme={null}
    import { generateText } from "ai";

    const result = await generateText({
      model: "openai/gpt-4o",
      messages,
      tools,
      experimental_telemetry: {
        isEnabled: true,
        functionId: "support-agent",
        metadata: {
          "gen_ai.agent.name": "support-agent",
          "lemma.thread_id": threadId,
        },
      },
    });
    ```

    By semantic convention, use `snake_case`, `CamelCase`, or `kebab-case` for `gen_ai.agent.name` (for example `support_agent`, `SupportAgent`, or `support-agent`).
  </Step>

  <Step title="Wrap the whole run in one root span">
    A multi-step agent makes several model and tool calls. Wrap the entire run in a single root span so all of them nest under one trace instead of arriving as separate traces.

    ```typescript theme={null}
    import { propagateAttributes, startActiveObservation } from "@langfuse/tracing";
    import { generateText, stepCountIs } from "ai";

    export async function runSupportAgent(userMessage: string, threadId: string) {
      return await startActiveObservation("support-agent", async (root) => {
        root.update({ input: userMessage });

        return await propagateAttributes(
          {
            traceName: "support-agent",
            sessionId: threadId,
            metadata: { "gen_ai.agent.name": "support-agent" },
          },
          async () => {
            const result = await generateText({
              model: "openai/gpt-4o",
              messages: [{ role: "user", content: userMessage }],
              tools,
              stopWhen: stepCountIs(8),
              experimental_telemetry: {
                isEnabled: true,
                functionId: "support-agent",
                metadata: {
                  "gen_ai.agent.name": "support-agent",
                  "lemma.thread_id": threadId,
                },
              },
            });

            root.update({ output: result.text });
            return result.text;
          },
        );
      });
    }
    ```

    The AI SDK spans created inside the callback automatically become children of the root, producing one nested trace:

    ```text theme={null}
    support-agent              ← trace root (input, output)
    ├─ generateText            ← generation (model, tokens)
    ├─ search_docs             ← tool call (args, result)
    └─ generateText            ← generation (final answer)
    ```
  </Step>

  <Step title="Flush before the process exits">
    In serverless or other short-lived runtimes, flush so the whole trace ships in one batch.

    ```typescript theme={null}
    import { lemmaProcessor } from "./instrumentation";

    // at the end of a request / serverless handler
    await lemmaProcessor.forceFlush();
    ```
  </Step>
</Steps>

<Warning>
  If model or tool calls show up as their own separate traces, the work ran outside the root's active context — usually a lost async context across a queue, worker, or stream. Keep the `generateText` call inside the `startActiveObservation` callback. See [Troubleshooting](/tracing/troubleshooting/common-problems#every-span-appears-as-a-separate-trace).
</Warning>

## Verify in Lemma

Open the [Lemma dashboard](https://platform.uselemma.ai) → **Traces** and confirm:

* **One trace per run** — a single multi-step execution is one trace, not one per model call.
* **Root has input and output** — the root span shows the user message and the final response.
* **Generations are nested** — each model call appears as a child generation with model and token usage.
* **Tools are nested** — each tool invocation appears as a child tool span with arguments and result.

## Next steps

<CardGroup cols={2}>
  <Card title="Trace contract" icon="file-check" href="/reference/trace-contract">
    The exact shape Lemma reads.
  </Card>

  <Card title="Setup" icon="plug" href="/tracing/instrumentation/setup">
    Wire the Langfuse → Lemma exporter.
  </Card>

  <Card title="Threads and sessions" icon="users" href="/tracing/instrumentation/context">
    Group multi-turn conversations with a thread id.
  </Card>

  <Card title="Good vs bad traces" icon="circle-check" href="/reference/good-vs-bad-traces">
    What issue detection looks for, per shape.
  </Card>
</CardGroup>
