บทที่ 09 · ประกอบ Harness · Operating & Observability
ควบคุมลูป & มองเห็นสิ่งที่มันทำ
มีรอบหนึ่ง query ค้างไม่จบ และอีกครั้งมันตอบผิดแต่คุณ ดูไม่ออกว่าทำไม
สองปัญหานี้คือเรื่อง “การเดินเครื่อง” ของ agent: คุมลูป (จำกัดจำนวนรอบและเวลา ไม่ให้ค้างชั่วกัลป์)
และ มองเห็น (observability) — ไม่ใช่แค่ log เยอะ ๆ แต่เป็น โครงสร้าง ที่ไล่หาเหตุได้
พูดแบบเข้าใจง่าย
สองสิ่งที่ต้องมีตอนเอา agent ไปเดินจริง:
- คุมลูป (loop control) — จำกัดจำนวนรอบ (max turns) และมี timeout ครอบทั้งงาน
เพื่อให้ tool ที่ค้างหรือโมเดลที่วนไม่จบ ถูกตัดจบ ไม่กินทรัพยากรตลอดไป
- Observability — ไม่ใช่ “log ทุกอย่าง” แต่คือ trace ต่อหนึ่งคำขอ ที่แตกเป็น
spans ซ้อนกัน (เรียกโมเดล / เรียก tool / ดึงข้อมูล / subagent) แต่ละ span บันทึก เจตนา (intent)
ว่า “ทำไมถึงแตกกิ่งนี้” ทั้งหมดใช้มาตรฐานเดียว (OpenTelemetry GenAI) จึงเปิดอ่านในเครื่องมือไหนก็ได้
log แบนราบ (บรรทัดต่อบรรทัด) ตอบได้แค่ “อะไรรันไปบ้าง” แต่ตอบไม่ได้ว่า บรรทัดไหนเป็นของคำขอไหน · ใครเรียกใคร · ทำไมถึงแตกกิ่งตรงนี้
เปรียบเทียบ: statement ธนาคาร vs ใบเสร็จพับเป็นต้นไม้พร้อมเหตุผล
log แบนราบ = statement: รายการเรียงตามเวลา แต่ละบรรทัดถูกต้องในตัว แต่บอกไม่ได้ว่ากาแฟแก้วไหนของทริปไหน หรือซื้อทำไม
trace ที่มี span + intent = ใบเสร็จพับเป็นต้นไม้: ทั้งทริปคือพับนอก แต่ละร้านคือพับย่อย และทุกบรรทัดมีโน้ตว่า “ทำไม” —
พอมีรายการผิด คุณเปิดพับที่ถูกต้องแทนการไถ statement 4000 บรรทัด
ในระบบของเรา — ลูปมีเพดาน + trace ที่ยัง “แบน”
max turnsจำกัดจำนวนรอบของลูป
→
timeoutครอบทั้งงาน — tool ค้างก็ถูกตัดจบ
→
บันทึกทุกการเรียก tool ถูก log ลงไฟล์ trace
→
…แต่แบนไม่มี trace_id / parent / intent
ระบบจริงของเรา คุมลูปได้ดี — มีเพดานจำนวนรอบและ timeout ~90 วินาทีครอบทั้งงาน tool ค้างจึงไม่ค้างตลอดไป
ส่วน observability เรามี “จุดเริ่มต้นที่ซื่อสัตย์”: ทุกการเรียก tool ถูกบันทึกเป็นบรรทัด JSON หนึ่งบรรทัด (เวลา/เครื่องมือ/สถานะ/นาน/ผล/ error)
แต่มัน ยังแบน — ไม่มี trace_id ไว้จับกลุ่มต่อหนึ่งคำขอ ไม่มี parent ไว้ซ้อนชั้น ไม่มีช่อง intent
เอกสารของระบบเองเรียกมันว่า “telemetry baseline แบบเบา ๆ” พอ run มี 30 ขั้น การดีบักคือการไถจอแล้วเดาว่าบรรทัดไหนเป็นของ run ไหน
นี่คือจุดอ่อนที่เราจะ “ค้นพบ” ไม่ใช่ปกปิด — observability เป็นมิติที่ระบบเรายัง อ่อน
(มี log แต่ยังไม่มีโครงสร้าง span) จำไว้ แล้วเราจะมาสรุปภาพรวมความแข็ง/อ่อนของทั้ง fleet ในบทที่ 17
ทำพลาด vs ทำถูก
⚠️ แย่ · ลูปไม่มีเพดาน
tool ค้าง = ค้างตลอดกาล
ไม่มี max turns ไม่มี timeout — tool ที่ค้างหรือโมเดลที่วนตัดสินใจไม่จบ จะกินเวลาและเงินไปเรื่อย ๆ
ผู้ใช้เห็นแค่สปินเนอร์หมุนไม่เลิก
⚠️ แย่ · “เรา log ทุกอย่างแล้ว” = มองเห็นแล้ว
log แบนราบไล่เหตุไม่ได้
แต่ละบรรทัดถูกต้องในตัว แต่บอกไม่ได้ว่าบรรทัดไหนเป็นของ run ที่พัง — ยิ่งมีงานเบื้องหลังทำงานพร้อมกัน
timestamp ห่างกันมิลลิวินาทีก็แยกไม่ออก การดีบักกลายเป็นการเดา
✅ ดี · ลูปมีเพดาน + trace เป็น span tree
โครงสร้างคือ observability
max turns + timeout กันค้าง; และ trace ที่มี trace_id + parent + intent ทำให้ run 30 ขั้นกลายเป็นต้นไม้
ที่กิ่งพังเด้งแดงให้เห็นในคลิกเดียว — ข้อมูลชุดเดิม แต่จัดโครงสร้างให้ไล่เหตุได้
ลองเอง — ไล่หาเหตุจาก trace
กิจกรรม · เทียบมุมมอง
Debug From The Trace: Flat Log vs Span Tree
งานจองที่ล้มเหลวอันเดียวกัน แสดง 2 แบบ ลองหา “สาเหตุจริง” ใน log แบนราบ ก่อน (จับเวลา + นับครั้งที่คลิกผิด)
แล้วกด “เปิด span tree” ดูว่าข้อมูลชุดเดิมเป๊ะ แต่โครงสร้างทำให้เจอเหตุในคลิกเดียว
คลิกหาบรรทัด “สาเหตุจริง” → แล้วเปิด span tree
มองไปข้างหน้า — ครบ 7 ชิ้นแล้ว ถึงเวลาตั้งชื่อมัน
เราเพิ่มทีละชิ้นมาตั้งแต่บทที่ 04: tools · context · memory · grounding · permissions · loop-control · observability
ทั้งหมดรวมกันมีชื่อว่าอะไร? บทที่ 10 จะ ตั้งชื่อทั้งกอง และเปิด Harness Scorecard ใบแรก
สรุปบทที่ 09
- คุมลูป = max turns + timeout ครอบทั้งงาน → tool ค้างไม่ค้างตลอดไป
- observability ≠ log เยอะ แต่คือ trace ต่อคำขอ ที่มี span ซ้อน + intent
- log แบนราบ ไล่เหตุไม่ได้ เมื่อมีงานพร้อมกันหลายอย่าง — ขาด trace_id/parent/intent
- observability เป็นมิติที่ระบบเรายัง อ่อน (ค้นพบ ไม่ใช่ปกปิด)
Harness Scorecard · มิติ: “คุมลูปได้ + ไล่เหตุจาก trace ได้ไหม?”
ผู้ช่วยจัดการแล็บ: คุมลูป ✅ · observability ⚠️ อ่อน (มี log แต่ยังแบน ไม่มี span/intent)
📋 build-your-harness checklist · บรรทัดที่ 9
“เพดานลูป (max turns + timeout) + trace ต่อคำขอที่มี trace_id · parent · intent”