ข้าม​ไป​ยัง​เนื้อหา

Offline suite + online monitoring — สอง​ด่าน​ที่​ปิด​เป็น​วง

บท 5 กับ 6 สร้าง​ของ​สอง​อย่าง​ขึ้น​มา: golden dataset ที่มา​จาก​ความ​ล้มเหลว​จริง กับ​วินัย error analysis ที่นั่ง​อ่าน trace ที​ละ​เคส คำถาม​ของ​บท​นี้​คือ eval พวก​นั้น “รัน” ที่ไหน คำ​ตอบ​มี​สอง​ที่ และ​มัน​ไม่ใช่​ทาง​เลือก​แทน​กัน — ที่​หนึ่ง​คือ​ก่อน​ปล่อย version ใหม่ อีก​ที่​คือ​บน production ที่ ops-copilot กำลัง​คืน​เงิน​ให้​ลูกค้า​จริง​อยู่​ตอน​นี้ บท​นี้​ว่าด้วย​การ​วาง​ทั้ง​สอง​ด่าน​ให้​ทำงาน​ร่วม​กัน แล้ว​ปิด​มัน​เป็น​วง

📦 code ตัวอย่าง (กำลัง​จัด​ทำ)

สเก็ตช์​ใน​บท​นี้​อ้าง​ถึง ops-copilot agent ที่​คืน​เงิน​ออเดอร์​ใน domain Order เดิม​จาก repo kaen-food-ordering (กำลัง​จัด​ทำ) — เป็น​คอร์ส​เชิง​ออกแบบ/ระเบียบ​วิธี code ที่​เห็น​เป็น​ภาพร่าง​ของ​โครงสร้าง regression case, config การ​สุ่ม​ให้​คะแนน และ topology ของวง​ปิด ไม่ใช่ eval harness ที่​รัน​ได้​ทันที

Offline EvalOffline Evalการ​วัดผล agent กับ Golden Dataset ที่​คัด​ไว้​ล่วงหน้า (มัก​มี Ground Truth ประกอบ) ก่อน​ปล่อย​แต่ละ version จริง เหมือน​ยูนิต test/integration test ของ code ทั่วไป รัน​ซ้ำ​ได้​ผล​เดิม​ทุก​ครั้ง​และ​ใช้​ดัก​ไม่​ให้ version ใหม่​แย่​ลง​กว่า​เดิมProcess คือ​การ​รัน agent กับ dataset ที่​คัด​ไว้​ล่วงหน้า — golden dataset จาก​บท 5 ที่​มัก​มี​เฉลย (expected output) ประกอบ — ก่อน​ปล่อย version ใหม่​ออก​ไป LangSmith ลาก​เส้น​แบ่ง​ไว้​ที่ “ข้อมูล + เฉลย”: offline eval รัน​กับ dataset ที่​จัด​ไว้ มี​เฉลย​ให้​เทียบ (จะ​มี​หรือ​ไม่มี​ก็ได้) ทำ​ก่อน ship เพื่อ​ดัก regression และ​เทียบ version เก่า​กับ​ใหม่ เทียบ​ได้​กับ “like unit/integration tests” (ดู LangSmith, “Evaluation concepts”) มัน​คือ eval ที่​คุม​สภาพ​แวดล้อม​ได้: input ชุด​เดิม เกณฑ์​ชุด​เดิม รัน​กี่​รอบ​ก็​ให้​ผล​เทียบ​กัน​ได้

เคส offline ของ ops-copilot หน้าตา​เหมือน​ที่​บท 1 วาง​ไว้​เป๊ะ: ป้อน​คำ​สั่ง “คืน​เงิน​ออเดอร์ #123 จำนวน ฿120” แล้ว​เกณฑ์​ผ่าน​คือ มี​แถว​คืน​เงิน​ของออเดอร์​นั้น​ใน​ฐาน​ข้อมูล​จริง จำนวน ฿120 — ไม่ใช่​เช็ค​ว่า agent พิมพ์​ว่า “คืน​เงิน​เรียบร้อย” นี่​คือ outcome ที่​ตรวจสอบ​ได้​จริง และ​เพราะ​มัน​รัน​ใน​สภาพ​แวดล้อม​ที่​เรา​คุม เรา​จึง​เอา​ไป​ตั้ง​เป็น​ด่าน​ตายตัว​ใน CI ได้

