Langfuse WorkshopClickHouse Workshops

02 트레이싱

이것은 추적 단계를 위한 빈 슬레이트입니다 — checkpoint/01-base-app과 동일한 코드이지만 아직 Langfuse 배선이 없습니다. Langfuse 패키지는 이미 package.json에 있습니다 — npm inst...

워크숍 자료는 공개 langfuse/langfuse-workshop 저장소에서 유지됩니다. 실행 가능한 앱, 체크포인트 분기 및 로컬 설정을 위해 저장소를 사용하세요.

이 Markdown 파일 보기

시작점

git checkout checkpoint/02-tracing

이것은 추적 단계를 위한 빈 슬레이트입니다 — checkpoint/01-base-app과 동일한 코드이지만 아직 Langfuse 배선이 없습니다. Langfuse 패키지는 이미 package.json에 있습니다 — 아직 하지 않았다면 npm install을 실행하세요. .env에 OPENAI_API_KEY과 Langfuse 키가 있는지 확인하세요.

왜 추적합니까

추적은 에이전트가 취하는 모든 단계를 기록합니다 — 모든 모델 호출, 모든 도구 호출, 입력되는 입력 및 반환되는 출력 — 발생한 순서대로. 에이전트를 블랙박스에서 사후에 검사할 수 있는 것으로 변환하여, 답변이 잘못되었을 때 추측하는 대신 오류가 발생한 정확한 단계를 지적할 수 있습니다.

더 큰 그림의 동기를 원하면 Langfuse Academy 추적 레슨을 참조하세요. 기술 세부 사항(SDK 옵션, OpenTelemetry 내부, 스팬 속성)을 원하면 추적 문서에서 다룹니다.

목표

Dad가 "How do I turn Bluetooth on?"이라고 물으면, 에이전트는 OpenAI를 한 번만 히트합니다. 백그라운드에서는 무엇을 해야 할지 OpenAI에 묻고, get_support_context을 호출하여 Dad의 iPhone 설정을 가져오고, OpenAI에 다시 묻고, search_help_library를 호출하여 Bluetooth 단계를 얻은 다음, 번호가 매겨진 답변을 생성하기 위해 OpenAI를 한 번 더 묻습니다. 지금은 아무것도 보이지 않습니다.

이 장의 목표는 Langfuse에서 이 단계의 모든 것을 보이게 하는 것입니다 — 하나의 채팅 턴은 에이전트 실행, OpenAI 생성, 그리고 두 가지 도구 호출이 모두 순서대로 기록된 하나의 중첩된 트레이스가 됩니다.

Spec의 단계별 프로세스

에이전트의 구조를 반영하는 세 가지 단계로 트레이스를 구축합니다:

  1. 첫 번째 트레이스 — OpenAI 생성 자체를 기록합니다.
  2. 중첩된 트레이스 — 생성을 차례당 하나의 에이전트 실행 아래로 그룹화합니다.
  3. 도구 호출 기록 — 각 도구 호출을 자체 관찰로 만듭니다.

1단계 — 첫 번째 트레이스

입력과 출력이 무엇인지 보기 위해 OpenAI 호출 자체에서 관찰성을 원하고, 얼마나 많은 비용, 토큰 및 시간이 소비되는지. 두 가지 변경으로 충분합니다.

src/server/index.ts

Langfuse 스팬 프로세서를 파일 맨 위에서 시작하세요:

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

new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }).start();

프로세서는 Node 프로세스 환경에서 LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY 및 LANGFUSE_BASE_URL을 읽습니다. 이 워크숍에서 서버는 Langfuse SDK가 시작되기 전에 저장소 .env을 로드하므로 대신 .env를 편집하세요.

참고: 마지막 트레이스가 npm run dev을 중지하거나 다시 시작할 때 때때로 늦게 표시되는 경우 index.ts로 돌아가서 위의 한 줄짜리를 langfuseSpanProcessor 및 sdk 변수라고 이름이 지어진 것으로 변환하십시오. 그래야 shutdown()이 플러시할 수 있습니다:

const langfuseSpanProcessor = new LangfuseSpanProcessor();
const sdk = new NodeSDK({ spanProcessors: [langfuseSpanProcessor] });
sdk.start();

