Vercel AI SDK로 트레이싱하기

Vercel AI SDK로 트레이싱하기 (Legacy)

OpenTelemetry(OTEL)를 사용해 Vercel AI SDK 실행을 LangSmith로 트레이싱하는 방법을 알려드릴게요.

경고: 이 페이지는 AI SDK 실행을 트레이싱하는 이전 방식을 문서화합니다. OTEL 설정이 필요 없는 더 간단하고 일반적인 방법은 새 가이드를 참고하세요.

참고: JavaScript의 많은 인기 있는 OpenTelemetry 구현은 현재 실험적이며, 특히 LangSmith를 다른 프로바이더와 함께 계측할 때 프로덕션에서 불규칙하게 동작할 수 있습니다. AI SDK 5를 사용한다면 AI SDK 실행을 트레이싱하는 권장 접근 방식을 사용하는 것을 강력히 권장합니다.

출처: 문서

본문

0. 설치

Vercel AI SDK와 필요한 OTEL 패키지를 설치합니다. 아래 코드 스니펫에서는 OpenAI 통합을 사용하지만, 그들의 다른 옵션도 사용할 수 있습니다.

npm install ai @ai-sdk/openai zod
yarn add ai @ai-sdk/openai zod
pnpm add ai @ai-sdk/openai zod
npm install @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/context-async-hooks
yarn add @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/context-async-hooks
pnpm add @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/context-async-hooks

1. 환경 구성하기

export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=<your-api-key>
export LANGSMITH_OTEL_ENABLED=true

# This example uses OpenAI, but you can use any LLM provider of choice
export OPENAI_API_KEY=<your-openai-api-key>

2. 트레이스 기록하기

Node.js

트레이싱을 시작하려면 코드 시작 부분에서 initializeOTEL 메서드를 import하고 호출해야 합니다:

import { initializeOTEL } from "langsmith/experimental/otel/setup";

const { DEFAULT_LANGSMITH_SPAN_PROCESSOR } = initializeOTEL();

그런 다음 트레이싱하려는 AI SDK 호출에 experimental_telemetry 인자를 추가합니다.

정보: 남은 트레이스를 LangSmith로 플러시하려면 애플리케이션이 종료되기 전에 await DEFAULT_LANGSMITH_SPAN_PROCESSOR.shutdown();을 호출하는 것을 잊지 마세요.

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

let result;
try {
  result = await generateText({
    model: openai("gpt-5.4-nano"),
    prompt: "Write a vegetarian lasagna recipe for 4 people.",
    experimental_telemetry: {
      isEnabled: true,
    },
  });
} finally {
  await DEFAULT_LANGSMITH_SPAN_PROCESSOR.shutdown();
}

LangSmith 대시보드에서 이런 트레이스를 볼 수 있습니다.

도구 호출이 있는 실행도 트레이싱할 수 있습니다:

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

await generateText({
  model: openai("gpt-5.4-nano"),
  messages: [
    {
      role: "user",
      content: "What are my orders and where are they? My user ID is 123",
    },
  ],
  tools: {
    listOrders: tool({
      description: "list all orders",
      parameters: z.object({ userId: z.string() }),
      execute: async ({ userId }) =>
        `User ${userId} has the following orders: 1`,
    }),
    viewTrackingInformation: tool({
      description: "view tracking information for a specific order",
      parameters: z.object({ orderId: z.string() }),
      execute: async ({ orderId }) =>
        `Here is the tracking information for ${orderId}`,
    }),
  },
  experimental_telemetry: {
    isEnabled: true,
  },
  maxSteps: 10,
});

결과는 이런 트레이스입니다.

traceable 사용

AI SDK 도구 호출 주변이나 내부에 traceable 호출을 래핑할 수 있습니다. 그렇게 하려면 각 traceable에 전달하는 LangSmith client 인스턴스를 초기화한 다음, 모든 트레이스가 플러시되도록 client.awaitPendingTraceBatches();를 호출할 것을 권장합니다. 이렇게 하면 DEFAULT_LANGSMITH_SPAN_PROCESSOR에서 shutdown()이나 forceFlush()를 수동으로 호출할 필요가 없습니다. 예:

import { initializeOTEL } from "langsmith/experimental/otel/setup";

initializeOTEL();

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

const client = new Client();

const wrappedText = traceable(
  async (content: string) => {
    const { text } = await generateText({
      model: openai("gpt-5.4-nano"),
      messages: [{ role: "user", content }],
      tools: {
        listOrders: tool({
          description: "list all orders",
          parameters: z.object({ userId: z.string() }),
          execute: async ({ userId }) => {
            const getOrderNumber = traceable(
              async () => {
                return "1234";
              },
              { name: "getOrderNumber" }
            );
            const orderNumber = await getOrderNumber();
            return `User ${userId} has the following order: ${orderNumber}`;
          },
        }),
      },
      experimental_telemetry: {
        isEnabled: true,
      },
      maxSteps: 10,
    });
    return { text };
  },
  { name: "parentTraceable", client }
);

