Laminar Observability

Laminar Observability

Laminar은 AI 에이전트를 위해 특별히 설계된 오픈소스, Otel 네이티브 관측성 플랫폼이에요.

Laminar의 기능:

이 가이드의 한 버전은 Laminar 문서에서 볼 수 있어요.

출처: 문서

본문

설정

코딩 에이전트를 사용해 Laminar을 설치하거나 수동으로 설치할 수 있어요.

코딩 에이전트로 설정

아래 프롬프트를 복사해 코딩 에이전트에 붙여넣으면 완전히 자동으로 통합돼요.

1. Run `npx lmnr-cli setup` at the project root to get started with Laminar. This command will authenticate the user, save a new project API key to .env, and install the Laminar skill.
2. Instrument this project with Laminar using the installed skill or the docs:
   https://laminar.sh/docs/tracing/integrations/vercel-ai-sdk
3. Run a traced path inside your application.
4. Verify instrumentation:
   `lmnr-cli sql query "SELECT * FROM traces ORDER BY start_time DESC LIMIT 1" --json`

수동 설정

Laminar을 수동으로 설정하려면 먼저 @lmnr-ai/lmnr 패키지를 설치해요.

npm install @lmnr-ai/lmnr

프로젝트 API 키를 받아 환경에 설정

그런 다음 Laminar에 가입하거나 인스턴스를 셀프호스팅(github)하고 새 프로젝트를 만들어요.

npx lmnr-cli@latest setup을 사용해요. 이 명령은:

  • Laminar에 디바이스를 인증하고,
  • 새 프로젝트 API 키를 만들어 .env에 LMNR_PROJECT_API_KEY로 저장하며,
  • 코딩 에이전트가 Laminar SDK로 에이전트를 계측하는 데 사용할 Laminar 스킬을 설치해요.

Next.js

트레이싱 초기화

Next.js에서 Laminar 초기화와 AI SDK 텔레메트리 통합은 둘 다 instrumentation.{ts,js}에서 해야 해요:

export async function register() {
  // prevent this from running in the edge runtime
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');

    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}

next.config에 @lmnr-ai/lmnr 추가

next.config.js(.ts / .mjs)에 다음 줄을 추가해요:

const nextConfig = {
  serverExternalPackages: ['@lmnr-ai/lmnr'],
};

export default nextConfig;

Laminar은 OpenTelemetry에 의존하는데 OpenTelemetry는 일부 Node.js 특화 기능을 사용하므로, Next.js에 이 사실을 알려야 하기 때문이에요. 자세한 내용은 Next.js 문서에서 확인하세요.

AI SDK 호출 트레이싱

통합이 등록되면 모든 AI SDK 호출에서 텔레메트리가 자동으로 캡처돼요:

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

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is Laminar flow?',
});

이것은 ai.generateText에 대한 span을 생성해요. Laminar은 다음 정보를 수집하고 표시해요:

  • LLM 호출 입력과 출력
  • 시작 및 종료 시간
  • 기간 / 지연시간
  • 사용된 프로바이더와 모델
  • 입력 및 출력 토큰
  • 입력 및 출력 가격
  • 추가 메타데이터와 span 속성

이전 버전의 Next.js

13.4 ≤ Next.js < 15를 사용한다면 실험적 instrumentation 훅도 활성화해야 해요. next.config.js에 다음을 넣어요:

module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

자세한 내용은 Laminar의 AI SDK 통합 가이드와 Next.js instrumentation 문서를 참고하세요. Next.js의 모든 trace를 활성화하는 방법도 문서에서 배울 수 있어요.

@vercel/otel과 함께 사용

Laminar은 @vercel/otel과 함께 존재하며 AI SDK 호출을 추적할 수 있어요. 기본 Laminar 설정은 다음을 보장해요:

  • 일반 Next.js trace는 @vercel/otel을 통해 Vercel로 구성된 텔레메트리 백엔드로 전송되고,
  • AI SDK 및 기타 LLM 또는 브라우저 에이전트 trace는 Laminar을 통해 전송돼요.
import { registerOTel } from '@vercel/otel';

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const { initializeLaminarInstrumentations, LaminarAiSdkTelemetry } =
      await import('@lmnr-ai/lmnr');

    // Next.js telemetry
    registerOTel({
      serviceName: 'my-service',
      instrumentations: initializeLaminarInstrumentations(),
    });

    // Laminar AI SDK telemetry
    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}

모든 Next.js trace를 Laminar을 통해 추적할 수 있게 하는 고급 구성은 예시 저장소를 참고하세요.

@sentry/node와 함께 사용

Laminar은 @sentry/node와 함께 존재하며 AI SDK 호출을 추적할 수 있어요. Sentry.init 이후에 Laminar을 초기화해야 해요.

이것은 다음을 보장해요:

  • Sentry가 계측하는 모든 것은 Sentry 백엔드로 전송되고,
  • AI SDK 및 기타 LLM 또는 브라우저 에이전트 trace는 Laminar을 통해 전송돼요.
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const Sentry = await import('@sentry/node');
    const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');

    Sentry.init({
      dsn: process.env.SENTRY_DSN,
    });

    // Make sure to initialize Laminar **after** `Sentry.init`
    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}