async function shutdown() {
  server.close();
  await langfuseSpanProcessor.forceFlush();
  await sdk.shutdown();
}

추적 모델을 이해하는 데 필요하지는 않지만, 로컬 개발에서 "내 마지막 트레이스는 어디로 갔나?" 순간을 혼동하지 않습니다.

src/server/support-agent.ts

가져오기를 추가하세요:

import { observeOpenAI } from "@langfuse/openai";

그런 다음 OpenAI 클라이언트를 만드는 위치를 감싸세요. runSupportConversation에서 이 줄을 찾으세요:

const openai = new OpenAI({ apiKey: env.openaiApiKey });

그리고 다음으로 변경하세요:

const openai = observeOpenAI(new OpenAI({ apiKey: env.openaiApiKey }));

그게 전부입니다. 팩토리 함수도 없고, 별도의 원본 클라이언트도 없습니다 — observeOpenAI은 호출 사이트에서 OpenAI 클라이언트를 인라인으로 래핑하고, openai.chat.completions.create(...) 아래는 이제 모든 호출에 대한 트레이스를 내보냅니다.

검증: npm run dev, 질문을 하나 물어보고, Langfuse를 새로고침하세요 — 각 OpenAI 호출마다 프롬프트, 응답, 토큰 및 지연 시간이 있는 하나의 생성이 표시되어야 합니다. 각 생성은 여전히 자신의 최상위 레벨 트레이스입니다. 다음에 수정합니다.

1단계 후 Langfuse 추적 보기 — 각 채팅 차례는 독립형 openai-chat-completion 생성으로 나타납니다.

2단계 — 중첩된 트레이스

생성을 컨텍스트에 넣으려면 차례마다 하나의 에이전트 실행 아래로 그룹화합니다. src/server/support-agent.ts에서 세 가지 편집 — 함수 본문 변경 없음.

1. 가져오기를 추가하세요:

import { observe } from "@langfuse/tracing";

2. 기존 함수를 강등하세요. 찾기:

