Error analysis และคนดู (หัวใจของคอร์ส)
บทที่ 4 บอกว่า LLM-as-judge ใช้ได้ก็ต่อเมื่อ calibrate แล้ว บทที่ 5 บอกว่า eval set ที่ดีต้องมาจากความล้มเหลวจริง สองข้อนี้ชี้ไปที่กิจกรรมเดียวกันที่คอร์สนี้ถือว่าเป็นหัวใจ — การนั่งอ่าน trace ที่ ops-copilot ทำงานจริง แล้วหาว่าอะไรพังจากอะไร ไม่ใช่การเปิด dashboard ดูตัวเลขรวมแล้วเดา บทนี้ว่าด้วยวินัยของการอ่าน trace ให้เป็นระบบ (error analysis), การใช้คนใน loop เพื่อจับสิ่งที่ automation มองข้าม และเหตุผลว่าทำไมเกณฑ์ “อะไรดี” ถึงเขียนให้ครบตั้งแต่วันแรกไม่ได้
ประโยคที่สรุปทั้งบท: judge คือเครื่องมือที่เรา calibrate แล้วต้องคอยเช็คซ้ำ ไม่ใช่ oracle ทุกเทคนิคในบทนี้มีไว้รักษาให้ loop eval ยังเชื่อมกลับหาสายตาคนอยู่เสมอ ไม่ปล่อยให้ตัวเลขจาก judge ลอยไปตัดสินเองลำพัง
สเก็ตช์ในบทนี้อ้างถึง ops-copilot ที่คืนเงินออเดอร์ใน domain Order จาก repo kaen-food-ordering (กำลังจัดทำ) — บทนี้เป็นคอร์สเชิงวิธีการ (methodology) สิ่งที่เห็นเป็น log ของ open coding, ตาราง confusion matrix และภาพวงจร ไม่ใช่ eval harness ที่รันได้ทันที ให้ใช้เป็นแบบร่างของกระบวนการ ไม่ใช่ code พร้อมรัน
error analysis ไม่ใช่ dashboard
หัวข้อที่มีชื่อว่า “error analysis ไม่ใช่ dashboard”จุดเริ่มของ eval ที่ดีไม่ใช่ metric บนหน้าจอ แต่คือ Error AnalysisError Analysisการนั่งอ่าน trace ที่ agent ทำงานจริงทีละเคสเพื่อหาว่าอะไรพังจากอะไร เน้นจับ 'ความผิดพลาดแรกสุด' ในแต่ละ trace ก่อนเสมอ เพราะความผิดพลาดต้นทางมักลากให้พังต่อเป็นทอด ๆ — เป็นจุดเริ่มต้นของ eval ที่ดี ไม่ใช่การมองแค่ dashboard ตัวเลขรวม และควรทำซ้ำทุก 2-4 สัปดาห์Process — การนั่งอ่าน trace ที่ agent ทำงานจริงทีละเคส เพื่อหาว่าอะไรพัง และพังจากอะไร Hamel Husain กับ Shreya Shankar ย้ำว่ากระบวนการนี้มาก่อน dashboard เสมอ เพราะตัวเลขรวม (เช่น “success rate 82%”) บอกแค่ว่าพังเท่าไร ไม่ได้บอกว่าพังแบบไหน และคุณแก้ 18% ที่พังไม่ได้ถ้ายังไม่รู้ว่ามันพังด้วยสาเหตุอะไรบ้าง (ดู Hamel/Shreya, “LLM Evals FAQ”)
กระบวนการมีสามจังหวะต่อกัน: รวบรวม trace ที่เป็นตัวแทนงานจริง → open coding → axial coding → วนกลับไปทำซ้ำจนอิ่มตัว จากนั้นค่อยเอาหมวดหมู่ที่ได้ไปตั้งเป็นเกณฑ์ให้ judge และวน error analysis ใหม่ทุก 2–4 สัปดาห์ ไม่ใช่ทำครั้งเดียวจบ
open coding กับ axial coding — คนละจังหวะ คนละงาน
หัวข้อที่มีชื่อว่า “open coding กับ axial coding — คนละจังหวะ คนละงาน”Open CodingOpen Codingเทคนิคจากงานวิจัยเชิงคุณภาพที่ยืมมาทำ Error Analysis — อ่าน trace ทีละเคสแล้วเขียนบรรยาย 'ความผิดพลาดแรกสุด' ที่เห็นด้วยคำพูดของตัวเอง โดยยังไม่ยึดหมวดหมู่ตายตัว จนกว่าจะไล่ดูราวร้อยเคสแล้วเริ่มไม่เจอแพทเทิร์นใหม่ จึงค่อยจัดกลุ่มเป็น Failure TaxonomyProcess คือเทคนิคที่ยืมมาจากงานวิจัยเชิงคุณภาพ: อ่าน trace ทีละเคส แล้วเขียนบรรยายความผิดพลาดแรกสุดที่เห็นด้วยคำพูดของตัวเอง โดยยังไม่ยึดหมวดหมู่ตายตัว จุดสำคัญคือคำว่า “แรกสุด” — ในระบบหลายขั้นตอน ความผิดพลาดต้นทางมักลากให้พังต่อเป็นทอดๆ ถ้า agent อ่าน orderId ผิดตั้งแต่ต้น ทุกก้าวถัดจากนั้นก็ผิดตาม การจดทุกอาการที่เห็นจะทำให้นับซ้ำเกินจริง จึงจดแค่ตัวจุดชนวนของแต่ละ trace
พอมี log ของความผิดพลาดแรกครบพอ จึงเข้าจังหวะที่สอง — axial coding: จัดกลุ่มบันทึกเหล่านั้นเป็นหมวดที่ตั้งชื่อได้ แล้วนับความถี่ของแต่ละหมวด ผลลัพธ์คือ failure taxonomy ที่บอกว่าควรลงมือแก้อะไรก่อน (หมวดที่โผล่บ่อยสุด = คุ้มค่าแก้สุด) พูดสั้นๆ: open coding เปิดกว้างไม่ตัดสิน สร้างหมวดจากสิ่งที่เห็นจริง ส่วน axial coding ปิดเข้าหมวดแล้วนับ — ลำดับต้องเป็นแบบนี้ ถ้าเริ่มด้วยหมวดสำเร็จรูปก่อนอ่าน คุณจะเห็นแต่สิ่งที่เตรียมใจไว้แล้ว และพลาดหมวดที่นึกไม่ถึง
รู้ได้อย่างไรว่าอ่านพอแล้ว? ใช้เกณฑ์ saturation เป็นกฎคร่าวๆ (rule of thumb ของ Hamel/Shreya): ถ้าอ่าน trace เพิ่มไปราว 20 เคสล่าสุดแล้วยังไม่โผล่หมวดใหม่เลย ก็หยุดได้ — แต่ให้เริ่มด้วยการอ่านอย่างน้อย 100 เคสก่อน อย่าเพิ่งสรุปตอนอ่านไปสิบเคส (ดู Hamel, “Why is error analysis so important…”)
// สเก็ตช์ log ของ open coding — ไม่ใช่ code ที่รันได้ เป็นตารางบันทึก// 'ความผิดพลาดแรกสุด' ของแต่ละ trace จาก ops-copilot ที่ไปคืนเงินจริงconst openCodingLog = [ { trace: "t-001", firstFailure: "เรียก tool refund ด้วย orderId ผิด — คืนเงินออเดอร์ #124 แทน #123" }, { trace: "t-002", firstFailure: "คืนถูกออเดอร์ แต่จำนวน ฿100 แทน ฿120 (อ่าน subtotal แทน total)" }, { trace: "t-003", firstFailure: "agent ตอบ 'คืนเงินเรียบร้อย' โดยไม่เคยเรียก tool refund เลย" }, // ... จดต่อจนครบ 100+ trace โดยจดแค่ 'ความผิดพลาดแรก' ของแต่ละอัน ไม่จดทุกอาการ];
// axial coding: จัดกลุ่ม log ข้างบนเป็นหมวดแล้วนับความถี่ → รู้ว่าจะแก้อะไรก่อนconst axialTally = { "อ่าน field จำนวนเงินผิด (subtotal/total)": 14, "orderId ผิดตัว": 11, "อ้างว่าทำแต่ไม่ได้เรียก tool": 7, // หมวดที่โผล่บ่อยสุดอยู่บนสุด = คุ้มค่าลงมือแก้ก่อน};สังเกต t-003: agent พิมพ์ว่า “คืนเงินเรียบร้อย” แต่ฐานข้อมูลไม่มีแถวคืนเงินเลย นี่คือเหตุผลที่ทั้งคอร์สยืนยันว่า outcome check ที่แท้จริงคือ “มีแถวคืนเงินของออเดอร์ #123 ในฐานข้อมูลจริง จำนวน ฿120 หรือยัง” ไม่ใช่ “agent บอกว่าทำเสร็จหรือยัง” — error analysis คือที่ที่ความต่างนี้โผล่มาให้เห็นกับตา
flowchart LR
T["เก็บ trace จริง<br/>(100+ เคสจาก ops-copilot)"] --> O["open coding<br/>จด 'ความผิดพลาดแรก' ของแต่ละ trace"]
O --> A["axial coding<br/>จัดหมวด + นับความถี่"]
A --> S{"อ่านเพิ่ม ~20 เคส<br/>เจอหมวดใหม่ไหม?"}
S -->|เจอ| O
S -->|"ไม่เจอ = saturation"| C["calibrate judge<br/>วัด TPR/TNR กับป้ายของคน"]
C --> R["กลับมาอ่าน transcript<br/>ทุก 2–4 สัปดาห์"]
R --> T
classDef human fill:#16a34a,stroke:#065f46,color:#f8fafc;
class O,R human;
คำบรรยายภาพ: วงจร error analysis ที่ไม่มีจุดจบ เก็บ trace จริง → open coding (จดความผิดพลาดแรก) → axial coding (จัดหมวด+นับ) → เช็ค saturation ถ้ายังเจอหมวดใหม่ก็วนกลับไปอ่านต่อ ถ้าอิ่มตัวแล้วจึงเอาหมวดที่ได้ไป calibrate judge ด้วย TPR/TNR แล้ววนกลับมาอ่าน transcript ใหม่ทุก 2–4 สัปดาห์ กล่องเขียวสองใบ (open coding และกลับมาอ่าน) คือจุดที่ต้องมีสายตาคนเสมอ วงจรนี้เชื่อม judge กลับหาคนตลอด ไม่ปล่อยให้ตัวเลขลอยไปตัดสินเอง
calibrate judge ด้วย TPR/TNR ไม่ใช่ raw agreement
หัวข้อที่มีชื่อว่า “calibrate judge ด้วย TPR/TNR ไม่ใช่ raw agreement”พอได้หมวดหมู่จาก error analysis แล้ว เราจึงเอาไปตั้งเป็นเกณฑ์ให้ LLM-as-judge ตรวจแทนคนในสเกลใหญ่ แต่ก่อนไว้ใจ judge ต้องผ่าน Judge CalibrationJudge Calibrationกระบวนการเช็คว่า LLM-as-judge ตัดสินตรงกับป้ายที่มนุษย์ผู้เชี่ยวชาญให้ไว้แค่ไหน ก่อนปล่อยให้ judge ตัดสินเองเต็มรูปแบบ — ต้องดูอัตราที่ judge จับได้ถูกทั้งฝั่ง 'ผ่านจริง' และ 'พังจริง' แยกกัน ไม่ใช่แค่ % ที่ตรงกับมนุษย์รวม ๆ เพราะถ้าเคสพังมีน้อย judge ที่ตอบ 'ผ่าน' มั่ว ๆ ก็ยังได้คะแนนสูงลวงตา และต้อง calibrate ซ้ำทุกครั้งที่เปลี่ยน model judge หรือ rubricProcess ก่อน — วัดว่ามันตัดสินตรงกับป้ายที่คนผู้เชี่ยวชาญให้ไว้บนชุดทดสอบที่กันไว้ (held-out) แค่ไหน และกับดักใหญ่ที่สุดตรงนี้คือการวัดด้วย raw agreement (% ที่ judge ตรงกับคนรวมๆ)
ทำไมมันเป็นกับดัก? สมมติเชิงตัวเลขเพื่อให้เห็นภาพ (ไม่ใช่ค่าที่วัดจากระบบจริง): ถ้าเคสพังเกิดแค่ 10% ของ traffic แล้ว judge ที่ขี้เกียจตอบ “ผ่าน” ทุกเคส จะได้ agreement สูงถึง 90% ทั้งที่มันจับ failure ไม่ได้เลยสักเคส —
| judge บอก “ผ่าน” | judge บอก “พัง” | |
|---|---|---|
| ผ่านจริง (90 เคส) | 90 | 0 |
| พังจริง (10 เคส) | 10 | 0 |
agreement = (90 + 0) / 100 = 90% ดูดีลวงตา แต่ TPR = จับพังได้ถูก / พังจริงทั้งหมด = 0 / 10 = 0% judge ตัวนี้ไร้ค่าโดยสิ้นเชิงสำหรับงานที่เราสร้างมันมาทำ นี่คือเหตุผลที่ Hamel/Shreya บอกให้วัดสองฝั่งแยกกัน — “Focus on achieving high True Positive Rate (TPR) and True Negative Rate (TNR) with your judge on a held out labeled test set.” TPR คืออัตราที่ judge จับ “เคสพังจริง” ได้ถูก และ TNR คืออัตราที่มันปล่อย “เคสผ่านจริง” ผ่านโดยไม่ตีตกผิด ทั้งสองต้องสูงพร้อมกัน raw agreement ตัวเดียวซ่อนความล้มเหลวนี้ไว้ได้เสมอเมื่อ failure เป็นของหายาก (ดู Hamel/Shreya, “LLM Evals FAQ”)
และย้ำจากบทที่ 4: judge ไม่ใช่ผู้ตัดสินที่เป็นกลางโดยอัตโนมัติ มันมีอคติแฝงที่วัดได้ (เอนตามลำดับที่เห็นก่อน ตามความยาวคำตอบ และชอบ output จาก model ตระกูลเดียวกับตัวเอง) การ calibrate ด้วย TPR/TNR จึงไม่ใช่พิธีกรรมครั้งเดียว — ต้องทำซ้ำทุกครั้งที่เปลี่ยน model judge หรือแก้ rubric judge คือเครื่องมือที่เรารักษาให้เที่ยงด้วยการเช็คซ้ำ ไม่ใช่ของที่ตั้งแล้วลืม
คนยังต้องอ่าน transcript — automation มองข้ามเสมอ
หัวข้อที่มีชื่อว่า “คนยังต้องอ่าน transcript — automation มองข้ามเสมอ”ต่อให้ judge ผ่าน calibration แล้ว ก็ยังต้องมี Human-in-the-loopHuman-in-the-loopการให้มนุษย์เข้ามาตรวจสอบหรือให้ป้ายในจุดสำคัญของกระบวนการ eval เช่น สุ่มตรวจผลตัดสินของ LLM-as-judge หรืออนุมัติก่อน agent ทำสิ่งที่ย้อนกลับไม่ได้ — มนุษย์ยังจับสิ่งที่ automation มองข้ามได้เสมอ (เช่น agent เลือกแหล่งข้อมูลที่ดู SEO ดีแต่ไม่น่าเชื่อถือ) จึงเป็นสิ่งที่รักษาคุณภาพ eval ไว้ไม่ให้พึ่งพา judge เพียงลำพังProcess — คนคอยอ่าน transcript จริงเป็นระยะ เพราะ judge ตรวจได้เฉพาะเกณฑ์ที่เราเขียนสั่งมันไว้แล้วเท่านั้น ความล้มเหลวชนิดที่เรายังนึกไม่ถึง มันมองไม่เห็น ตัวอย่างจริงจาก Anthropic: “human testers noticed that our early agents consistently chose SEO-optimized content farms over authoritative but less highly-ranked sources like academic PDFs or personal blogs” — agent เลือกแหล่งข้อมูลที่ทำ SEO เก่งแต่ไม่น่าเชื่อถือ แทนแหล่งที่น่าเชื่อถือกว่าอย่าง PDF วิชาการหรือ block ส่วนตัว นี่เป็นความล้มเหลวที่ไม่มี metric ไหนจับได้ เพราะไม่มีใครคิดจะเขียนเกณฑ์ “ห้ามชอบ content farm” ไว้ก่อน คนอ่านเจอ แล้วจึงกลายเป็นการแก้ prompt ที่เป็นรูปธรรม (ดู Anthropic, “How we built our multi-agent research system”)
นี่ไม่ใช่คำแนะนำจากฝั่งนักวิจัยเท่านั้น — คู่มือฝั่งวิศวกรของ Anthropic เองปิดท้ายด้วยประโยคสั้นๆ ประโยคเดียว: “Read the transcripts!” (ดู Anthropic, “Demystifying evals for AI agents”) สำหรับ ops-copilot ก็เช่นกัน — dashboard อาจบอกว่า success rate นิ่งดี แต่การนั่งอ่าน transcript สิบเคสอาจเจอว่า agent เริ่มถามยืนยันซ้ำซากจนลูกค้ารำคาญ หรือคืนเงินถูกจำนวนแต่ใช้ 8 ก้าวทั้งที่ควรใช้ 2 — สิ่งที่ automation ยังไม่ถูกสอนให้จับ
criteria drift — ทำไมเขียนเกณฑ์ให้ครบตั้งแต่วันแรกไม่ได้
หัวข้อที่มีชื่อว่า “criteria drift — ทำไมเขียนเกณฑ์ให้ครบตั้งแต่วันแรกไม่ได้”มีเหตุผลเชิงลึกกว่านั้นว่าทำไมคนถึงถอดออกจาก loop ไม่ได้ Shankar และคณะในงาน “Who Validates the Validators?” ตั้งชื่อปรากฏการณ์นี้ว่า Criteria DriftCriteria Driftปรากฏการณ์ที่เกณฑ์ 'อะไรคือคำตอบที่ดี' ค่อย ๆ ชัดขึ้นหรือเปลี่ยนไปเองระหว่างมนุษย์ตรวจเคสจริงไปเรื่อย ๆ เพราะคนเรามักรู้ว่า 'ดี' หมายถึงอะไรก็ต่อเมื่อได้เห็น output จริงจำนวนมากแล้วเท่านั้น ไม่ใช่คิดเกณฑ์ครบตั้งแต่วันแรก — เหตุผลที่ Rubric ต้องรักษาความสอดคล้องด้วยการปรับปรุงซ้ำได้เรื่อย ๆProcess: เกณฑ์ที่ว่า “อะไรคือคำตอบที่ดี” ค่อยๆ ชัดขึ้นและเปลี่ยนไประหว่างที่คนลงมือ grade เคสจริง ไม่ใช่สิ่งที่คิดครบได้ตั้งแต่วันแรก คนเรามักรู้ว่า “ดี” หมายถึงอะไรก็ต่อเมื่อได้เห็น output จริงจำนวนมากแล้วเท่านั้น — เห็นเคสที่ agent คืนเงินก่อนขออนุมัติผู้จัดการ ถึงจะนึกได้ว่า “อ๋อ นี่ก็เป็นเกณฑ์ที่ต้องมี” (ดู Shankar et al., “Who Validates the Validators?” (arXiv 2404.12272))
นัยของ criteria drift คือ rubric ต้องเป็นของที่แก้ไขซ้ำได้เรื่อยๆ ไม่ใช่เขียนทิ้งไว้ตายตัว และวงจร error analysis ต้องวนไม่หยุด เพราะทุกครั้งที่คนอ่าน trace ใหม่ เกณฑ์อาจคมขึ้นอีก ซึ่งแปลว่าต้องกลับไป calibrate judge ใหม่ด้วยเกณฑ์ที่คมขึ้นนั้น — วงจรในภาพด้านบนจึงไม่มีปุ่ม “เสร็จแล้ว”
error analysis คือหัวใจของคอร์สนี้เพราะมันคือจุดที่ทุกอย่างเชื่อมกัน: อ่าน trace จริงด้วย open coding เพื่อจับความผิดพลาดแรก, จัดหมวดและนับด้วย axial coding จนอิ่มตัว, เอาหมวดที่ได้ไป calibrate judge โดยวัด TPR/TNR ไม่ใช่ raw agreement, แล้วยังต้องมีคนคอยอ่าน transcript เพราะทั้ง judge และ dashboard มองข้ามความล้มเหลวที่เรายังไม่ได้เขียนเกณฑ์ไว้ และเพราะ criteria drift ทำให้เกณฑ์ยังงอกใหม่ได้เสมอ judge จึงเป็นเครื่องมือที่เรา calibrate แล้วคอยเช็คซ้ำ ไม่ใช่ oracle ที่ปลดคนออกจาก loop ได้ บทที่ 7 จะเอาวินัยนี้ไปวางบนสองสนาม — offline eval ก่อนปล่อย กับ online eval บนโปรดักชันจริง
บทนี้สังเคราะห์จากแหล่งหลักด้านล่าง อ่านต่อได้ที่ต้นทางโดยตรง:
- Hamel/Shreya, “LLM Evals FAQ” — error analysis มาก่อน dashboard, saturation และการ calibrate judge ด้วย TPR/TNR แทน raw agreement
- Hamel, “Why is error analysis so important…” — ขั้นตอน open coding → axial coding และกฎ ~20/100 เคส
- Anthropic, “Demystifying evals for AI agents” — ประโยคปิดท้าย “Read the transcripts!”
- Anthropic, “How we built our multi-agent research system” — คนจับ bias เลือก SEO content farm ที่ automation มองข้าม
- Shankar et al., “Who Validates the Validators?” (arXiv 2404.12272) — criteria drift: รู้ว่า “ดี” คืออะไรจากการ grade จริง
เช็กความเข้าใจ — บทที่ 6
ข้อ 1 / 3open coding กับ axial coding ต่างกันอย่างไรในกระบวนการ error analysis?