Offline suite + online monitoring — สองด่านที่ปิดเป็นวง
บท 5 กับ 6 สร้างของสองอย่างขึ้นมา: golden dataset ที่มาจากความล้มเหลวจริง กับวินัย error analysis ที่นั่งอ่าน trace ทีละเคส คำถามของบทนี้คือ eval พวกนั้น “รัน” ที่ไหน คำตอบมีสองที่ และมันไม่ใช่ทางเลือกแทนกัน — ที่หนึ่งคือก่อนปล่อย version ใหม่ อีกที่คือบน production ที่ ops-copilot กำลังคืนเงินให้ลูกค้าจริงอยู่ตอนนี้ บทนี้ว่าด้วยการวางทั้งสองด่านให้ทำงานร่วมกัน แล้วปิดมันเป็นวง
สเก็ตช์ในบทนี้อ้างถึง ops-copilot agent ที่คืนเงินออเดอร์ใน domain Order เดิมจาก repo kaen-food-ordering (กำลังจัดทำ) — เป็นคอร์สเชิงออกแบบ/ระเบียบวิธี code ที่เห็นเป็นภาพร่างของโครงสร้าง regression case, config การสุ่มให้คะแนน และ topology ของวงปิด ไม่ใช่ eval harness ที่รันได้ทันที
offline eval คือประตูก่อน ship
หัวข้อที่มีชื่อว่า “offline eval คือประตูก่อน ship”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 ได้
regression suite — ของที่ต้องไม่พัง
หัวข้อที่มีชื่อว่า “regression suite — ของที่ต้องไม่พัง”ในกอง 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 eval คือการเฝ้า production สด
หัวข้อที่มีชื่อว่า “online eval คือการเฝ้า production สด”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 ที่ไปแตะเงินจริง ความเสี่ยงสูงพอจะทำให้สุ่มในสัดส่วนที่สูงกว่าปกติได้อย่างสมเหตุสมผล
2 target ที่ดูขัดกัน แต่ไม่ขัด
หัวข้อที่มีชื่อว่า “2 target ที่ดูขัดกัน แต่ไม่ขัด”มาถึงจุดที่คนสับสนบ่อย: 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 (ควรมีแดงให้เห็นบ้าง) อย่าเอามารวมเป็นเลขเดียวแล้วมานั่งงงว่าทำไมเดี๋ยวต้องสูง เดี๋ยวต้องต่ำ
Drift — ทำไม eval ครั้งเดียวไม่จบ
หัวข้อที่มีชื่อว่า “Drift — ทำไม 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 ครั้งเดียวจบแล้วเลิก
ปิดวง — จาก production กลับเข้า offline
หัวข้อที่มีชื่อว่า “ปิดวง — จาก production กลับเข้า offline”ชิ้นสุดท้ายที่ทำให้สองด่านนี้กลายเป็นระบบเดียว คือ การปิดวง: 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 — ไม่มีเฉลย ให้คะแนนแบบ asyncconst 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:
- Continuous Integration — วินัยรันชุดทดสอบอัตโนมัติทุกครั้งก่อน merge/ship ที่ offline suite ของบทนี้เข้าไปสวมร่างเป็นด่าน eval แทนด่าน test
- Observability — การมองเห็นสิ่งที่ระบบทำจริงบน production ซึ่งเป็นรากของ online eval: เฝ้า trace สดเพื่อจับปัญหาที่ offline suite ไม่เคยครอบคลุม
เช็กความเข้าใจ — บทที่ 7
ข้อ 1 / 3offline eval กับ online eval ต่างกันหลักๆ ที่อะไร?