Langfuse WorkshopClickHouse Workshops

02 Theo dõi

Đây là bảng trắng cho bước tracing — code tương tự như checkpoint/01-base-app, không có wiring Langfuse nào. Các package Langfuse đã có trong package.json — chạy npm inst...

Tài liệu workshop được duy trì trong repository công khai langfuse/langfuse-workshop. Sử dụng repository cho ứng dụng có thể chạy được, các nhánh checkpoint, và cài đặt cục bộ.

Xem file Markdown này

Điểm bắt đầu

git checkout checkpoint/02-tracing

Đây là bảng trắng cho bước tracing — code tương tự như checkpoint/01-base-app, không có wiring Langfuse nào. Các package Langfuse đã có trong package.json — chạy npm install nếu bạn chưa. Đảm bảo .env có OPENAI_API_KEY và khóa Langfuse của bạn.

Tại sao chúng ta trace

Tracing ghi lại mọi bước mà agent của bạn thực hiện — mỗi lệnh gọi model, mỗi invocation tool, inputs đi vào và outputs đi ra — theo thứ tự chúng xảy ra. Nó biến agent từ một hộp đen thành cái gì đó bạn có thể mở ra và kiểm tra sau, vì vậy khi một câu trả lời sai bạn có thể chỉ vào bước chính xác nơi nó sai thay vì đoán.

Nếu bạn muốn xem động lực toàn bộ, hãy xem bài học Langfuse Academy về tracing. Nếu bạn muốn chi tiết kỹ thuật (tùy chọn SDK, OpenTelemetry internals, span attributes), tài liệu tracing cover điều đó.

Mục tiêu

Khi Dad hỏi "Làm cách nào để bật Bluetooth?", agent không chỉ hit OpenAI một lần. Phía sau hậu trường, nó hỏi OpenAI phải làm gì, gọi get_support_context để lấy setup iPhone của Dad, hỏi OpenAI lần nữa, gọi search_help_library cho các bước Bluetooth, sau đó hỏi OpenAI một lần nữa để tạo ra câu trả lời được đánh số. Không có gì hiển thị hôm nay.

Mục tiêu của chương này là làm cho mọi một trong các bước đó hiển thị trong Langfuse — một chat turn trở thành một trace lồng nhau với agent run, các generations OpenAI, và hai tool calls đều được ghi lại theo thứ tự.

Quá trình từng bước của Spec

Chúng ta sẽ xây dựng trace trong ba bước phản ánh cấu trúc của agent:

  1. Trace đầu tiên — ghi lại các generations OpenAI chính nó.
  2. Nested traces — nhóm các generations dưới một agent run cho mỗi turn.
  3. Recording tool calls — làm cho mỗi invocation tool là observation riêng của nó.

Bước 1 — Trace đầu tiên

Chúng ta muốn khả năng quan sát trên chính các lệnh gọi OpenAI để xem inputs và outputs là gì, và chi phí, tokens và thời gian bao nhiêu được sử dụng. Hai thay đổi là đủ.

src/server/index.ts

Bắt đầu Langfuse span processor gần đầu tệp:

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

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

Procesor đọc LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, và LANGFUSE_BASE_URL từ môi trường quá trình Node. Trong workshop này, máy chủ tải repository .env trước khi Langfuse SDK bắt đầu, vì vậy hãy chỉnh sửa .env thay vì dựa vào các giá trị export shell.

Ghi chú phụ: nếu trace cuối cùng đôi khi xuất hiện muộn khi bạn dừng hoặc khởi động lại npm run dev, hãy quay lại index.ts và chuyển one-liner ở trên thành named langfuseSpanProcessor và sdk variables vì vậy shutdown() có thể flush chúng:

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

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

Bạn không cần cái này để hiểu mô hình tracing, nhưng nó tránh các khoảnh khắc "trace cuối cùng của tôi đi đâu rồi?" trong local dev.

src/server/support-agent.ts

Thêm import:

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

Sau đó wrap OpenAI client nơi bạn tạo nó. Tìm dòng này trong runSupportConversation:

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

và thay đổi nó thành:

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

Đó là toàn bộ diff. Không có factory function, không có separate raw client — observeOpenAI wrap OpenAI client inline tại call site, và openai.chat.completions.create(...) bên dưới nó giờ emits một trace cho mỗi lệnh gọi.

Verify: npm run dev, hỏi một câu hỏi, refresh Langfuse — bạn sẽ thấy một generation cho mỗi lệnh gọi OpenAI với prompt, response, tokens, và latency. Mỗi generation vẫn là nó's own top-level trace; chúng tôi sửa điều đó tiếp theo.

