02 การติดตาม
นี่คือสเลตว่างสำหรับขั้นตอนการติดตาม — โค้ดเดียวกับ checkpoint/01-base-app โดยไม่มีการเชื่อมต่อ Langfuse ยัง แพ็คเกจ Langfuse อยู่ในแล้ว package.json — เรียกใช้ npm inst...
เนื้อหาเวิร์กช็อปเก็บรักษาไว้ในที่เก็บข้อมูล langfuse/langfuse-workshop ที่เป็นสาธารณะ ใช้ที่เก็บข้อมูลสำหรับแอปพลิเคชันที่สามารถเรียกใช้ได้ สาขา checkpoint และการตั้งค่าในเครื่อง
จุดเริ่มต้น
git checkout checkpoint/02-tracingนี่คือสเลตว่างสำหรับขั้นตอนการติดตาม — โค้ดเดียวกับ checkpoint/01-base-app โดยไม่มีการเชื่อมต่อ Langfuse ยัง แพ็คเกจ Langfuse อยู่ในแล้ว package.json — เรียกใช้ npm install หากคุณยังไม่ได้ ตรวจสอบให้แน่ใจว่า .env มีคีย์ OPENAI_API_KEY และคีย์ Langfuse ของคุณ
เหตุใดเราจึงติดตาม
การติดตามจะบันทึกทุกขั้นตอนของเอเจนต์ของคุณ — ทุกการเรียกโมเดล ทุกการเรียกใช้เครื่องมือ อินพุตที่ใส่เข้าไปและเอาต์พุตที่กลับมา — ตามลำดับที่เกิดขึ้น มันเปลี่ยนเอเจนต์จากกล่องดำให้เป็นสิ่งที่คุณสามารถเปิดและตรวจสอบหลังจากนั้น เพื่อเมื่อคำตอบผิด คุณสามารถชี้ไปที่ขั้นตอนที่แน่นอนที่มันผิดไปแทนที่จะเดา
หากคุณต้องการแรงจูงใจรูปแบบใหญ่ ให้ดูบทเรียน Langfuse Academy เกี่ยวกับการติดตาม หากคุณต้องการรายละเอียดทางเทคนิค (ตัวเลือก SDK การติดตาม internals OpenTelemetry คุณลักษณะสแปน) เอกสารการติดตาม ครอบคลุมว่า
เป้าหมาย
เมื่อพ่อถาม "ฉันจะเปิด Bluetooth ได้อย่างไร?" เอเจนต์ไม่เพียงแค่ตี OpenAI ครั้งเดียว เบื้องหลังฉากมันขอ OpenAI ว่าจะทำอะไร เรียกใช้ get_support_context เพื่อดึงการตั้งค่า iPhone ของพ่อ ขอ OpenAI อีกครั้ง เรียกใช้ search_help_library สำหรับขั้นตอน Bluetooth แล้วขอ OpenAI อีกครั้งเพื่อสร้างคำตอบที่มีหมายเลข ไม่มีสิ่งที่มองเห็นได้วันนี้
เป้าหมายของบทนี้คือให้ทุกขั้นตอนเหล่านั้นมองเห็นได้ใน Langfuse — การหันมาแชทหนึ่งครั้งกลายเป็นรอยติดตามที่ซ้อนกันหนึ่งรายการพร้อมการเรียกใช้เอเจนต์ การสร้างสรรค์ OpenAI และการเรียกใช้เครื่องมือสองชิ้นที่บันทึกทั้งหมด ตามลำดับ

เราจะสร้างรอยติดตามในสามขั้นตอนที่สะท้อนโครงสร้างของเอเจนต์:
- รอยติดตามแรก — บันทึกการสร้างสรรค์ OpenAI เองเท่านั้น
- รอยติดตามที่ซ้อนกัน — จัดกลุ่มการสร้างสรรค์ภายใต้การเรียกใช้เอเจนต์หนึ่งครั้งต่อการหันมา
- การบันทึกการเรียกใช้เครื่องมือ — ทำให้การเรียกใช้เครื่องมือแต่ละครั้งกลายเป็นการสังเกตการณ์ของตัวเอง
ขั้นตอนที่ 1 — รอยติดตามแรก
เราต้องการการสังเกตการณ์เรื่องการเรียกใช้ OpenAI เองเพื่อดูว่าอินพุตและเอาต์พุตคืออะไร และต้นทุน โทเค็น และเวลาที่ใช้ไป การเปลี่ยนแปลงสองอย่างก็เพียงพอแล้ว
src/server/index.ts
เริ่มตัวประมวลผลสแปน Langfuse ใกล้ด้านบนของไฟล์:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }).start();ตัวประมวลผลอ่าน LANGFUSE_PUBLIC_KEY LANGFUSE_SECRET_KEY และ LANGFUSE_BASE_URL จากสภาพแวดล้อมกระบวนการโหนด ในเวิร์กช็อปนี้ เซิร์ฟเวอร์โหลดที่เก็บข้อมูล .env ก่อนที่ Langfuse SDK จะเริ่มต้น ดังนั้นให้แก้ไข .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 พร้อมพร้อมท์ การตอบสนอง โทเค็น และเวลาในการตอบสนอง การสร้างสรรค์แต่ละครั้งยังคงเป็นรอยติดตามระดับบนของตัวเอง เราแก้ไขสิ่งนั้นต่อไป