ใน​กอง offline eval มี​ชุด​ย่อย​ที่​สำคัญ​เป็น​พิเศษ: Regression SuiteRegression Suiteชุด Offline Eval ของ​เคส​ที่ agent เคย​ทำ​ถูก​อยู่​แล้ว ใช้​รักษา​มาตรฐาน​เดิม​ไว้​ไม่​ให้ version ใหม่​ทำ​พัง รัน​ก่อน​ปล่อย​ทุก​ครั้ง — ควร​ผ่าน​เกือบ 100% เสมอ ต่าง​จาก​ชุด eval เคส​ใหม่/เคส​ยาก​ที่​ถ้า​ผ่าน 100% แปล​ว่า​ยัง​ท้าทาย agent ไม่​พอProcess — เคส​ที่ agent เคย​ทำ​ถูก​อยู่​แล้ว เรา​เก็บ​ไว้​เพื่อ​รักษา​ไม่​ให้ version ใหม่​ทำ​พัง​ของ​เก่า Anthropic ระบุ target ของ​มัน​ชัด: regression eval “should have a nearly 100% pass rate” (ดู Anthropic, “Demystifying evals for AI agents”) เพราะ​มัน​คือ​ของ​ที่ “ต้อง​ไม่​พัง” — ถ้า​เคส​คืน​เงิน​เบสิก​ที่​เคย​ผ่าน​มา​ตลอด​จู่ๆ ตก​หลัง​แก้ prompt นั่น​คือ regression ที่​ต้อง block ไม่​ให้ ship ไม่ใช่​ปล่อย​ผ่าน

Online EvalOnline Evalการ​วัดผล agent จาก​การ​ใช้งาน​จริง​บน production แบบ​ต่อ​เนื่อง โดย​ไม่มี​คำ​ตอบ​อ้างอิง​ตายตัว​ให้​เทียบ เหมือน​งาน monitoring หรือ canary testing — จับ​ปัญหา​จาก query จริง​ที่ offline eval ไม่​เคย​เจอ แต่​ข้อ​เสีย​คือ​กว่า​จะ​รู้​ปัญหา​ก็​มัก​มี​ผู้​ใช้​เจอ​ไป​ก่อน​แล้ว เคส​ที่​คะแนน​ต่ำ​ควร​ถูก​ป้อน​กลับ​ไป​เป็น​เคส​ใหม่​ใน Golden DatasetProcess คือ​การ​วัด agent จาก​การ​ใช้งาน​จริง​บน production แบบ​ต่อ​เนื่อง จุด​ที่​ต่าง​จาก offline แบบ​ขาดกัน​คือ ไม่มี​เฉลย — query ที่​ลูกค้า​พิมพ์​เข้า​มาสดๆ ไม่มี​ใคร​เขียน expected output ไว้​ก่อน LangSmith เทียบ​มัน​กับ “like live monitoring or canary testing” (ดู LangSmith, “Evaluation concepts”) เรา​ไม่​ได้​ถาม​ว่า “ตรง​เฉลย​ไหม” เพราะ​ไม่มี​เฉลย เรา​ถาม​ว่า “ดู​มี​ปัญหา​ไหม” จาก trace จริง​ที่​ไหล​ผ่าน ข้อดี​คือ​มัน​จับ​ปัญหา​จาก query จริง​ที่ offline suite ไม่​เคย​เจอ ข้อ​เสีย​คือ​มัน​เป็น​ด่าน​ตั้ง​รับ — กว่า​จะ​รู้​ว่า​พัง มัก​มี​ผู้​ใช้​เจอ​ไป​ก่อน​แล้ว

