ทุกคนต่างหมกมุ่นอยู่กับพรอมต์ พวกเขาปรับแต่งคำทักทาย ปรับโทนเสียง และกังวลว่าโมเดลจะฟังดูอบอุ่นพอหรือไม่ นั่นคือการเสียเวลาเปล่า เมื่อเอเจนต์ AI เริ่มส่งอีเมลจริงไปยังผู้ใช้จริง อันตรายไม่ใช่การที่มันเขียนว่า "Best regards" แทนที่จะเป็น "Cheers" แต่อันตรายคือคุณไม่สามารถบอกได้อย่างแน่ชัดว่าเกิดอะไรขึ้นระหว่างการตัดสินใจของเอเจนต์กับการที่ข้อความไปถึงกล่องจดหมาย ผมมองไปที่ "ขอบเขต" (boundary) เป็นอันดับแรก เพราะนั่นคือจุดที่ระบบในระดับ production มักจะพังลงอย่างเงียบเชียบ
สัญญา (Contract) คือจุดอ่อน
การสาธิต AI นั้นให้อภัยได้ง่าย การสนทนาที่ราบรื่นในหน้าต่างเบราว์เซอร์มักจะซ่อนความวุ่นวายของข้อสันนิษฐานเอาไว้ แต่ในระบบที่ใช้งานจริง จุดอ่อนที่แท้จริงอยู่ที่ "สัญญา" (contract) ระหว่างสามสิ่ง: การตัดสินใจของเอเจนต์, เครื่องมือที่ดำเนินการตามคำสั่ง (tool), และขั้นตอนที่ตรวจสอบผลลัพธ์ หากขอบเขตนี้คลุมเครือ ระบบจะทำงานได้อย่างสวยงามจนกระทั่งมันหยุดทำงาน จากนั้นมันจะล้มเหลวอย่างเงียบเชียบ ส่งอีเมลซ้ำไปยังกลุ่มลูกค้าทั้งหมด หรือส่งข้อความผิดเวลาโดยไม่มีบันทึกที่ชัดเจนว่าทำไม พรอมต์อาจจะอ่านดูสละสลวยเหมือนบทกวี แต่สถาปัตยกรรมที่อยู่เบื้องล่างอาจจะยังคงยึดโยงกันไว้ด้วยเศษเชือกเท่านั้น
เลิกปล่อยให้เอเจนต์เขียนได้อย่างอิสระ
ข้อผิดพลาดที่พบบ่อยที่สุดคือการปล่อยให้เอเจนต์เผชิญกับหน้ากระดาษว่างเปล่า ทีมงานมักปล่อยให้มันอธิบายอีเมลด้วยข้อความดิบ (raw text) แล้วจึงหวังพึ่งเครื่องมือปลายทางในการตีความเจตนาจากความเรียง นั่นเป็นวิธีการที่เปราะบาง LLM อาจเสนอเจตนาที่สมเหตุสมผล แต่โครงสร้างพื้นฐานของคุณไม่ต้องการความคิดสร้างสรรค์ มันต้องการ "สัญญา" (contract) มันต้องการฟิลด์ที่เฉพาะเจาะจงซึ่งเครื่องจักรสามารถตรวจสอบได้โดยไม่มีความคลุมเครือ
เมื่อเอเจนต์ส่งคำขออีเมล ผลลัพธ์ควรระบุสิ่งที่ระบบหลังบ้านต้องการอย่างครบถ้วน:
- Template version: เวอร์ชันของเนื้อหาอีเมลที่ถูกใช้งาน เพื่อให้คุณทราบว่าผู้ใช้เห็นอะไร
- Recipient scope: ขอบเขตผู้รับ โดยกำหนดด้วย User ID หรือกฎของกลุ่มเป้าหมาย (segment rules) ไม่ใช่ด้วยภาษาธรรมชาติอย่าง "ผู้ใช้ที่เพิ่งลงทะเบียน"
- Trace ID: รหัสระบุตัวตนที่ไม่ซ้ำกัน ซึ่งจะติดตามคำขอนี้ตั้งแต่เอเจนต์ ผ่านตัวดำเนินการ (executor) ผ่านผู้ให้บริการอีเมล และเข้าไปยัง Log ของคุณ
- Time window: ช่วงเวลาที่การส่งนี้มีผล เพื่อไม่ให้การตัดสินใจที่ค้างคาของเอเจนต์ไปกระตุ้นการส่งอีเมลตอนเที่ยงคืนในอีกหลายชั่วโมงต่อมา
- Idempotency: คีย์ที่ป้องกันไม่ให้การส่งเชิงตรรกะเดียวกันเกิดขึ้นซ้ำ หากเอเจนต์พยายามส่งใหม่หรือเครือข่ายเกิดขัดข้อง
ข้อความดิบ (Raw text) คือ API ที่แย่มาก เพราะมันเปิดช่องว่างให้เกิดความคลุมเครือเกี่ยวกับความเร่งด่วน กลุ่มเป้าหมาย และการดำเนินการ ฟิลด์ที่เฉพาะเจาะจงนั้นเครื่องจักรสามารถอ่านได้ ตรวจสอบได้ และทดสอบได้ ซึ่งจะเปลี่ยนคำสั่งที่คลุมเครือให้กลายเป็นคำสั่งที่ตรวจสอบความถูกต้องได้
เน้นการกระทำ ไม่ใช่ความเรียง
แทนที่จะมอบหมายงานเขียนแบบปลายเปิดให้เอเจนต์ ให้จำกัดมันไว้ด้วยเมนูของ "การกระทำ" (actions) ที่ได้รับอนุญาต ให้คิดซะว่ามันคือ API ภายในที่มี Enum แบบคงที่ เอเจนต์ไม่ต้องร่างหัวข้ออีเมลหรือกังวลเรื่องคำขึ้นต้น แต่มันจะเลือกการกระทำ เช่น send_review_request หรือ send_retry_notice นั่นคือขอบเขตของอิสระในการสร้างสรรค์ของมัน
จากนั้น ตัวดำเนินการที่ทำงานแบบกำหนดผลลัพธ์ได้แน่นอน (deterministic executor) จะนำคีย์การกระทำนั้นไปดึงเทมเพลตที่ถูกต้องจากระบบควบคุมเวอร์ชัน นำข้อมูลที่ผ่านการทำความสะอาดแล้ว (sanitized data) มาเติมลงไป ดึงรายชื่อผู้รับจากแหล่งข้อมูลที่ตรวจสอบแล้ว และสร้างคำสั่งสุดท้าย เอเจนต์มีหน้าที่ตัดสินใจว่าต้องเกิดอะไรขึ้น ส่วนโค้ดที่น่าเบื่อและคาดเดาได้จะเป็นตัวตัดสินว่าสิ่งนั้นจะเกิดขึ้นได้อย่างไร
การแยกส่วนแบบนี้ทำให้ระบบทดสอบได้ง่าย คุณสามารถตรวจสอบได้ว่าสถานะอินพุตที่กำหนดจะกระตุ้น send_retry_notice ได้อย่างแม่นยำโดยไม่ต้องรันการประมวลผล (inference) ของ LLM เลย Unit test ของคุณจะทำงานได้เร็วและแม่นยำเพราะมันตรวจสอบตรรกะการจับคู่ (mapping logic) ไม่ใช่ค่า Temperature ของโมเดล ส่วน Integration test ของคุณจะมุ่งเน้นไปที่การตรวจสอบว่าตัวดำเนินการจับคู่การกระทำเข้ากับบริการอีเมลได้อย่างถูกต้องหรือไม่ ไม่ใช่การลุ้นว่าวันนี้โมเดลจะอารมณ์ดีหรือเปล่า
สร้างขึ้นเป็น 5 เลเยอร์
ระบบที่แข็งแกร่งไม่ได้เกิดขึ้นจากพรอมต์เพียงชุดเดียว แต่มันถูกสร้างขึ้นเป็นเลเยอร์ และแต่ละเลเยอร์จะมีหน้าที่รับผิดชอบที่ชัดเจนเพียงอย่างเดียว
1. แบ็กเอนด์ลดทอนเหตุการณ์ให้เหลือเพียงข้อมูลที่ปลอดภัย
ไม่ว่าตัวกระตุ้นจะเป็น Webhook, การเปลี่ยนแปลงในฐานข้อมูล หรือ Job ที่ตั้งเวลาไว้ เลเยอร์นี้จะทำความสะอาดอินพุต ตัดฟิลด์ที่ไม่คาดคิดออก และส่งต่อเฉพาะสิ่งที่เอเจนต์จำเป็นต้องใช้เท่านั้น หาก Payload ของ Webhook มี 20 ฟิลด์ แต่เอเจนต์ต้องการเพียง 2 ฟิลด์ ก็ให้ส่งไปแค่ 2 ฟิลด์นั้น ไม่ควรมีข้อความดิบจากผู้ใช้หลุดไปถึงเลเยอร์การตัดสินใจโดยไม่ผ่านการตรวจสอบ
2. เอเจนต์เลือกการกระทำจาก Schema ที่กำหนดไว้
เอเจนต์จะดูบริบท ตัดสินใจ และส่งออกคีย์การกระทำที่กำหนดไว้ล่วงหน้าพร้อมกับ Metadata ที่จำเป็น มันจะไม่ร่างความเรียง และจะไม่เดาผู้รับ แต่มันจะส่ง Payload ที่มีโครงสร้างซึ่งเลเยอร์ถัดไปสามารถตรวจสอบความถูกต้องเทียบกับ JSON schema ได้
3. เครื่องมือจะตรวจสอบสิทธิ์และฟิลด์ที่จำเป็น
Agent context นี้มีสิทธิ์ในการเรียกใช้ send_review_request สำหรับผู้ใช้รายนี้หรือไม่? ขอบเขตของผู้รับ (recipient scope) ไม่ว่างเปล่าและอยู่ในขีดจำกัดที่อนุญาตหรือไม่? มี idempotency key อยู่ใน log และมีความไม่ซ้ำกัน (unique) หรือไม่? trace ID อยู่ในรูปแบบที่ถูกต้องหรือไม่? หากไม่ผ่าน ให้แจ้งข้อผิดพลาดออกมาอย่างชัดเจน (fail loudly) ก่อนที่จะมีการเรียกใช้บริการอีเมลใดๆ
4. บริการอีเมลจะบันทึกการส่งพร้อมกับ trace ID
ทุกข้อความที่ออกจากระบบของคุณควรมี trace identifier ติดไปด้วยผ่าน API ของผู้ให้บริการ และเข้าสู่ observability stack ของคุณ หากผู้ใช้ร้องเรียนว่าได้รับอีเมลซ้ำสองฉบับ คุณควรจะสามารถค้นหาด้วย ID เดียวแล้วเห็นได้ทันทีว่าการซ้ำซ้อนนั้นมีต้นตอมาจากไหน: การเรียก agent ซ้ำ (retried agent call), executor ที่ทำงานไม่เสถียร (flaky executor) หรือ callback ที่ทำงานผิดพลาด
5. การทดสอบแบบ end-to-end จะตรวจสอบเนื้อหาและผลลัพธ์ในกล่องจดหมายจริง
เปิดข้อความที่ถูก render แล้วในกล่องจดหมายจริง หัวข้ออีเมล (subject line) ถูกต้องหรือไม่? ลิงก์ยกเลิกการรับข่าวสาร (unsubscribe link) ใช้งานได้หรือไม่? การคลิกปุ่ม call-to-action หลัก นำไปสู่หน้าที่ถูกต้องพร้อมสถานะผู้ใช้ที่ถูกต้องหรือไม่? การผ่าน unit test หมายความว่าโค้ดทำงานได้ แต่มีเพียงการทดสอบในกล่องจดหมายเท่านั้นที่จะบอกคุณได้ว่าอีเมลนั้นใช้งานได้จริงสำหรับมนุษย์
ใช้หลักฐานแทนการคาดเดา
เมื่อการทดสอบล้มเหลวใน pipeline นี้ คุณจำเป็นต้องมีหลักฐานเฉพาะเจาะจง 4 อย่าง อย่ารับอะไรที่น้อยไปกว่านี้
- การตัดสินใจดั้งเดิมจาก agent มันเลือกดำเนินการอย่างไร และบริบทของ input ทั้งหมดคืออะไร?
- คำสั่งที่ผ่านการ normalize จากเครื่องมือ deterministic executor สร้างอะไรขึ้นมาหลังจากใช้ template, hydration logic และกฎการตรวจสอบ (validation rules) แล้ว?
- ข้อความในกล่องจดหมายที่แยกไว้ต่างหาก ไม่ใช่แค่ log ของสิ่งที่คุณคิดว่าส่งไป แต่ต้องเป็นข้อความ MIME จริงๆ รวมถึง headers ทั้งหมด ที่ถูกบันทึกไว้ในกล่องจดหมายสำหรับทดสอบโดยเฉพาะ
- ผลลัพธ์สุดท้ายหลังจากคลิกลิงก์ สถานะของหน้าเว็บที่เกิดขึ้น, การเปลี่ยนแปลงในฐานข้อมูล หรือเหตุการณ์ภายนอกที่พิสูจน์ว่าอีเมลนั้นบรรลุวัตถุประสงค์แล้ว
หากขาดหลักฐานชิ้นใดชิ้นหนึ่งไป ทีมของคุณจะเติมเต็มช่องว่างนั้นด้วยการสันนิษฐาน พวกเขาจะคาดเดา การคาดเดาในระบบอัตโนมัติ (automation) นั้นมีราคาแพง มันทำให้เสียเวลาหลายชั่วโมง ทำลายความเชื่อมั่น และเปลี่ยนทุกเหตุการณ์ให้กลายเป็นปริศนาทางนิติวิทยาศาสตร์ แทนที่จะเป็น