ขั้นตอนที่ 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 ที่ซ้อนกันอยู่ด้านล่าง

ขั้นตอนที่ 3 — การบันทึกการเรียกใช้เครื่องมือ
การสร้างสรรค์ OpenAI ได้เอกสารการเรียกใช้เครื่องมืออยู่แล้วในเอาต์พุต tool_calls ของมัน แต่เราไม่มีการสังเกตการณ์สำหรับการทำงานของเครื่องมือที่แท้จริง — ไม่มีวิธีดูว่าอินพุตใดเข้าไปและออกมา รูปแบบ observe(...) เดียวกัน สามารถใช้กับแต่ละเครื่องมือได้
src/server/tools.ts
เพิ่มนำเข้าและตัวช่วย executeTool ที่สังเกต สองตัวด้านบน executeTool จากนั้น แทนที่ TOOL_DEFINITIONS ที่มีอยู่แล้วด้วยเวอร์ชั่นด้านล่าง เพื่อให้สวิตช์เรียกตัวช่วยที่ห่อแทนการทำงานแบบอินไลน์ npm run dev อยู่ไม่ถูกต้อง
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}` };
}
}หาก Multiple exports with the same name "executeTool" หยุดด้วย executeTool dad-it-support-chat-turn ดั้งเดิมยังคงอยู่ที่ไหนสักแห่งด้านล่างไฟล์ ลบมันและเก็บเฉพาะเวอร์ชั่นด้านบน

วิธีตรวจสอบว่าคุณเสร็จสิ้น
- การหันมาผู้ใช้เพียงครั้งเดียวสร้างรอยติดตามหนึ่งรายการใน Langfuse
- การสังเกตการณ์รูท: (ประเภท
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 ผู้ใช้และเซッชัน
บทนำข้างต้นได้รับการติดตามด้วยรูป parent → generation → tool ที่สะอาด สิ่งที่ทีมส่วนใหญ่ต้องการถัดไปคือ แบ่ง รอยติดตาม ตามผู้ใช้และตามเซชัน — เพื่อให้คุณดึง "ทุกการหันมาของผู้ใช้นี้ด้วยเอเจนต์" หรือ "เซชั่นหลายการหันมาแบบเต็มจากเช้าเมื่อวาน" ด้วยเหตุผลด้านความเรียบง่าย เราข้ามขั้นตอนนี้ไปในบทนำการติดตามสดใจ แต่จุดสนใจหลังจากการติดตามรวมมันเพื่อให้บทหลังสามารถใช้มุมมองเซชัน/ผู้ใช้โดยไม่มีขั้นตอนรหัสอื่น ดูข้อมูลเกี่ยวกับ Sessions และ Users ในเอกสาร 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 Users มุมมอง Sessions และตัวกรองแท็กทั้งหมดส่องแสงทันทีที่แอตทริบิวต์มีอยู่
สถานะสุดท้าย
แอปพลิเคชันที่ติดตามเสร็จสิ้นนี้คือจุดเริ่มต้นสำหรับ 03-prompt-management และ 04-monitoring
01 แอปพื้นฐาน
คุณสามารถปรับแต่งประสบการณ์ของคุณได้โดยการเปลี่ยนแปลงข้อมูลจำเพาะโทรศัพท์ในไฟล์ support-data.ts การเพิ่มข้อมูลโทรศัพท์ของพ่อของคุณหมายความว่าคุณจะได้รับการตอบสนองสำหรับโทรศัพท์ประเภทนั้น
03 การจัดการพรอมต์
คุณมีแอปที่มีการติดตามการทำงาน พรอมต์ของระบบอยู่เป็นค่าคงที่ชื่อ SYSTEMPROMPT ในไฟล์ src/server/support-agent.ts และใช้โดยตรงเป็นข้อความของระบบ