พอ​ไม่มี​เฉลย การ​ให้​คะแนน online จึง​มัก​พึ่ง LLM-as-judge หรือ heuristic เป็น​ตัว​ตรวจ และ​ตรง​นี้​คือ​จุด​ที่​ต้อง​ระวัง​ที่สุด: judge ไม่ใช่ oracle ที่​เชื่อ​ได้​ทันที มัน​มี​อคติ​แฝง​ที่​วัด​ได้ (เอน​ตาม​ลำดับ​ที่​เห็น​ก่อน ตาม​ความ​ยาว​คำ​ตอบ และ​ชอบ​งาน​จาก model ตระกูล​เดียว​กับ​ตัวเอง) เรา​จึง​ต้อง calibrate มัน​ด้วย TPR/TNR เทียบ​กับ​ป้าย​ที่​มนุษย์​ให้ ไม่ใช่​ดู % agreement ดิบๆ (บท 4 กับ 6 ว่า​ไว้​เต็มๆ) และ​ต่อ​ให้ calibrate แล้ว มนุษย์​ก็​ยัง​จับ​สิ่ง​ที่ judge มอง​ข้าม​ได้​เสมอ — online monitoring ที่​ดี​จึง​ยัง​ต้อง​มี​คน​สุ่ม​อ่าน transcript อยู่ ไม่ใช่​ยก​ให้ judge เฝ้า​คน​เดียว​แล้ว​ถือว่า​จบ

การ​ให้​คะแนน​ทุก trace บน production ที่ทรา​ฟิก​สูง​เป็น​เรื่อง​แพง​และ​อาจ​ถ่วง latency Braintrust จึง​แนะ​ให้​รัน​แบบ async และ​สุ่ม​เอา โดย​เคลม​ว่า “no impact on latency” พร้อม​กฎ​นิ้ว​โป้ง​ว่า​ให้​สุ่ม​ราว 1–10% สำหรับ app ทรา​ฟิก​สูง และ​ขยับ​ขึ้น​ไป​ถึง 100% สำหรับ app ทรา​ฟิก​ต่ำ​หรือ​ความ​เสี่ยง​สูง (ดู Braintrust, “Evaluate”) ย้ำ​ว่า​ตัวเลข​สัดส่วน​พวก​นี้​เป็น rule of thumb ของ vendor เจ้า​เดียว ไม่ใช่​มาตรฐาน​อุตสาหกรรม — ใช้​เป็น​จุด​ตั้งต้น ไม่ใช่​ตัวเลข​ศักดิ์สิทธิ์ สำหรับ ops-copilot ที่​ไป​แตะ​เงิน​จริง ความ​เสี่ยง​สูง​พอ​จะ​ทำให้​สุ่ม​ใน​สัดส่วน​ที่​สูง​กว่า​ปกติ​ได้​อย่าง​สม​เหตุ​สม​ผล

มา​ถึง​จุด​ที่​คน​สับสน​บ่อย: regression suite ต้อง​ผ่าน​เกือบ 100% แต่ eval อีก​แบบ​กลับ​บอกว่า​ถ้า​ผ่าน 100% แปล​ว่า “ยัง​ไม่​ดี​พอ” สอง​อย่าง​นี้​ขัด​กัน​ไหม — ไม่​ขัด เพราะ​มัน​คนละ​ชนิด eval

regression suite วัด ของ​ที่​แก้​จบ​แล้ว — เคส​ที่​รู้​คำ​ตอบ เคย​ทำ​ถูก การ​ผ่าน​เกือบ 100% จึง​เป็น​เรื่อง​ถูกต้อง ส่วน eval เคส​ใหม่​หรือ​เคส​ยาก​ที่​เรา​ตั้งใจ​ออกแบบ​มา​ท้าทาย agent เป็น​คนละ​เรื่อง Hamel เตือน​ว่า “If you’re passing 100% of your evals, you’re likely not challenging your system enough. A 70% pass rate might indicate a more meaningful evaluation.” — ถ้า eval ชุด​ใหม่​ผ่าน​หมด 100% นั่น​แปล​ว่า​มัน​ง่าย​เกิน​ไป ไม่​ได้​ทดสอบ​อะไร ตัวเลข​ราว 70% มัก​มี​ความหมาย​มากกว่า เพราะ​มัน​ยัง​เหลือ​ที่​ให้​เห็น​ว่า agent พัง​ตรง​ไหน (ดู Hamel, “LLM Evals FAQ”)

