การใช้เวลาแปดเดือนใน GitHub Actions merge queue สอนอะไรบางอย่างที่ตารางเปรียบเทียบฟีเจอร์ไม่มีวันบอกคุณได้ เฟรมเวิร์กหนึ่งอาจมาพร้อมกับเมทริกซ์ถึง 50 แบบ แดชบอร์ดที่สวยงาม และการอ้างอิงจากห้องแล็บวิจัยที่มีชื่อเสียง แต่ถ้ามันขัดขวางการ deploy ของคุณเพียงเพราะคะแนน "vibe check" เปลี่ยนจาก 0.72 เป็น 0.68 ทั้งที่ใช้โค้ดชุดเดิม มันก็แย่ยิ่งกว่าไร้ประโยชน์เสียอีก มันจะกลายเป็นอุปสรรคสำคัญต่อความเร็วในการส่งมอบงาน (shipping velocity) ของคุณ
นั่นคือตัวกรองที่บทสรุปการประเมิน LLM ส่วนใหญ่มักมองข้าม พวกเขานับแค่ความสามารถ แต่แทบไม่เคยตั้งคำถามสำคัญเพียงข้อเดียวสำหรับ merge queue เลยว่า: การตรวจสอบนี้ผ่านหรือล้มเหลวด้วยวิธีเดิมเป๊ะๆ ทุกครั้งที่รันหรือไม่?
ผมเรียนรู้เรื่องนี้จากการลงมือทำในสิ่งที่น่าลำบากใจ ผมได้เชื่อมต่อ LLM eval frameworks แบบ open-source 6 ตัวเข้ากับ CI pipeline จริงๆ พวกมันถูกรันกับ pull requests ใน production จริงๆ เป็นเวลาแปดเดือน มีเพียงสองตัวเท่านั้นที่ได้รับสิทธิ์ให้ทำหน้าที่เป็น gatekeepers ส่วนที่เหลือถูกลดระดับไปเป็นเพียงแดชบอร์ดสำหรับให้คำแนะนำ ถูกย้ายไปรันใน nightly jobs หรือถูกถอดออกไปเลย บทเรียนนี้ทั้งเจ็บปวดและมีราคาแพง: โครงสร้างแบบ deterministic (ที่คาดเดาผลลัพธ์ได้แน่นอน) ชนะคุณภาพแบบ probabilistic (ที่อาศัยความน่าจะเป็น) เมื่อคุณต้องทำหน้าที่เฝ้า main branch
หน้าที่ที่แท้จริงของ Merge Gate
CI gate ไม่ใช่สภาพแวดล้อมสำหรับการวิจัย แต่มันคือการ์ด (bouncer) หน้าประตู จุดประสงค์ทั้งหมดของมันคือการดูการเปลี่ยนแปลงที่เฉพาะเจาะจงแล้วตอบว่า ใช่ หรือ ไม่ ใช่ PR นี้สามารถรวมเข้ากับ main branch ได้ หรือ ไม่ มันทำไม่ได้ คำตอบนั้นต้องมาถึงภายในไม่กี่วินาที มีต้นทุนเพียงเล็กน้อย และต้องไม่เปลี่ยนผลลัพธ์ย้อนหลัง หากคุณรัน pipeline เดิมกับ commit เดิมในวันอังคารที่เงียบสงบเทียบกับวันศุกร์ที่วุ่นวาย ผลลัพธ์จะต้องเหมือนกันทุกประการ
นี่คือจุดที่ LLM eval frameworks ส่วนใหญ่พลาด พวกมันถูกสร้างโดย data scientist เพื่อ data scientist โดยเน้นการเพิ่มประสิทธิภาพด้านข้อมูลเชิงลึก (insight) การสำรวจ (exploration) และการให้คะแนนที่ละเอียดอ่อน (nuanced scoring) แต่ merge queue เน้นการตัดสินใจแบบ binary (ใช่/ไม่ใช่), ความเร็ว และความเสถียรที่ไม่มีความไม่แน่นอน (zero flakiness) ซึ่งเป้าหมายทั้งสองนี้ทับซ้อนกันเพียงบางส่วนเท่านั้น
ทำไม LLM-as-Judge ถึงทำให้ Queue พัง
เครื่องมือที่ล้มเหลวในการทดสอบของผมมีข้อผิดพลาดในการออกแบบอย่างหนึ่งที่เหมือนกัน นั่นคือ พวกมันพึ่งพาการเรียกใช้ LLM-as-judge มากเกินไปในฐานะกลไกหลักในการเป็น gate
Prompt ของ LLM-as-judge จะสั่งให้โมเดลให้คะแนนผลลัพธ์ตั้งแต่หนึ่งถึงสิบ, เลือกคำตอบที่ดีกว่าจากสองคำตอบ หรือประเมินความถูกต้องของข้อเท็จจริง วิธีนี้มีประสิทธิภาพในการทำความเข้าใจแนวโน้มของคุณภาพ แต่มันคือยาพิษสำหรับการทำ blocking CI check Input เดียวกันอาจให้คะแนนต่างกันในแต่ละวัน เพราะค่า temperature, เวอร์ชันของโมเดล และรูปแบบของ prompt ล้วนสร้าง noise (ความไม่แน่นอน) เมื่อคะแนนนั้นถูกผูกไว้กับเกณฑ์ (threshold) ที่ตายตัวและ exit code ที่เข้มงวด คิวของคุณก็จะถูกบล็อกด้วยสิ่งที่ไม่มีตัวตน (blocks on ghosts)
ความล้มเหลวจะเกิดขึ้นต่อเนื่องอย่างรวดเร็ว การตรวจสอบแบบ nondeterministic ทำให้เกิดคิวสะสม วิศวกรจะเรียนรู้ที่จะกด retry ไปเรื่อยๆ จนกว่าจะได้ตัวเลขที่น่าพอใจ ซึ่งเป็นการฝึกให้ทีมเพิกเฉยต่อ build ที่ขึ้นสีแดง (red builds) ค่า token จะพอกพูนขึ้นเพราะทุกการ retry จะเผาผลาญ API credits มากขึ้น และที่แย่ที่สุดคือ สัญญาณที่ได้รับจะไร้ความหมาย Build ที่เป็นสีแดงควรหมายถึง "คุณเพิ่งนำบั๊กเข้ามา" แต่ถ้ามันหมายถึง "วันนี้โมเดลที่เป็นคนตัดสินดันตื่นมาเรื่องมาก" ความเชื่อมั่นก็จะหมดไป
สิ่งที่ผู้รอดชีวิตทำต่างออกไป
Promptfoo และ DeepEval รอดมาได้เพราะพวกมันปฏิบัติกับการตรวจสอบแบบ deterministic เป็นสิ่งสำคัญอันดับแรก (first-class citizens) และมองคะแนนจาก LLM judge เป็นเพียงสัญญาณรองที่ไม่ขัดขวางการทำงาน (non-blocking signals) พวกมันเข้าใจว่า gate ต้องการ exit code ไม่ใช่ตัวเลขทศนิยม (floating-point number) ที่มาพร้อมกับความคิดเห็น
Promptfoo ซึ่งปล่อยภายใต้ MIT license ถูกสร้างขึ้นมาเพื่อใช้งานผ่าน command line โดยมันจะรันการตรวจสอบ (assertions) เช่น regex matches, JSON schema validation, contains checks และการเปรียบเทียบสตริงแบบตรงตัว (exact string comparisons) สิ่งเหล่านี้ไม่ใช่เรื่องหรูหรา แต่มันคือคำสั่ง grep และ jq ที่ถูกทำให้ดูดีขึ้น ซึ่งนั่นคือเหตุผลว่าทำไมมันถึงใช้งานได้ดีใน CI regex ไม่ match ก็คือไม่ match ส่วน JSON schema ไม่ผ่านก็คือ error Promptfoo จะคืนค่าเป็น standard Unix exit codes ดังนั้น GitHub Actions จึงเข้าใจได้ทันทีว่าควรหยุดการ merge เมื่อใด มันเป็น language-agnostic เพราะทำงานในรูปแบบ CLI tool คุณจึงไม่จำเป็นต้องติดตั้ง Python ecosystem ไว้ใน repo ของ Node.js service เพียงเพื่อจะตรวจสอบผลลัพธ์
DeepEval ซึ่งใช้ license Apache 2.0 คือตัวเลือกสำหรับทีม Python มันทำงานร่วมกับระบบได้เหมือน pytest คุณเขียนเทสต์ด้วย syntax ที่คุ้นเคย และเมื่อเกิดความล้มเหลว มันจะบล็อก suite นั้นตามธรรมชาติ DeepEval มีเมทริกซ์ให้เลือกมากมาย แต่รายละเอียดสำคัญคือคุณต้องใช้งานพวกมันอย่างระมัดระวัง ให้พึ่งพาเมทริกซ์แบบ deterministic หรือ heuristic สำหรับการทำ gate ส่วนถ้าคุณจะใช้ G-Eval หรือตัวให้คะแนนที่ใช้ judge อื่นๆ ให้ห่อหุ้ม (wrap) พวกมันไว้ใน report generators แบบ non-blocking แทนที่จะใช้ hard asserts เมื่อใช้งานในลักษณะนี้ DeepEval จะให้ความสะดวกสบาย (ergonomics) ของ testing framework โดยไม่มีความไม่เสถียร (flakiness) เหมือนใน research notebook
ส่วนอีกสี่ตัวที่เหลือควรอยู่ตรงไหน
เฟรมเวิร์กทั้งสี่ตัวที่ไม่รอดจากการเป็น gate ยังคงมีคุณค่า เพียงแต่พวกมันควรไปอยู่ในส่วนอื่นของ toolchain ของคุณแทน
Future AGI (Apache 2.0) มาพร้อมกับเมทริกซ์กว่าห้าสิบรายการ และมุ่งเป้าไปที่ทีมที่สร้าง custom SDKs ตัวเมทริกซ์มีความละเอียดถี่ถ้วน ปัญหาคือเครื่องมือนี้คาดหวังให้คุณเขียน harness ของตัวเองเพื่อขับเคลื่อนมันใน CI queue ในบริบทของการวิจัย นั่นถือเป็นการแลกเปลี่ยนที่สมเหตุสมผล แต่ใน merge queue ทุกเลเยอร์ของการเชื่อมต่อแบบ custom คือแหล่งกำเนิดความไม่เสถียรใหม่ มันเป็นเครื่องมือประเมินที่มีความสามารถสูง แต่ยังไม่ใช่ gatekeeper ที่พร้อมใช้งานทันที
RAGAS (Apache 2.0) โดดเด่นในการวัดคุณภาพของ retrieval-augmented generation เมทริกซ์ด้าน faithfulness และ answer relevance ของมันมีประโยชน์อย่างยิ่งในการทำความเข้าใจว่าฐานความรู้ (knowledge base) มีประสิทธิภาพอย่างไรเมื่อเวลาผ่านไป แต่น่าเสียดายที่เมทริกซ์เหล่านั้นต้องพึ่งพา LLM judges อย่างมาก พวกมันเหมาะมากสำหรับงานตรวจสอบคุณภาพรายคืน (nightly quality job) ที่ส่งเทรนด์ไปยัง Slack แต่ไม่เหมาะที่จะเป็น bouncer สำหรับ pull request ควรย้าย RAGAS ไปไว้ใน scheduled analysis pipeline แทนที่จะใช้เป็นตัวขัดขวางการ merge (merge blockers)
Arize Phoenix ใช้ Elastic License 2.0 และอยู่ในจุดที่แตกต่างออกไปโดยสิ้นเชิง มันเชื่อมต่อ distributed tracing เข้ากับการประเมินผล ช่วยให้คุณมองเห็น (observability) ว่าทำไมโมเดลถึงแสดงพฤติกรรมในลักษณะนั้นๆ คุณต้องการสิ่งนี้เมื่อกำลังดีบั๊กเหตุการณ์ในโปรดักชัน (production incident) หรือกำลังสืบหาต้นตอของอาการ hallucination กลับไปยัง chunk ของการ retrieval ที่ผิดพลาด แต่คุณคงไม่ต้องการให้เครื่องมือ tracing มาตัดสินว่า feature branch ของนักพัฒนา junior จะสามารถ deploy ได้หรือไม่ สถาปัตยกรรมของมันถูกสร้างขึ้นเพื่อสร้างความเข้าใจ (insight) ไม่ใช่เพื่อทำหน้าที่เป็น binary gates
MLflow Evaluate (Apache 2.0) สืบทอดความเชี่ยวชาญมาจากการทำ experiment tracking มันมีขนาดใหญ่ (heavy) การดึงมันเข้ามาใน CI image ที่เน้นความเบา (lean) จะเพิ่มเวลาในการ startup และเพิ่ม dependencies ที่ทำให้ทุกๆ job ช้าลง หากคุณจำเป็นต้องใช้มันใน pipeline จริงๆ ให้ใช้เพียง heuristic metrics สำหรับการตรวจสอบโครงสร้าง (structural checks) ถึงอย่างนั้น คุณก็ยังต้องฝืนการออกแบบพื้นฐานของ framework อยู่ดี เพราะ MLflow ต้องการ log การรันและเปรียบเทียบการทดลองข้ามสัปดาห์ แต่ merge queue ต้องการคำตัดสินภายในเวลาไม่ถึงหนึ่งนาที
กฎเชิงปฏิบัติสำหรับการทำ Gating
หากคุณไม่ได้อะไรเลยจากการทดลองนี้ ขอให้จดจำกฎสามข้อนี้ไว้
อย่างแรก: ตรวจสอบที่โครงสร้าง (structure) ไม่ใช่ความรู้สึก (vibe) คุณสามารถบังคับให้ output เป็น JSON ที่ถูกต้องได้ คุณสามารถบังคับให้มี key ที่จำเป็นได้ คุณสามารถบังคับให้ classification label อยู่ใน enum ที่อนุญาตได้ การตรวจสอบเหล่านี้รวดเร็ว ราคาถูก และมีความแน่นอน (deterministic) แต่คุณไม่สามารถบังคับได้อย่างน่าเชื่อถือว่าสรุปความนั้น "เป็นกันเอง" (friendly) หรือการเขียนใหม่นั้น "สร้างสรรค์" (creative) คุณสมบัติเหล่านั้นควรอยู่ในขั้นตอนการรีวิวโดยมนุษย์หรือการประเมินแบบ batch เป็นระยะ ไม่ใช่ใน automated gates
อย่างที่สอง: หากคะแนนเปลี่ยนแปลงทั้งที่ input ไม่ได้เปลี่ยน ให้ลดระดับความสำคัญของมันทันที ลองรัน evaluation suite ของคุณสองครั้งกับ artifact ตัวเดิมเป๊ะๆ หากมีเมทริกซ์ใดเปลี่ยนจาก pass เป็น fail มันก็หมดสิทธิ์ที่จะใช้บล็อกการ merge ให้เปลี่ยนมันไปเป็น advisory dashboard ที่ซึ่งความผันผวน (variance) เป็นเรื่องที่คาดการณ์ได้และยอมรับได้
อย่างที่สาม: เคารพ exit code รายงาน HTML สวยๆ ที่มีแบนเนอร์สีแดงไม่ได้หยุดการ merge แต่ nonzero exit code ทำได้ เครื่องมือประเมินของคุณต้องพูดภาษาเดียวกับ CI platform ของคุณ Standard out มีไว้สำหรับมนุษย์ ส่วน exit codes มีไว้สำหรับเครื่องจักร
บทสรุป
เรายังอยู่ในช่วงเริ่มต้นของการหาวิธีทดสอบแอปพลิเคชันที่ขับเคลื่อนด้วย LLM สิ่งที่เย้ายวนใจคือการปฏิบัติกับการประเมินผลเหมือนกับเกณฑ์การให้คะแนนของมนุษย์ นั่นคือมีความละเอียดอ่อน มีบริบท และมีความเป็นอัตวิสัย (subjective) เล็กน้อย ซึ่งนั่นใช้ได้ในงานวิจัย แต่จะล้มเหลวอย่างสิ้นเชิงใน merge queue
หลังจากผ่านการใช้งานจริง (production traffic) มาแปดเดือน ตอนนี้ pipeline ของผมใช้ Promptfoo สำหรับการทำ structural และ schema assertions ในแต่ละ service และใช้ DeepEval สำหรับการตรวจสอบพฤติกรรมฝั่ง Python ที่สามารถจับคู่กับเงื่อนไข pass-fail ได้อย่างชัดเจน ส่วนอย่างอื่นทั้งหมดจะรายงานไปยัง nightly dashboards แทน ผลที่ได้คือ queue มีความเสถียร สัญญาณ (signal) มีความชัดเจน และทีมก็กลับมาเชื่อมั่นใน build ที่ขึ้นสีแดงได้อีกครั้ง
คุณไม่ต้องการเมทริกซ์ที่มากขึ้นที่จุด gate ของคุณ แต่คุณต้องการเมทริกซ์ที่น้อยลงแต่บอกความจริงได้ในทุกๆ ครั้ง
อ้างอิงจากการทดสอบและบทความต้นฉบับที่แชร์บน Dev.to สำหรับการพูดคุยเพิ่มเติมเกี่ยวกับการสร้างระบบ AI ที่เชื่อถือได้ เข้าร่วม GyaanSetu community บน Telegram