Langfuse Traces view sau Step 1 — mỗi chat turn xuất hiện như các standalone openai-chat-completion generations.

Bước 2 — Nested traces

Để đặt các generations vào ngữ cảnh chúng tôi nhóm chúng dưới một agent run cho mỗi turn. Ba chỉnh sửa trong src/server/support-agent.ts — không có thay đổi function body.

1. Thêm import:

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

2. Hạ cấp function hiện có. Tìm:

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

Hạ export và đổi tên nó:

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

Body vẫn chính xác như nó là.

3. Thêm wrapped export ở dưới cùng của file:

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

index.ts vẫn import runSupportConversation cùng cách. observe(...) auto-captures function argument là trace input và return value là trace output.

Verify: một chat turn nên giờ xuất hiện như một single dad-it-support-chat-turn observation với OpenAI generation được lồng bên dưới.

Trace tree sau Step 2 — một dad-it-support-chat-turn agent root với OpenAI generation như một child.

Bước 3 — Recording tool calls

OpenAI generation đã đề cập đến các tool calls trong tool_calls output của nó, nhưng chúng tôi không có observation cho execution tool thực tế — không có cách để xem input nào đi vào và output nào ra. Cùng một observe(...) pattern, có thể được áp dụng cho mỗi tool.

src/server/tools.ts

Thêm import và hai observed helpers ở trên executeTool, sau đó thay thế executeTool hiện có bằng phiên bản bên dưới vì vậy switch gọi các wrapped helpers thay vì làm công việc inline. TOOL_DEFINITIONS vẫn không bị cảm động.

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}` };
  }
}

Nếu npm run dev dừng với Multiple exports with the same name "executeTool", executeTool gốc vẫn còn ở dưới cùng của file. Xóa nó và giữ chỉ phiên bản ở trên.

Full trace sau Step 3 — dad-it-support-chat-turn (agent) với OpenAI generation và get_support_context + search_help_library tool observations như anh em bên dưới.

Cách xác minh bạn đã hoàn thành

  • Một single user turn tạo ra một trace trong Langfuse.
  • Root observation: dad-it-support-chat-turn (type agent).
  • Child generation từ observeOpenAI(...) với prompt, response, tokens, latency.
  • Child tool observations: get_support_context, search_help_library.
  • Root input là chat request; root output là chat response.

Wrap-up

Cùng pattern, các observation type khác nhau, cùng concept: observe(fn, { asType }) wraps một function và emits một span với tên và type bạn cho nó. observeOpenAI(client) là một phiên bản specialized của wrap đó cho OpenAI SDK.

Một cách thẳng thắn hơn để thêm rich tracing phù hợp với Langfuse best practices là Langfuse skill (/langfuse)). Nó áp dụng các recommended patterns cho codebase của bạn mà không cần bạn hand-rolling mỗi wrap. Walkthrough này tồn tại vì vậy bạn hiểu skill đang làm gì dưới hầu.

observeOpenAI chính nó wraps OpenAI SDK chính thức — dưới hầu nó giống như Langfuse auto-instrumentation cho OpenAI JS). Nếu bạn đang sử dụng một SDK khác (Anthropic, Vercel AI SDK, HTTP client của riêng bạn), Langfuse integrations catalogue có wrapper tương đương hoặc hướng dẫn auto-instrumentation.

Appendix/Bonus section — User và session IDs

Walkthrough ở trên lấy bạn tracing với một parent → generation → tool shape sạch. Điều tiếp theo hầu hết các teams muốn là slice traces bằng user và bằng session — vì vậy bạn có thể kéo "mỗi turn user này có với agent" hoặc "full multi-turn session từ hôm qua sáng." Vì lý do đơn giản chúng tôi bỏ qua bước này trong walkthrough tracing trực tiếp, nhưng checkpoints sau tracing bao gồm nó vì vậy các chương sau có thể sử dụng session/user views mà không cần bước code khác. Xem thông tin trên Sessions và Users trong tài liệu Langfuse.

Vắn tắt:

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...
  }
);

Bất kỳ cái gì bên trong propagateAttributes(...) block — bao gồm tất cả child spans được emit bởi observeOpenAI — tự động lấy userId, sessionId, và tags gắn kèm. Langfuse Users view, Sessions view, và tag filters tất cả sáng lên ngay khi các attributes hiện diện.

Trạng thái cuối cùng

Ứng dụng đã hoàn thành traced này là điểm bắt đầu cho 03-prompt-management và 04-monitoring.

Trên trang này

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.

VI