ย้ำ​ให้​ชัด: “70%” ไม่ใช่ target ที่​ต้อง​เล็ง​ให้​โดน — มัน​เป็น rule of thumb ว่า “eval ที่​ดี​ควร​เหลือ​ที่​ให้ fail” ต่างหาก ตัวเลข​จริง​ขึ้น​กับ​ว่า​คุณ​ออกแบบ​เคส​ยาก​แค่​ไหน กฎ​ที่​ใช้ได้​จริง​คือ แยก​สอง​ชุด​ออก​จาก​กัน​ให้​ขาด: regression (ต้อง​เขียว​เกือบ​หมด) กับ frontier eval (ควร​มี​แดง​ให้​เห็น​บ้าง) อย่า​เอา​มา​รวม​เป็น​เลข​เดียว​แล้ว​มา​นั่ง​งง​ว่า​ทำไม​เดี๋ยว​ต้อง​สูง เดี๋ยว​ต้อง​ต่ำ

DriftDriftอาการ​ที่​พฤติกรรม​หรือ​การกระจาย​ของ query จริง​ค่อย ๆ เปลี่ยน​ไป​จาก​ตอน​ที่​เคย eval ผ่าน​ไว้ เช่น หลัง​ผู้​ให้​บริการ​อัปเดต model หรือ​ผู้​ใช้​เริ่ม​ถาม​คำถาม​แบบ​ใหม่​ที่ Golden Dataset ไม่​เคย​ครอบคลุม — เหตุผล​ที่​ต้อง​มี Regression Suite และ Online Eval รัน​ต่อ​เนื่อง ไม่ใช่ eval ครั้ง​เดียว​จบProcess คือ​อาการ​ที่​การกระจาย​ของ query จริง​บน production ค่อยๆ เคลื่อน​ไป​จาก​ตอน​ที่​เรา​ออกแบบ golden dataset ไว้ — ลูกค้า​เริ่ม​ถาม​แบบ​ใหม่​ที่ dataset ไม่​เคย​ครอบคลุม หรือ​ผู้​ให้​บริการ model อัปเดต​เบื้องหลัง​จน​พฤติกรรม​ขยับ ตัวอย่าง​กับ ops-copilot: เดิม dataset มี​แต่​เคส “คืน​เงิน​เต็ม​จำนวน” แล้ว​วัน​หนึ่ง​ลูกค้า​เริ่ม​ขอ “คืน​เงิน​บาง​ส่วน” หรือ “คืน​เป็น​เครดิต​ใน app” มาก​ขึ้น​เรื่อยๆ — offline suite เดิม​ไม่มี​ทาง​จับ​ได้​เลย เพราะ​มัน​ไม่​เคย​มี​เคส​แบบ​นั้น​อยู่​ใน​ชุด online eval คือ​ด่าน​เดียว​ที่​เห็น drift แบบ​นี้ เพราะ​มัน​มอง​ของ​จริง​ที่​ไหล​ผ่าน​อยู่

บท​นี้​อธิบาย drift ไว้​เชิง​คุณภาพ​เท่านั้น — “query จริง​เปลี่ยนหน้า​ไป​จาก​ที่ eval ครอบคลุม” — ไม่​ลง​ลึก​วิธี​วัด​เชิง​สถิติ เพราะ​นั่น​เป็น​คนละ​เรื่อง​และ​ไม่ใช่​แก่น​ของ​บท​นี้ สิ่ง​ที่​ต้อง​จำ​คือ: drift เป็น​เหตุผล​ว่า​ทำไม​ทั้ง regression suite และ online eval ต้อง​รัน​ต่อ​เนื่อง ไม่ใช่ eval ครั้ง​เดียว​จบ​แล้ว​เลิก