export async function runSupportConversation(request: ChatRequest): Promise<ChatResponse> {

export을 놓고 이름을 바꾸세요:

async function runSupportConversationInner(request: ChatRequest): Promise<ChatResponse> {

본문은 정확히 그대로 유지됩니다.

3. 파일 맨 아래에 래핑된 내보내기를 추가하세요:

export const runSupportConversation = observe(runSupportConversationInner, {
  name: "dad-it-support-chat-turn",
  asType: "agent"
});

index.ts은 여전히 동일한 방식으로 runSupportConversation을 가져옵니다. observe(...)은 함수 인수를 트레이스 입력으로 자동 캡처하고 반환 값을 트레이스 출력으로 자동 캡처합니다.

검증: 이제 하나의 채팅 턴이 하나의 dad-it-support-chat-turn 관찰로 나타나야 하고 OpenAI 생성이 아래에 중첩됩니다.

2단계 후 추적 트리 — OpenAI 생성이 자식으로 있는 하나의 dad-it-support-chat-turn 에이전트 루트.

3단계 — 도구 호출 기록

OpenAI 생성은 이미 tool_calls 출력에서 도구 호출을 언급하지만, 실제 도구 실행에 대한 관찰이 없습니다 — 어떤 입력이 들어갔고 무엇이 나왔는지 볼 수 없습니다. observe(...) 패턴도 각 도구에 적용할 수 있습니다.

src/server/tools.ts

가져오기와 executeTool 위의 두 가지 관찰된 도우미를 추가한 다음 기존 executeTool을 아래 버전으로 교체하여 스위치가 인라인 작업 대신 래핑된 도우미를 호출하도록 합니다. TOOL_DEFINITIONS은 그대로 유지됩니다.

import { observe } from "@langfuse/tracing";

const getSupportContextTool = observe(
  async () => {
    const context = getSupportContext();

    return {
      ok: true,
      context: {
        id: context.id,
        label: context.label,
        devices: context.devices,
        deviceSummary: context.deviceSummary,
        responseStyle: context.responseStyle,
        scopeHighlights: context.scopeHighlights,
        notableApps: context.notableApps
      }
    };
  },
  { name: "get_support_context", asType: "tool" }
);

const searchHelpLibraryTool = observe(
  async (input: { question: string }) => {
    const guides = searchGuides(input.question);

    return {
      ok: true,
      results: guides.map((guide) => ({
        id: guide.id,
        title: guide.title,
        summary: guide.summary,
        steps: guide.steps,
        caution: guide.caution ?? null
      }))
    };
  },
  { name: "search_help_library", asType: "tool" }
);

export async function executeTool(name: string, input: Record<string, unknown>): Promise<ToolResult> {
  switch (name) {
    case "get_support_context":
      return getSupportContextTool();

    case "search_help_library":
      return searchHelpLibraryTool({ question: String(input.question ?? "") });

    default:
      return { ok: false, error: `Unsupported tool: ${name}` };
  }
}

npm run dev이 Multiple exports with the same name "executeTool"로 중지되면 원본 executeTool이 여전히 파일 아래에 있습니다. 삭제하고 위의 버전만 유지하세요.

3단계 후 전체 추적 — dad-it-support-chat-turn (에이전트)과 아래에 형제로 있는 OpenAI 생성 및 get_support_context + search_help_library 도구 관찰.

완료 여부 확인 방법

  • 단일 사용자 턴이 Langfuse에서 하나의 트레이스를 만듭니다.
  • 루트 관찰: dad-it-support-chat-turn (유형 agent).
  • observeOpenAI(...)에서 생성된 자식 — 프롬프트, 응답, 토큰, 지연 시간.
  • 자식 도구 관찰: get_support_context, search_help_library.
  • 루트 입력은 채팅 요청이고, 루트 출력은 채팅 응답입니다.

마무리

동일한 패턴, 다양한 관찰 유형, 동일한 개념: observe(fn, { asType })은 함수를 래핑하고 제공하는 이름과 유형을 가진 스팬을 내보냅니다. observeOpenAI(client)은 OpenAI SDK용 해당 래핑의 전문화된 버전입니다.

Langfuse 모범 사례에 따라 라인에 풍부한 추적을 추가하는 더 간단한 방법은 Langfuse 스킬 (/langfuse)입니다. 손으로 각 래핑을 굴리지 않고도 코드베이스에 권장되는 패턴을 적용합니다. 이 연습은 스킬이 후드 아래에서 무엇을 하고 있는지 이해하도록 존재합니다.

observeOpenAI 자체는 공식 OpenAI SDK를 래핑합니다 — 후드 아래 Langfuse OpenAI JS용 자동 계측과 동일합니다. 다른 SDK (Anthropic, Vercel AI SDK, 고유한 HTTP 클라이언트)를 사용하는 경우 Langfuse 통합 카탈로그에는 동등한 래퍼 또는 자동 계측 가이드가 있습니다.

부록/보너스 섹션 — 사용자 및 세션 ID

위의 연습은 깔끔한 부모 → 생성 → 도구 모양으로 추적을 얻게 합니다. 다음 대부분의 팀이 원하는 것은 사용자 및 세션별로 추적을 조각화하는 것입니다 — "이 사용자가 에이전트와 가진 모든 차례" 또는 "어제 아침의 전체 다중 턴 세션"을 끌어올 수 있습니다. 간단한 이유로 우리는 라이브 추적 연습에서 이 단계를 건너뜁니다만, 추적 후 체크포인트는 이를 포함하므로 이후 장은 다른 코드 단계 없이도 세션/사용자 보기를 사용할 수 있습니다. Langfuse 문서에서 세션 및 사용자에 대한 정보를 참조하세요.

간단히 말해서:

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

return propagateAttributes(
  {
    userId: request.userId ?? `workshop-${context.id}`,
    sessionId: request.sessionId,
    tags: ["langfuse-workshop", "dad-it-support"]
  },
  async () => {
    // ...the same tool-calling loop...
  }
);

propagateAttributes(...) 블록 내의 모든 것 — observeOpenAI에서 내보낸 모든 자식 스팬을 포함하여 — userId, sessionId 및 태그를 자동으로 얻습니다. Langfuse 사용자 보기, 세션 보기 및 태그 필터는 모두 속성이 나타나는 즉시 켜집니다.

최종 상태

이 완성된 추적된 앱은 03-prompt-management 및 04-monitoring의 시작점입니다.

이 페이지의 내용

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

KO