Node.js

트레이싱 초기화

그런 다음 애플리케이션에서 트레이싱을 초기화해요:

import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(new LaminarAiSdkTelemetry());

이것은 애플리케이션에서 가능한 한 일찍 한 번 해야 하며, 다른 트레이싱 라이브러리(예: @sentry/node)가 초기화된 이후에 해야 해요.

Laminar 문서에서 더 읽어보세요.

AI SDK 호출 트레이싱

통합이 등록되면 모든 AI SDK 호출에서 텔레메트리가 자동으로 캡처돼요:

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

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is Laminar flow?',
});

이것은 ai.generateText에 대한 span을 생성해요. Laminar은 다음 정보를 수집하고 표시해요:

  • LLM 호출 입력과 출력
  • 시작 및 종료 시간
  • 기간 / 지연시간
  • 사용된 프로바이더와 모델
  • 입력 및 출력 토큰
  • 입력 및 출력 가격
  • 추가 메타데이터와 span 속성

@sentry/node와 함께 사용

Laminar은 @sentry/node와 함께 동작해 AI SDK 호출을 추적할 수 있어요. Sentry.init 이후에 Laminar을 초기화해야 해요:

const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');
const Sentry = await import('@sentry/node');
const { registerTelemetry } = await import('ai');

Sentry.init({
  dsn: process.env.SENTRY_DSN,
});

registerTelemetry(new LaminarAiSdkTelemetry());

이것은 다음을 보장해요:

  • Sentry가 계측하는 모든 것은 Sentry 백엔드로 전송되고,
  • AI SDK 및 기타 LLM 또는 브라우저 에이전트 trace는 Laminar을 통해 전송돼요.

두 라이브러리는 추가 고급 구성을 허용하지만, 위 기본 설정이 권장돼요.

추가 구성

Laminar 옵션

LaminarAiSdkTelemetry는 Laminar.initialize()에 옵션을 전달할 수 있어요. 셀프호스팅 사용자의 경우,

import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(new LaminarAiSdkTelemetry({
  laminarOptions: {
    projectApiKey: proces...KEY,
    baseUrl: "http://localhost",
    httpPort: 8000,
    grpcPort: 8001,
  },
}));

입력 또는 출력 기록 안 함

기본적으로 Laminar 통합은 모든 입력과 출력을 기록하지만, 생성자 옵션에서 이를 비활성화할 수 있어요.

import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(new LaminarAiSdkTelemetry({
  recordInputs: false, // default true
  recordOutputs: false, // default true
}));

모든 에이전트 스텝에 span 추가

AI SDK 텔레메트리 통합은 모든 에이전트 스텝에 대해 스텝 span을 생성해요. 기본적으로 Laminar은 이 span들을 무시해요. 생성자 옵션에서 이를 구성할 수 있어요.

import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(new LaminarAiSdkTelemetry({
  createStepSpan: true, // default false
}));

span 이름

기본 span 이름을 재정의하려면 telemetry 옵션 안에 functionId를 설정할 수 있어요.

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    functionId: 'poem-writer',
  },
});

중첩 span

AI SDK 호출뿐 아니라 애플리케이션의 다른 함수도 추적하려면 Laminar의 observe 래퍼를 사용할 수 있어요.

import { observe } from '@lmnr-ai/lmnr';

const result = await observe({ name: 'my-function' }, async () => {
  // ... some work
  await generateText({
    //...
  });
  // ... some work
});

이것은 "my-function"이라는 이름의 span을 만들고 함수 호출을 추적해요. 그 안에 중첩된 ai.generateText span이 보일 거예요.

observe로 감싼 함수의 입력 인자를 추적하려면 추가 인자로 래퍼에 전달하세요. 함수의 반환값은 래퍼에서 반환되고 span의 출력으로 추적돼요.

const result = await observe(
  { name: 'poem writer' },
  async (topic: string, mood: string) => {
    const { text } = await generateText({
      model: openai('gpt-6-luna'),
      prompt: `Write a poem about ${topic} in ${mood} mood.`,
    });
    return text;
  },
  'Laminar flow',
  'happy',
);

메타데이터

Laminar에서 메타데이터는 trace 수준에 설정돼요. 메타데이터는 키-값 쌍을 포함하며 trace를 필터링하는 데 사용할 수 있어요.

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      'my-key': 'my-value',
      'another-key': 'another-value',
    },
  },
});

이것은 Laminar의 메타데이터로 변환되어 trace에 저장돼요.

태그

예약된 메타데이터 키 중 하나는 tags예요. 이를 사용해 span에 태그를 추가할 수 있어요.

태그는 이후 Laminar에서 trace를 필터링하는 데 사용할 수 있어요.

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      tags: ['fallback-model', 'api-handler'],
    },
  },
});

세션 ID와 사용자 ID

Laminar의 trace는 세션 또는 사용자 ID로 그룹화할 수 있어요. 이들도 예약된 메타데이터 키예요.

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      sessionId: 'session-123',
      userId: 'user-123',
    },
  },
});

더 알아보기 (Learn more)