ชิ้น​สุดท้าย​ที่​ทำให้​สอง​ด่าน​นี้​กลาย​เป็น​ระบบ​เดียว คือ การ​ปิด​วง: trace ที่​ได้​คะแนน​ต่ำ​จาก online eval ไม่​ควร​จบ​แค่​เป็น​ตัวเลข​บน dashboard — มัน​ควร​ถูก​หยิบ​กลับ​มา​เป็น เคส​ใหม่​ใน golden dataset เมื่อ ops-copilot เจอ query แบบ​ใหม่​บน production แล้ว​ทำ​พัง (คืน​เงิน​ผิด​จำนวน หรือ​คืน​ผิด​ออเดอร์) เคส​นั้น​แหละ​คือ​วัตถุดิบ​ชั้น​ดี​ของ offline suite รอบ​ถัด​ไป เพราะ​มัน​คือ​ความ​ล้มเหลว​จริง ไม่ใช่​เคส​ที่​เรา​จินตนาการ​ขึ้น (ตรง​กับ​หลัก​บท 5 ที่​ว่า eval ต้อง​มา​จาก​ของ​จริง) Braintrust วาง​แพทเทิร์น​นี้ไว้ตรงๆ ว่า online scoring บน sampled traffic ป้อน​ตัวอย่าง​คะแนน​ต่ำ​กลับ​เข้า dataset (ดู Braintrust, “Evaluate”)

// สเก็ตช์เพื่อสื่อโครงสร้าง ไม่ใช่ code ที่รันได้จริง
// เคสหนึ่งใน regression suite (offline) — มีเฉลยชัดเจน ต้องผ่านเกือบ 100%
const regressionCase = {
input: "คืนเงินออเดอร์ #123 จำนวน ฿120",
// เกณฑ์ผ่าน = สถานะจริงของ DB ไม่ใช่คำพูดของ agent
assertOutcome: (db) => {
const row = db.refunds.findOne({ orderId: 123 });
return row != null && row.amount === 120;
},
};
// การตั้งค่า online scoring บน production — ไม่มีเฉลย ให้คะแนนแบบ async
const onlineScoring = {
sampleRate: 0.1, // สุ่ม ~1–10% (rule of thumb ของ vendor ไม่ใช่มาตรฐาน)
runAsync: true, // ให้คะแนนนอก request path จึงไม่ถ่วง latency
scorer: "llm-judge-calibrated", // ต้องผ่าน TPR/TNR กับป้ายมนุษย์ก่อน ไม่ใช่ oracle
onLowScore: "queue -> golden dataset", // ปิดวง: ป้อนกลับเป็นเคส offline ใหม่
};

พอ​ปิด​วง​ได้ drift ที่ online จับ​ได้​ก็​ไหล​กลับ​ไป​อุด offline suite เอง — เคส “คืน​เงิน​บาง​ส่วน” ที่​เพิ่ง​โผล่​บน production กลาย​เป็น regression case ที่ version หน้า​ต้อง​ผ่าน วงจร​นี้​แหละ​ที่​ทำให้ eval ไม่​แก่​ตาย​ไป​พร้อม dataset ของ​วัน​แรก

flowchart TB
  subgraph OFF["ก่อน ship — offline eval (ใน CI)"]
    G["golden dataset<br/>เคสคัดไว้ + เฉลย"]
    RS["regression suite<br/>เคสที่เคยทำถูก — ต้องผ่านเกือบ 100%"]
    NE["eval เคสใหม่/เคสยาก<br/>ผ่าน ~70% ถือว่ามีความหมาย"]
    G --> RS
    G --> NE
    RS --> GATE{"ผ่านด่านไหม?"}
    NE --> GATE
  end
  GATE -->|ไม่ผ่าน| FIX["แก้ก่อน ไม่ปล่อย"]
  GATE -->|ผ่าน| SHIP["ปล่อย version ใหม่ขึ้น production"]
  subgraph ON["บน production — online eval"]
    SHIP --> MON["เฝ้า trace จริงต่อเนื่อง<br/>ไม่มีเฉลย สุ่ม 1–10% มาให้คะแนน"]
    MON --> LOW["trace คะแนนต่ำ<br/>เช่น คืนเงินผิดจำนวนกับ query แบบใหม่ (drift)"]
  end
  LOW -->|ป้อนกลับเป็นเคสใหม่| G
  classDef gate fill:#f59e0b,stroke:#7c2d12,color:#1c1917;
  classDef bad fill:#dc2626,stroke:#7f1d1d,color:#f8fafc;
  class GATE gate;
  class FIX bad;

