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ộ.
Đ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ự.

Chúng ta sẽ xây dựng trace trong ba bước phản ánh cấu trúc của agent:
- Trace đầu tiên — ghi lại các generations OpenAI chính nó.
- Nested traces — nhóm các generations dưới một agent run cho mỗi turn.
- 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.

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.

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.

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(typeagent). - 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.
01 Ứng dụng cơ sở
Bạn có thể tùy chỉnh trải nghiệm của mình bằng cách thay đổi specs điện thoại trong tệp support-data.ts. Thêm thông tin điện thoại của dad của bạn có nghĩa là, bạn sẽ nhận được câu trả lời cho đúng loại điện thoại.
03 Quản lý prompt
Bạn có một ứng dụng được theo dõi. System prompt tồn tại như một hằng số được gọi là SYSTEMPROMPT trong src/server/support-agent.ts và được sử dụng trực tiếp làm system message.