การทดสอบของคุณผ่านเมื่อวันจันทร์ ด้วยอินพุตเดิม, โค้ดเดิม, temperature=0. แต่เมื่อวันอังคารมันกลับล้มเหลว ทั้งที่คุณไม่ได้เปลี่ยนอะไรเลย การยืนยัน (assertion) ตรวจสอบสตริงที่ตรงกันเป๊ะ แต่โมเดลคืนคำตอบเดียวกันที่ใช้คำพูดแตกต่างกันเล็กน้อย การทดสอบขึ้นสีแดง เอเจนต์ก็ยังทำงานได้ดี และตอนนี้คุณกำลังดีบั๊กชุดทดสอบของคุณแทนที่จะเป็นผลิตภัณฑ์
นี่คือภาระที่ต้องแบกรับเมื่อทดสอบสิ่งที่เรียกใช้โมเดลภาษา ผลลัพธ์จะเปลี่ยนแปลงไป แม้ว่าคุณจะสั่งไม่ให้เปลี่ยนก็ตาม ตั้งค่า temperature เป็นศูนย์ คุณก็ยังจะไม่ได้รับคำตอบที่เหมือนกันทุกไบต์ในการรันแต่ละครั้ง นักพัฒนาส่วนใหญ่เรียนรู้สิ่งนี้ด้วยวิธีที่ยากลำบากเพียงครั้งเดียว แล้วก็เขียนวิธีการทดสอบใหม่ คู่มือนี้จะแสดงให้คุณเห็นถึงวิธีการเขียน assertion ที่ยังคงใช้ได้ผลเมื่อข้อความที่อยู่ภายใต้นั้นมีการเปลี่ยนแปลงอยู่เสมอ นี่คือการเจาะลึกถึงโหมดความล้มเหลวที่สามจากคู่มือของเราเรื่อง เหตุใด AI agents จึงพังในการใช้งานจริง
ทำไม temperature=0 จึงไม่ได้หมายถึงการกำหนดได้ (deterministic)
Temperature ควบคุมว่าโมเดลจะสุ่มโทเค็นถัดไปอย่างไร ที่ศูนย์ โมเดลจะเลือกโทเค็นที่มีความน่าจะเป็นสูงสุดทุกครั้ง ซึ่งให้ความรู้สึกว่าน่าจะสามารถทำซ้ำได้ แต่มันไม่ใช่ และเหตุผลนั้นอยู่ลึกลงไปใต้ตัวโมเดล
การคำนวณเลขทศนิยม (floating-point math) ไม่ใช่ associative บน GPU การบวกตัวเลขชุดเดียวกันในลำดับที่ต่างกันจะทำให้ได้ผลลัพธ์ที่แตกต่างกันเล็กน้อยในตำแหน่งทศนิยมสุดท้าย ความแตกต่างเล็กๆ น้อยๆ นั้นสามารถส่งผลให้โทเค็นใดมีอันดับสูงสุด และโทเค็นที่แตกต่างกันเพียงหนึ่งตัวจะเปลี่ยนทุกสิ่งทุกอย่างที่ตามมา ลำดับของการบวกเหล่านั้นขึ้นอยู่กับว่าผู้ให้บริการรวมคำขอของคุณกับทราฟฟิกอื่นอย่างไร, ฮาร์ดแวร์ใดที่รันมัน, และเวอร์ชันเคอร์เนลใดที่ถูกใช้งานในวันนั้น คุณไม่สามารถควบคุมสิ่งเหล่านี้ได้เลย
ผู้ให้บริการก็เปลี่ยนแปลงสิ่งต่างๆ ในฝั่งของพวกเขาเช่นกัน พวกเขาสลับ GPU, อัปเดตไลบรารีการอนุมาน (inference libraries), ทำการ re-quantize น้ำหนัก (weights) และส่งคำขอของคุณไปยังภูมิภาคอื่น การสนทนา vLLM ที่ยาวนาน อธิบายว่าทำไมการใช้ seed ที่คงที่และ temperature=0 จึงยังไม่เพียงพอสำหรับการสร้างซ้ำแบบบิตต่อบิต (bitwise reproducibility) สรุปสั้นๆ คือ: การกำหนดได้ (determinism) เป็นคุณสมบัติของสแต็กการให้บริการทั้งหมด ไม่ใช่เพียงแฟล็กที่คุณตั้งค่าในคำขอของคุณ
ดังนั้น เลิกถือว่าผลลัพธ์ที่เหมือนกันทุกประการเป็นพื้นฐานได้แล้ว โมเดลให้คำตอบที่มีความหมายเดียวกัน ไม่ว่าจะใช้คำพูดอย่างไรในการรันครั้งนี้ การทดสอบของคุณต้องยอมรับสิ่งนั้น
การยืนยันสตริงที่ตรงกันเป๊ะทำให้ชุดทดสอบของคุณเปราะบาง (flaky)
นี่คือกับดัก คุณเขียน assert response == "Your order total is $42.00." เพราะนั่นคือสิ่งที่กลับมาในครั้งแรก มันผ่าน จากนั้นโมเดลคืนค่าเป็น "Your total comes to $42.00" และการทดสอบก็ล้มเหลวกับคำตอบที่ถูกต้อง
การทดสอบที่ล้มเหลวกับคำตอบที่ถูกต้องนั้นแย่ยิ่งกว่าไม่มีการทดสอบเลย ทีมงานจะเรียนรู้ว่าชุดทดสอบนี้ส่งเสียงเตือนผิดๆ ผู้คนจะรันซ้ำจนกว่าจะขึ้นสีเขียว จากนั้นพวกเขาก็หยุดอ่านความล้มเหลว แล้วพวกเขาก็พลาดการถดถอยที่แท้จริงที่ซ่อนอยู่ในความวุ่นวาย การทดสอบที่เปราะบางไม่เพียงแต่เสียเวลาเท่านั้น แต่ยังบั่นทอนความเชื่อมั่นในชุดทดสอบทั้งหมด และเราเคยเขียนเกี่ยวกับ สาเหตุของการทดสอบที่เปราะบาง และทำไมมันถึงแพร่กระจาย การส่งออกที่ไม่สามารถกำหนดได้ (Non-deterministic output) เป็นวิธีที่เร็วที่สุดวิธีหนึ่งในการสร้างการทดสอบแบบนี้
สัญชาตญาณคือการยึดเอาต์พุตให้แน่นยิ่งขึ้น: บันทึกสตริงที่ตรงกันเป๊ะ, ถ่ายสแนปช็อต, เปรียบเทียบความแตกต่าง นั่นทำให้ความเปราะบางแย่ลง เพราะคุณได้ผูกการทดสอบของคุณเข้ากับสิ่งเดียวที่รับประกันว่าจะเปลี่ยนแปลง คุณต้องการการเคลื่อนไหวที่ตรงกันข้าม
ยืนยันตามโครงสร้างและความหมาย ไม่ใช่ข้อความที่ตรงกันเป๊ะ
ผลลัพธ์แตกต่างกันไป แต่สัญญาพื้นฐานไม่ควรเปลี่ยน เอเจนต์สนับสนุนอาจใช้คำพูดในการยืนยันการคืนเงินได้เป็นร้อยวิธี แต่ทุกคำตอบที่ถูกต้องจะประกอบด้วยข้อเท็จจริงเดียวกัน: จำนวนเงินคืน, รหัสคำสั่งซื้อ, สถานะ ทดสอบข้อเท็จจริง ไม่ใช่ถ้อยคำ
นั่นคือการเปลี่ยนแปลงทั้งหมด หยุดถามว่า "โมเดลพูดตรงนี้เลยใช่ไหม" และเริ่มถามว่า "คำตอบมีรูปแบบที่ถูกต้อง, มีฟิลด์ที่ถูกต้อง และค่าอยู่ในช่วงที่เหมาะสมหรือไม่" คุณสมบัติเหล่านั้นจะยังคงอยู่แม้มีการเปลี่ยนคำพูด การถดถอยที่แท้จริง, ฟิลด์ที่หายไป, ตัวเลขที่เกินขอบเขต, เพย์โหลดที่ผิดรูปแบบ ก็ยังคงทำให้ assertion ล้มเหลว นี่คือกลยุทธ์ที่จะนำไปใช้จริง
ตรวจสอบความถูกต้องของคำตอบเทียบกับ JSON schema
หากเอเจนต์ของคุณคืนข้อมูลที่มีโครงสร้าง ให้กำหนด JSON schema สำหรับข้อมูลนั้นและตรวจสอบความถูกต้องของทุกคำตอบเทียบกับ schema นั้น Schema จะตรวจสอบประเภทข้อมูล, ฟิลด์ที่จำเป็น, enum ที่อนุญาต และรูปแบบ โดยไม่สนใจค่าที่เฉพาะเจาะจง ฟิลด์ status ต้องเป็นหนึ่งใน refunded, pending หรือ denied ฟิลด์ order_id ต้องตรงกับรูปแบบ id ของคุณ ฟิลด์ amount ต้องเป็นตัวเลข ไม่ใช่สตริง
นี่คือ assertion เดียวที่แข็งแกร่งที่สุดที่คุณสามารถเขียนได้เมื่อรับมือกับคำตอบที่ไม่สามารถกำหนดได้ (non-deterministic response) เพราะมันตรวจจับความล้มเหลวที่สร้างความเสียหายได้: โมเดลละทิ้งฟิลด์, ซ้อนออบเจกต์ผิด, หรือคืนค่าเป็นข้อความบรรยายแทนที่จะเป็น JSON โหลด schema คำตอบของคุณลงใน Apidog และตรวจสอบความถูกต้องของคำตอบจริงของเอเจนต์เทียบกับ schema นั้น การไม่ตรงกันจะระบุฟิลด์ที่ผิดพลาดอย่างแม่นยำ ไม่ใช่ความแตกต่างของสตริงยาว 400 ตัวอักษร
ยืนยันว่าการเรียกใช้เครื่องมือมีรูปแบบและเป้าหมายที่ถูกต้อง
เมื่อเอเจนต์ของคุณตัดสินใจที่จะเรียกใช้เครื่องมือ ให้ทดสอบการเรียกใช้นั้น ไม่ใช่ประโยคที่นำไปสู่การเรียกใช้นั้น ยืนยันสามสิ่ง: มันเลือกเครื่องมือที่ถูกต้อง, มันมุ่งเป้าไปที่เป้าหมายที่ถูกต้อง และเพย์โหลดตรงกับ schema ของเครื่องมือนั้น เอเจนต์การจองที่เรียกใช้ POST /reservations ควรส่ง guests เป็นจำนวนเต็มและ date ที่ถูกต้อง ไม่ว่าเหตุผลเชิงภาษามนุษย์ใดจะสร้างการเรียกใช้นั้นขึ้นมา
นี่คือระเบียบวินัยเดียวกับการตรวจสอบความถูกต้องของเนื้อหาการตอบสนอง ซึ่งนำมาใช้กับการร้องขอที่ส่งออกไป ตรวจสอบว่าพารามิเตอร์ที่จำเป็นมีอยู่, ประเภทข้อมูลถูกต้อง และไม่มีฟิลด์ที่สร้างขึ้นเองแทรกเข้ามา วิธีการทดสอบการเรียกใช้ API ของเอเจนต์แบบ end-to-end ครอบคลุมการบันทึก schema เครื่องมือเหล่านั้นและการยืนยันเทียบกับ schema นั้น เพย์โหลดการเรียกใช้เครื่องมือมีสัญญาแม้ว่าถ้อยคำรอบๆ จะไม่มีก็ตาม
ใช้ช่วงตัวเลขแทนค่าที่แน่นอน
สำหรับตัวเลขใดๆ ที่โมเดลสร้างหรือส่งผ่าน ให้ยืนยันช่วงค่า ไม่ใช่ค่าที่แน่นอน เอเจนต์ตะกร้าสินค้าจะคำนวณยอดรวม คุณไม่ทราบตัวเลขที่แน่นอนในการรันทุกครั้ง, ตะกร้าสินค้าทุกประเภท และกฎภาษีทั้งหมด แต่คุณรู้ว่ามันต้องไม่เป็นค่าลบและต้องไม่เกินมูลค่าตะกร้าสินค้าบวกค่าจัดส่งสูงสุดและภาษี ดังนั้นให้ยืนยันว่า: คำตอบประกอบด้วย total ที่อยู่ระหว่าง 0 ถึงขีดจำกัดนั้น
ขอบเขตเดียวนี้จะตรวจจับความล้มเหลวที่สำคัญ เช่น ยอดรวมติดลบ, ยอดรวมที่มากเกินไปสิบเท่า, ยอดรวมเป็นศูนย์ในตะกร้าสินค้าที่เต็มไปด้วยของ ในขณะที่ละเลยการเปลี่ยนแปลงที่คุณไม่สนใจ ช่วงค่าใช้ได้กับคะแนนความเชื่อมั่น (confidence scores), จำนวนรายการ, การใช้งานโทเค็น (token usage), งบประมาณเวลาแฝง (latency budgets) และตัวเลขที่ได้มาจากการคำนวณใดๆ เลือกขอบเขตที่กว้างที่สุดที่ยังคงตรวจจับข้อผิดพลาดที่แท้จริงได้
ตรวจสอบว่าคีย์ที่จำเป็นมีอยู่และฟิลด์ที่ห้ามมีนั้นไม่มีอยู่
Assertion สองอย่างที่ง่ายแต่มีความสำคัญมาก ประการแรก คีย์ที่คุณต้องใช้จะต้องมีอยู่และไม่ใช่ค่าว่าง (non-null) ประการที่สอง คีย์ที่ไม่ควรปรากฏจะต้องไม่มีอยู่เลย เอเจนต์ที่จัดการตั๋วสนับสนุนควรส่งคืน resolution และไม่ควรเปิดเผยฟิลด์ internal_notes หรือ raw_prompt ให้ลูกค้า
การตรวจสอบการมีอยู่และการไม่มีอยู่ของฟิลด์นั้นถูกออกแบบมาให้ไม่ได้รับผลกระทบจากการเปลี่ยนคำพูด เนื่องจากเป็นการทดสอบโครงสร้างของคำตอบ ไม่ใช่เนื้อหา นอกจากนี้ยังเป็นเกราะป้องกันที่ราคาถูกที่สุดของคุณต่อการรั่วไหลของข้อมูลส่วนบุคคลทั้งหมด โดยที่โมเดลอาจรวมฟิลด์ที่ควรเก็บเป็นส่วนตัวโดยไม่ตั้งใจ
ใช้การตรวจสอบเชิงความหมายและเกณฑ์สำหรับข้อความอิสระ
บางครั้งเพย์โหลดเป็นข้อความบรรยาย และคุณยังคงต้องทดสอบมัน การจับคู่ที่ตรงกันเป๊ะจะไม่ทำงาน ดังนั้นให้ตรวจสอบคุณสมบัติแทน คำตอบมีหมายเลขคำสั่งซื้อที่คุณส่งเข้ามาหรือไม่? มันมีความยาวไม่เกินขีดจำกัดที่กำหนดไว้หรือไม่? มันหลีกเลี่ยงรายการวลีที่คุณไม่ต้องการส่งให้ผู้ใช้หรือไม่?
เมื่อคุณต้องการทดสอบความหมายอย่างแท้จริง ให้เปรียบเทียบโดยใช้ความคล้ายคลึงของ embedding เทียบกับคำตอบอ้างอิง และยืนยันว่าคะแนนผ่านเกณฑ์ที่กำหนด แทนที่จะเรียกร้องให้สตริงตรงกันทุกประการ พิจารณาการตรวจสอบเชิงความหมายเหล่านี้เป็นด่านหยาบๆ ไม่ใช่ด่านที่แม่นยำ พวกมันจะตรวจจับคำตอบที่ออกนอกเรื่องไป แต่จะไม่ตรวจจับข้อผิดพลาดทางข้อเท็จจริงที่ละเอียดอ่อน ดังนั้นควรใช้ควบคู่กับการยืนยันเชิงโครงสร้างที่กล่าวมาข้างต้น
สแนปช็อตช่วงค่า ไม่ใช่สแนปช็อตที่แน่นอน
การทดสอบสแนปช็อตยังคงมีประโยชน์ ตราบใดที่คุณสแนปช็อตส่วนที่เสถียร ตรึงรูปแบบของคำตอบ, ชุดของคีย์, ประเภท, ค่า enum และปล่อยให้ฟิลด์ที่มีการเปลี่ยนแปลงอิสระอยู่ในช่วงที่กำหนด ในทางปฏิบัติ สแนปช็อตของคุณจะบันทึกว่า "คำตอบนี้มีคีย์ a, b, c โดยที่ b อยู่ในช่วงนี้และ c มาจากชุดนี้" แทนที่จะเป็นกลุ่มข้อความที่แน่นอน เมื่อสแนปช็อตผิดพลาด มันจะผิดพลาดจากการเปลี่ยนแปลงโครงสร้างที่ควรพิจารณา ไม่ใช่จากคำพ้องความหมาย
สถานะและหน่วยความจำทำให้สิ่งนี้ยากขึ้น
ทุกสิ่งที่กล่าวมาข้างต้นถือว่ามีการร้องขอหนึ่งครั้งและมีการตอบสนองหนึ่งครั้ง เอเจนต์ไม่ได้ทำงานในลักษณะนั้น พวกมันเก็บหน่วยความจำข้ามรอบการสนทนา และสถานะนั้นจะเพิ่มแหล่งที่มาของการเปลี่ยนแปลง
คำตอบของเอเจนต์ที่มีสถานะขึ้นอยู่กับสิ่งที่มันเรียกข้อมูลมา, สิ่งที่มันเก็บไว้ก่อนหน้านี้ และลำดับที่รอบการสนทนาก่อนหน้าทำงาน การรันการสนทนาเดียวกันสองครั้งอาจแตกต่างกันได้เนื่องจากขั้นตอนการเรียกข้อมูลจัดอันดับเอกสารต่างกัน หรือเพราะสรุปที่เขียนในรอบที่สองมีผลต่อการให้เหตุผลในรอบที่ห้า ตอนนี้ผลลัพธ์ของคุณจะแตกต่างกันด้วยสองเหตุผลที่ซับซ้อน: การไม่สามารถกำหนดได้ของโมเดลเอง และสถานะเริ่มต้นที่แตกต่างกัน คำอธิบายของเราเกี่ยวกับ วิธีการทำงานของหน่วยความจำ AI agent จะอธิบายว่าสถานะนั้นอยู่ตรงไหนและสร้างขึ้นมาอย่างไร
มีสองนิสัยที่ทำให้สิ่งนี้สามารถทดสอบได้ ประการแรก ควบคุมสถานะที่คุณสามารถทำได้ กำหนดหน่วยความจำของเอเจนต์ไปยังจุดเริ่มต้นที่ทราบก่อนการทดสอบแต่ละครั้ง เพื่อให้คุณเปลี่ยนเพียงสิ่งเดียวไม่ใช่สองสิ่ง ประการที่สอง ยืนยันบนค่าคงที่ที่ไม่เปลี่ยนแปลงไม่ว่าจะผ่านเส้นทางใด ยอดคงเหลือที่กำลังดำเนินการไม่ควรติดลบ การสนทนาที่จองเที่ยวบินหนึ่งเที่ยวควรจบลงด้วยการจองเพียงครั้งเดียว ไม่ว่าจะใช้กี่รอบก็ตาม Assertion ที่ไม่ขึ้นกับเส้นทางคือสิ่งที่จะอยู่รอดได้กับเอเจนต์ที่มีสถานะและไม่สามารถกำหนดได้
จำลองการพึ่งพา (dependencies) เพื่อให้การทดสอบซ้ำได้
คุณไม่สามารถทดลองสิ่งเหล่านี้กับ API ของบุคคลที่สามที่ใช้งานจริงได้ พวกมันมีข้อจำกัดในการเรียกใช้ (rate-limit), พวกมันเปลี่ยนแปลงข้อมูลของพวกมัน และพวกมันยังเพิ่มแหล่งที่มาของความสุ่มที่สองนอกเหนือจากโมเดล เพื่อให้ได้การทดสอบที่สามารถทำซ้ำได้ ให้ตรึงทุกสิ่งที่ไม่ใช่พฤติกรรมที่คุณกำลังทดสอบ
จำลอง API ที่เอเจนต์เรียกใช้และตั้งโปรแกรมการตอบสนองที่ตายตัว ตอนนี้ API การชำระเงินจะคืนใบเสร็จแบบเดิมเสมอ, API การค้นหาจะคืนผลลัพธ์สามรายการเดิมเสมอ และสิ่งเดียวที่ยังคงเคลื่อนไหวคือการให้เหตุผลของเอเจนต์เอง ซึ่งเป็นสิ่งที่คุณต้องการสังเกต การพึ่งพาที่จำลองยังช่วยให้คุณสามารถบังคับให้เกิดกรณีสุดขีดที่ API ที่ทำงานปกตินั้นไม่สามารถสร้างได้ตามต้องการ จากนั้นยืนยันว่าเอเจนต์สามารถจัดการได้ ชี้ Apidog ไปยังการพึ่งพาของเอเจนต์เพื่อตั้งค่าการจำลองเหล่านั้นด้วยเนื้อหาที่เสถียรและควบคุมได้ และจับคู่กับการยืนยัน schema ข้างต้น สิ่งนี้อยู่ภายในการปฏิบัติที่กว้างขึ้นของ การทดสอบ AI แบบเอเจนต์ ซึ่งการจำลองและการยืนยันทำงานร่วมกัน
Apidog เหมาะกับอะไร (และไม่เหมาะกับอะไร)
ต้องแม่นยำเกี่ยวกับหน้าที่ของเครื่องมือ Apidog เป็นแพลตฟอร์มสำหรับออกแบบ, ทดสอบ และจำลอง API มันไม่ใช่เฟรมเวิร์กเอเจนต์, โฮสต์โมเดล, รันไทม์เอเจนต์ หรือแพลตฟอร์มสำหรับการประเมินและสังเกตการณ์ มันไม่ได้สร้างเอเจนต์ของคุณ, รันมัน, จัดการขั้นตอน หรือให้คะแนนการให้เหตุผลของมัน
สิ่งที่ Apidog เป็นเจ้าของคือเลเยอร์ API ที่เอเจนต์ของคุณสื่อสารด้วย และนั่นคือที่ที่การทดสอบเหล่านี้อยู่ มีสองสิ่งที่ Apidog ทำได้ดีอย่างแท้จริง คุณเขียน assertion บนคำตอบ API ของเอเจนต์ (การตรวจสอบ schema, รูปแบบคำตอบ, ช่วงตัวเลข, คีย์ที่จำเป็นและคีย์ที่ห้ามมี, รูปแบบเพย์โหลดการเรียกใช้เครื่องมือ) ที่ยังคงใช้ได้ผลกับผลลัพธ์ที่ไม่สามารถกำหนดได้ และคุณจำลองการพึ่งพาของเอเจนต์เพื่อให้การทดสอบทำงานซ้ำได้สองครั้ง นั่นคือช่องว่างที่ Apidog เติมเต็ม: สัญญาเกี่ยวกับคำขอและคำตอบ ไม่ใช่โมเดลที่สร้างสิ่งเหล่านั้น
ทดสอบสัญญา ไม่ใช่ถ้อยคำ
การไม่สามารถกำหนดได้ (Non-determinism) ไม่ใช่ข้อผิดพลาดที่คุณสามารถแก้ไขได้ด้วยการตั้งค่า มันเป็นคุณสมบัติของการรันโมเดลภาษา และ temperature=0 ก็ไม่ได้ปิดคุณสมบัตินี้ลง ทีมที่ส่งมอบเอเจนต์ที่น่าเชื่อถือได้หยุดต่อสู้กับมันแล้ว พวกเขาจะทดสอบสิ่งที่คงที่ เช่น schema, รูปแบบ, ช่วงค่า, ฟิลด์ที่จำเป็น และปล่อยให้ถ้อยคำเปลี่ยนแปลงไป ทำเช่นนั้นแล้วชุดทดสอบของคุณก็จะเงียบลงในทางที่ดี: มันจะยังคงขึ้นสีเขียวในขณะที่ข้อความเปลี่ยนแปลงไป และจะเปลี่ยนเป็นสีแดงเมื่อมีบางอย่างเสียหายเท่านั้น
เลือก assertion ที่เปราะบางหนึ่งรายการในชุดทดสอบของคุณในสัปดาห์นี้และเขียนใหม่ให้เป็นการตรวจสอบ schema และช่วงค่า ดาวน์โหลด Apidog เพื่อตรวจสอบความถูกต้องของคำตอบของเอเจนต์ของคุณเทียบกับสัญญา และจำลองการพึ่งพาที่ทำให้การทดสอบสามารถทำซ้ำได้