คำ​บรรยาย​ภาพ: สอง​ด่าน​ที่​ปิด​เป็น​วง​เดียว ด้าน​บน (offline ใน CI) golden dataset แตก​เป็น regression suite ที่​ต้อง​ผ่าน​เกือบ 100% กับ eval เคส​ใหม่​ที่​ตั้งใจ​ให้​ยาก ทั้ง​คู่​ผ่าน​ด่าน​ก่อน​ถึง​จะ ship ได้ ไม่​ผ่าน​คือ​หยุด​แก้ ด้าน​ล่าง (online บน production) เฝ้า trace จริง​ที่​ไม่มี​เฉลย สุ่ม​บาง​ส่วน​มา​ให้​คะแนน trace คะแนน​ต่ำ — รวม​ทั้ง​ของ​ที่มา​จาก drift — ถูก​ป้อน​กลับ​เข้า golden dataset เป็น​เคส​ใหม่ (เส้น​ล่าง​สุด) วง​นี้​แหละ​ที่​ทำให้ offline suite โต​ตาม​ของ​จริง​บน production แทนที่​จะ​แก่​ตาย​ไป​พร้อม dataset วัน​แรก

eval อยู่​สอง​ที่​และ​ทำงาน​คนละ​หน้าที่: offline วัด​กับ dataset ที่​คุม​ได้​ก่อน ship (ด่าน CI, มี​เฉลย, regression ต้อง​เกือบ 100%) ส่วน online เฝ้า production สด​ที่​ไม่มี​เฉลย (ด่าน​ตั้ง​รับ, จับ drift, มัก​พึ่ง judge ที่​ต้อง calibrate) 2 target ที่​ดู​ขัด​กัน — regression ~100% กับ frontier eval ~70% — ไม่​ขัด​เพราะ​เป็น​คนละ​ชนิด eval และ​สิ่ง​ที่​ทำให้​ทั้ง​สอง​เป็น​ระบบ​เดียว​คือ​การ​ปิด​วง: ป้อน trace คะแนน​ต่ำ​จาก production กลับ​เข้า offline set บท​หน้า​ปิด​คอร์ส​ด้วย​ข้อ​ถกเถียง​ที่​ยัง​ไม่​จบ — ควร “เขียน eval ก่อน​สร้าง​ความ​สามารถ” (eval-driven development) หรือ​ไม่


🔗 อ้างอิง​เพิ่มเติม​ใน DevIQ

เจาะ​ลึก​แนวคิด​ที่​บท​นี้​ยืน​อยู่​บน​ได้ที่​คลัง​อ้างอิง DevIQ:

  • Continuous Integration — วินัย​รัน​ชุด​ทดสอบ​อัตโนมัติ​ทุก​ครั้ง​ก่อน merge/ship ที่ offline suite ของ​บท​นี้​เข้าไป​สวม​ร่าง​เป็น​ด่าน eval แทน​ด่าน test
  • Observability — การ​มอง​เห็น​สิ่ง​ที่​ระบบ​ทำ​จริง​บน production ซึ่ง​เป็น​ราก​ของ online eval: เฝ้า trace สด​เพื่อ​จับ​ปัญหา​ที่ offline suite ไม่​เคย​ครอบคลุม

เช็กความเข้าใจ — บทที่ 7

ข้อ 1 / 3

offline eval กับ online eval ต่างกันหลักๆ ที่อะไร?