let result;
try {
  result = await wrappedText("What are my orders?");
} finally {
  await client.awaitPendingTraceBatches();
}

결과 트레이스는 이렇게 보입니다.

Next.js

먼저 @vercel/otel 패키지를 설치합니다:

npm install @vercel/otel
yarn add @vercel/otel
pnpm add @vercel/otel

그런 다음 루트 디렉터리에 instrumentation.ts 파일을 설정합니다. initializeOTEL을 호출하고 결과 DEFAULT_LANGSMITH_SPAN_PROCESSORregisterOTEL(...) 호출의 spanProcessors 필드로 전달합니다. 대략 이렇게:

import { registerOTel } from "@vercel/otel";
import { initializeOTEL } from "langsmith/experimental/otel/setup";

const { DEFAULT_LANGSMITH_SPAN_PROCESSOR } = initializeOTEL({});

export function register() {
  registerOTel({
    serviceName: "your-project-name",
    spanProcessors: [DEFAULT_LANGSMITH_SPAN_PROCESSOR],
  });
}

마지막으로 API 라우트에서도 initializeOTEL을 호출하고 AI SDK 호출에 experimental_telemetry 필드를 추가합니다:

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

import { initializeOTEL } from "langsmith/experimental/otel/setup";

initializeOTEL();

export async function GET() {
  const { text } = await generateText({
    model: openai("gpt-5.4-nano"),
    messages: [{ role: "user", content: "Why is the sky blue?" }],
    experimental_telemetry: {
      isEnabled: true,
    },
  });

  return new Response(text);
}

더 세밀한 제어를 위해 코드의 일부를 traceable로 래핑할 수도 있습니다.

Sentry

Sentry를 사용한다면 아래 예제처럼 LangSmith 트레이스 exporter를 Sentry의 기본 OpenTelemetry 계측에 연결할 수 있습니다.

경고: 작성 시점에 Sentry는 OTEL v1 패키지만 지원합니다. LangSmith는 v1과 v2를 모두 지원하지만, 계측이 동작하도록 반드시 OTEL v1 패키지를 설치해야 합니다.

npm install @opentelemetry/[email protected] @opentelemetry/[email protected] @opentelemetry/[email protected]
yarn add @opentelemetry/[email protected] @opentelemetry/[email protected] @opentelemetry/[email protected]
pnpm add @opentelemetry/[email protected] @opentelemetry/[email protected] @opentelemetry/[email protected]
import { initializeOTEL } from "langsmith/experimental/otel/setup";
import { LangSmithOTLPTraceExporter } from "langsmith/experimental/otel/exporter";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { traceable } from "langsmith/traceable";
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import * as Sentry from "@sentry/node";
import { Client } from "langsmith";

const exporter = new LangSmithOTLPTraceExporter();
const spanProcessor = new BatchSpanProcessor(exporter);

const sentry = Sentry.init({
  dsn: "...",
  tracesSampleRate: 1.0,
  openTelemetrySpanProcessors: [spanProcessor],
});

initializeOTEL({
  globalTracerProvider: sentry?.traceProvider,
});

const wrappedText = traceable(
  async (content: string) => {
    const { text } = await generateText({
      model: openai("gpt-5.4-nano"),
      messages: [{ role: "user", content }],
      experimental_telemetry: {
        isEnabled: true,
      },
      maxSteps: 10,
    });
    return { text };
  },
  { name: "parentTraceable" }
);

let result;
try {
  result = await wrappedText("What color is the sky?");
} finally {
  await sentry?.traceProvider?.shutdown();
}

기타 메타데이터 추가하기

LangSmith UI에서 조직화·필터링에 도움이 되도록 트레이스에 기타 메타데이터를 추가할 수 있습니다:

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

await generateText({
  model: openai("gpt-5.4-nano"),
  prompt: "Write a vegetarian lasagna recipe for 4 people.",
  experimental_telemetry: {
    isEnabled: true,
    metadata: { userId: "123", language: "english" },
  },
});

메타데이터는 LangSmith 대시보드에 표시되며 특정 트레이스를 필터링·검색하는 데 사용할 수 있습니다. AI SDK는 내부 하위 span에도 메타데이터를 전파한다는 점을 유의하세요.

실행 이름 커스터마이즈

experimental_telemetryls_run_name이라는 메타데이터 키를 전달해 실행 이름을 커스터마이즈할 수 있습니다.

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

await generateText({
  model: openai("gpt-5.4-mini"),
  prompt: "Write a vegetarian lasagna recipe for 4 people.",
  experimental_telemetry: {
    isEnabled: true,
    metadata: {
      ls_run_name: "my-custom-run-name",
    },
  },
});

더 알아보기