
การตรวจสอบความไม่แน่นอน (Non-determinism Audit) ของ AI Agent คือกระบวนการบันทึกกระบวนการตัดสินใจเพื่อให้สามารถตรวจสอบย้อนหลังได้ โดยตั้งอยู่บนพื้นฐานที่ว่า Agent อาจสร้างผลลัพธ์ที่แตกต่างกันได้แม้จะได้รับอินพุตเดียวกันก็ตาม
Agent ที่มีการฝัง Generative AI ไม่จำเป็นต้องตอบคำถามเดิมด้วยคำตอบเดิมเสมอไป ด้วยเหตุนี้ หลักฐานที่สามารถอธิบายได้ในภายหลังว่า "เหตุใดจึงตัดสินใจเช่นนั้น" จึงกลายเป็นเงื่อนไขเบื้องต้นสำหรับการประกันคุณภาพและการรับมือกับเหตุการณ์ไม่พึงประสงค์ คู่มือฉบับนี้จัดทำขึ้นสำหรับวิศวกรและผู้รับผิดชอบด้านการควบคุมคุณภาพที่ต้องการความรับผิดชอบ (Accountability) จาก Agent โดยจะเรียบเรียงขั้นตอนการออกแบบและการดำเนินงานของบันทึกการตรวจสอบ (Audit Log) ที่สามารถทำซ้ำได้ ตั้งแต่ข้อกำหนดเบื้องต้นในการออกแบบ รายการที่ต้องบันทึก การตรวจจับความผิดปกติ ไปจนถึงรูปแบบความล้มเหลวตามลำดับ
ซอฟต์แวร์แบบดั้งเดิมสามารถดีบั๊กได้โดยตั้งอยู่บนสมมติฐานที่ว่า "อินพุตเดียวกันย่อมให้เอาต์พุตเดียวกัน" แต่สำหรับเอเจนต์ที่มี Generative AI เป็นแกนหลัก สมมติฐานนี้กลับใช้ไม่ได้อีกต่อไป ก่อนอื่น เราจะมาแยกดูปัจจัยเชิงโครงสร้าง 3 ประการที่ทำให้การตรวจสอบ (Audit) เป็นเรื่องยาก
ในระบบที่เป็นแบบกำหนดได้ (Deterministic system) การทำซ้ำบั๊กสามารถทำได้โดยการ "ป้อนอินพุตเดิมเข้าไปใหม่" เนื่องจากหากอินพุต โค้ด และสถานะเหมือนเดิม ผลลัพธ์ย่อมเหมือนเดิม ดังนั้นตราบใดที่มีอินพุตหลงเหลืออยู่ในบันทึก (Log) ก็สามารถสืบหาต้นตอของปัญหาได้
แต่สำหรับ Agent นั้นไม่สามารถรับประกันความสามารถในการทำซ้ำ (Reproducibility) นี้ได้ แม้จะให้ Prompt เดิม แต่เนื่องจากโมเดลเลือกโทเค็นจากความน่าจะเป็น (Probability distribution) ทำให้เนื้อหา ข้อสรุป หรือลำดับของการเรียกใช้เครื่องมือ (Tool) อาจเปลี่ยนแปลงไป กล่าวคือ ต่อให้เก็บ "บันทึกของผลลัพธ์" เอาไว้ เมื่อป้อนอินพุตเดิมเข้าไปอีกครั้งในภายหลัง ก็อาจได้ผลลัพธ์ที่ต่างออกไป ทำให้บันทึกกับพฤติกรรมที่เกิดขึ้นจริงในขณะนั้นไม่ตรงกัน
ด้วยเหตุนี้ เราจึงจำเป็นต้องเปลี่ยนแนวคิดในการตรวจสอบ (Audit) สิ่งที่ควรบันทึกคือ "สิ่งที่เกิดขึ้นในการรันครั้งนั้น" โดยต้องละทิ้งสมมติฐานที่ว่าสามารถนำกลับมารันซ้ำเพื่อตรวจสอบภายหลังได้ และเริ่มต้นจากการตรึงข้อมูลการตัดสินใจทั้งหมด ณ ขณะที่รันจริงเอาไว้ในทันที
สาเหตุหลักของความไม่แน่นอน (non-determinism) มาจากการสุ่มเลือกโทเค็นขาออก พารามิเตอร์อย่าง temperature และ top-p จะทำหน้าที่ปรับจูนว่าจะเลือกเฉพาะตัวเลือกที่มีความน่าจะเป็นสูงเท่านั้น หรือจะเปิดโอกาสให้ตัวเลือกที่มีความน่าจะเป็นต่ำด้วย หากเพิ่มค่า temperature จะทำให้การแสดงออกมีความหลากหลายมากขึ้น แต่ก็จะทำให้ผลลัพธ์ที่ได้จากการป้อนข้อมูลชุดเดิมมีความแปรปรวนมากขึ้นเช่นกัน
หากปรับค่า temperature ให้เข้าใกล้ศูนย์ ผลลัพธ์จะมีแนวโน้มคงที่มากขึ้น แต่ก็ยังไม่สามารถรับประกันความแน่นอน (determinism) ได้อย่างสมบูรณ์ เนื่องจากอาจเกิดความแตกต่างเล็กน้อยได้จากการประมวลผลแบบขนานในส่วนของแบ็กเอนด์ (backend), ลำดับการคำนวณเลขทศนิยมของฮาร์ดแวร์ หรือการอัปเดตจากฝั่งผู้ให้บริการโมเดล
ในมุมมองของการตรวจสอบ (audit) การบันทึกค่าพารามิเตอร์เหล่านี้ลงในบันทึก (log) เป็นสิ่งที่ขาดไม่ได้ หากไม่มีการบันทึกค่า temperature, top-p, ค่า seed และเวอร์ชันของโมเดลไว้ เราจะสูญเสียฐานข้อมูลในการอภิปรายย้อนหลังว่า "เหตุใดจึงได้ผลลัพธ์เช่นนี้" การรับมือในทางปฏิบัติจึงไม่ใช่การตำหนิความไม่คงที่ของผลลัพธ์ แต่เป็นการบันทึกเงื่อนไขที่ทำให้เกิดความไม่คงที่นั้นไว้อย่างครบถ้วน
สำหรับการตอบสนองแบบครั้งเดียว (single-turn) ความผันผวนของเอาต์พุตสามารถสังเกตเห็นได้ในจุดเดียว แต่ปัญหาจะเกิดขึ้นเมื่อเอเจนต์ทำงานโดยเชื่อมโยงหลายขั้นตอนเข้าด้วยกัน ในโครงสร้างที่เอาต์พุตของขั้นตอนหนึ่งกลายเป็นอินพุตของขั้นตอนถัดไป ความผันผวนเพียงเล็กน้อยในระยะเริ่มต้นอาจส่งผลให้การตัดสินใจในขั้นตอนหลังๆ เปลี่ยนไปอย่างมาก
ตัวอย่างเช่น หากการเลือกเครื่องมือในขั้นตอนแรกเปลี่ยนไปเพียงเล็กน้อย ข้อมูลที่ได้รับก็จะเปลี่ยนไป และอาจส่งผลให้ข้อสรุปสุดท้ายแตกต่างไปจากเดิมอย่างสิ้นเชิง เนื่องจากแต่ละขั้นตอนระหว่างทางดูเหมือนเป็นการตัดสินใจที่ "สมเหตุสมผล" เมื่อมองเพียงแค่ผลลัพธ์ จึงไม่สามารถระบุได้ว่าจุดที่เกิดการเบี่ยงเบนนั้นอยู่ที่ใด
ในการตรวจสอบ (audit) ห่วงโซ่การทำงานนี้ จำเป็นต้องบันทึกสถานะระหว่างกลางของทุกขั้นตอนไว้ หากมีการเก็บอินพุต เอาต์พุต และเครื่องมือที่เลือกไว้ในแต่ละขั้นตอนตามลำดับเวลา เราจะสามารถย้อนกลับจากผลลัพธ์สุดท้ายเพื่อแยกแยะได้ว่าขั้นตอนใดคือจุดเปลี่ยน การออกแบบที่ละเว้นข้อมูลระหว่างทางและเก็บไว้เพียงผลลัพธ์สุดท้าย จะทำให้การติดตามห่วงโซ่การทำงานนี้เป็นไปไม่ได้
ก่อนที่จะกำหนดรายการบันทึก (Log) ทันทีนั้น มีข้อกำหนดเบื้องต้นที่ควรเตรียมให้พร้อมเสียก่อน หากเริ่มลงมือเขียนโปรแกรมโดยที่ยังไม่ชัดเจนว่าต้องบันทึกอะไร ไว้ที่ไหน และใครเป็นผู้รับผิดชอบ ก็มักจะนำไปสู่การต้องกลับมาออกแบบใหม่ในภายหลัง ในที่นี้เราจะมาตรวจสอบ 3 ข้อกำหนดเบื้องต้นก่อนเริ่มลงมือออกแบบกัน
สิ่งแรกที่ต้องกำหนดคือขอบเขตว่าจะให้การตรวจสอบครอบคลุมถึงส่วนใด หากพยายามบันทึกการทำงานทั้งหมดของ Agent อย่างละเอียดเท่ากันหมด ต้นทุนและภาระในการดำเนินงานจะสูงจนไม่สามารถทำได้จริง
ในทางปฏิบัติ การแยกพิจารณาระหว่างการดำเนินการที่มีความเสี่ยงสูงและต่ำจะช่วยให้จัดการได้ง่ายขึ้น ขั้นตอนที่เกี่ยวข้องกับการเขียนข้อมูลภายนอก, การชำระเงิน, การเข้าถึงข้อมูลส่วนบุคคล และการดำเนินการที่ไม่สามารถย้อนกลับได้ ควรบันทึกรายละเอียดให้ครบถ้วนที่สุดตั้งแต่ข้อมูลนำเข้าจนถึงเหตุผลประกอบการตัดสินใจ ในขณะที่กระบวนการที่เน้นการอ่านข้อมูลเป็นหลักและมีขอบเขตผลกระทบต่ำ อาจเลือกบันทึกเพียงแค่สรุปสาระสำคัญเท่านั้น
การกำหนดขอบเขตไม่ใช่สิ่งที่ทำเพียงครั้งเดียวแล้วจบไป เมื่อมีการเพิ่มเครื่องมือหรือสิทธิ์ใหม่ให้กับ Agent จะต้องตรวจสอบเสมอว่าการดำเนินการเหล่านั้นรวมอยู่ในขอบเขตการตรวจสอบแล้วหรือไม่ หากเพิ่มฟังก์ชันการทำงานโดยปล่อยให้การออกแบบขอบเขตคลุมเครือ อาจนำไปสู่สถานการณ์ที่การดำเนินการซึ่งต้องรับผิดชอบต่อสาธารณะมากที่สุดกลับตกหล่นไปจากบันทึกการตรวจสอบ
監査ログ(Audit Log)は「いざというときに参照できる」ことに価値があるため、保存先の選定と保持期間の設計が品質を左右します。アプリケーションの稼働ログと同じ場所に混ぜると、ローテーションで古い記録が消え、肝心なときに証跡が残っていないことがあります。
保存インフラを選ぶ際は、改ざんが難しい形で書き込めること、検索性が確保できること、長期保管のコストが見合うことを基準にします。書き込み専用(追記のみ)の領域に出力すると、後からの編集を防ぎやすく、証跡としての信頼性が高まります。
保持ポリシーは、業務上・法令上の要請から逆算して決めます。規制対象の業務では数年単位の保管が必要になることもあり、その場合はコストを抑えるために、検索が速いホットな領域と低コストのアーカイブを分ける設計が現実的です。どれだけの期間・どの粒度で残すかを最初に文書化しておくことが、後の運用の判断を楽にします。
บันทึกการตรวจสอบ (Audit log) ไม่ได้เป็นเพียงกลไกทางเทคนิคเท่านั้น แต่ยังเป็นประเด็นเรื่องขอบเขตความรับผิดชอบขององค์กรด้วย จำเป็นต้องมีการกำหนดให้ชัดเจนก่อนเริ่มดำเนินการว่า ใครเป็นผู้ออกแบบบันทึก ใครเป็นผู้ตรวจสอบดูแลในแต่ละวัน และใครจะเป็นผู้รับผิดชอบในการชี้แจงเมื่อเกิดเหตุการณ์ไม่พึงประสงค์ (Incident)
สิ่งที่มักเกิดขึ้นบ่อยครั้งคือ ทีมพัฒนาคิดว่า "ได้มีการสร้างบันทึกไว้แล้ว" ในขณะที่ทีมปฏิบัติการคิดว่า "บันทึกนั้นไม่ได้ถูกแชร์มาเพื่อเป็นเป้าหมายในการตรวจสอบ" ส่งผลให้ไม่มีใครดูแลบันทึกเหล่านั้นเลย บันทึกที่ถูกสร้างออกมาเพียงอย่างเดียวจะทำหน้าที่เป็นหลักฐานได้ก็ต่อเมื่อมีการกำหนดผู้รับผิดชอบในการอ้างอิงและขั้นตอนการทำงานที่ชัดเจนเท่านั้น
ในการระบุบทบาทหน้าที่ให้เป็นลายลักษณ์อักษร ควรแยกความรับผิดชอบอย่างน้อย 3 ส่วน ได้แก่ "ผู้รับผิดชอบการออกแบบบันทึก" "ผู้รับผิดชอบการตรวจสอบดูแลตามปกติ" และ "ผู้รับผิดชอบในการชี้แจงเมื่อเกิดเหตุการณ์" การกำหนดล่วงหน้าว่าใครจะเป็นผู้แสดงหลักฐานและอธิบายเหตุผลเมื่อมีการสอบถามจากภายนอกเกี่ยวกับสิ่งที่เอเจนต์ (Agent) ได้ตัดสินใจไปนั้น คือสิ่งที่เปลี่ยนบันทึกการตรวจสอบจากการเป็นเพียงแค่บันทึกข้อมูลธรรมดา ให้กลายเป็นเครื่องมือสำหรับความรับผิดชอบ (Accountability)
เมื่อเตรียมความพร้อมเรียบร้อยแล้ว ขั้นตอนต่อไปคือการตัดสินใจว่าจะเก็บข้อมูลอะไรไว้บ้าง เพื่อให้สามารถทั้ง "จำลองสถานการณ์" และ "อธิบาย" ได้อย่างสมบูรณ์ จำเป็นต้องรวบรวมข้อมูลประกอบการตัดสินใจที่นำไปสู่ผลลัพธ์นั้นอย่างครบถ้วนโดยไม่ตกหล่น ในที่นี้จะแบ่งรายการที่ควรบันทึกออกเป็น 3 ระดับ ดังนี้
สิ่งที่ควรบันทึกไว้เป็นอันดับแรกคือ ข้อมูลนำเข้า (Input) ทั้งหมดที่ถูกส่งไปยังโมเดลในการประมวลผลนั้นๆ ไม่ใช่เพียงแค่คำถามจากผู้ใช้เท่านั้น แต่รวมถึง System Prompt, เอกสารอ้างอิง, ประวัติการสนทนาที่ผ่านมา และตัวแปรที่ฝังอยู่ในเทมเพลต โดยต้องบันทึกสิ่งที่โมเดล "เห็น" จริงๆ ในขณะนั้นไว้ให้ครบถ้วน
ข้อควรระวังคือ ห้ามบันทึกข้อมูลนำเข้าโดยการสรุปหรือจัดรูปแบบใหม่ เพราะเมื่อต้องการนำกลับมาทำซ้ำหรือตรวจสอบในภายหลัง ข้อความดิบที่ถูกส่งไปจริงกับสรุปที่จัดทำขึ้นเพื่อให้มนุษย์อ่านง่ายนั้นมีความหมายที่แตกต่างกัน เมื่อถูกถามถึงเหตุผลในการตัดสินใจ สิ่งเดียวที่เชื่อถือได้คือสแนปชอตก่อนการจัดรูปแบบเท่านั้น
ในกรณีที่มีโครงสร้างแบบ RAG ซึ่งมีการดึงเอกสารจากภายนอกมาเพิ่มในบริบท (Context) จะต้องบันทึกผลลัพธ์ที่ดึงมาได้นั้นไว้ด้วย เนื่องจากแม้จะเป็นคำถามเดิม แต่เอกสารที่ถูกดึงมาอาจเปลี่ยนแปลงไปตามช่วงเวลาของการค้นหาหรือสถานะของดัชนี (Index) หากไม่บันทึกว่า "ในตอนนั้นอ่านอะไรไป" ก็จะไม่สามารถตัดสินความถูกต้องของผลลัพธ์ในภายหลังได้ ทั้งนี้ หากมีข้อมูลส่วนบุคคลรวมอยู่ด้วย จะต้องออกแบบระบบโดยคำนึงถึงการเข้ารหัสและการควบคุมการเข้าถึงข้อมูลในขณะจัดเก็บควบคู่กันไปด้วย
แม้จะเป็นอินพุตเดียวกัน แต่เอาต์พุตจะเปลี่ยนไปตามรุ่นของโมเดลและพารามิเตอร์ที่ใช้สอบถาม ดังนั้น จึงควรบันทึกเงื่อนไขการเรียกใช้งานในแต่ละครั้งไว้เสมอ ไม่ว่าจะเป็นชื่อรุ่นและเวอร์ชันของโมเดล, การตั้งค่าการสุ่ม (Sampling) เช่น Temperature หรือ top-p, ค่า Seed และจำนวนโทเค็นสูงสุด (Max tokens)
โดยเฉพาะเวอร์ชันของโมเดลเป็นสิ่งที่มักถูกมองข้าม หากผู้ให้บริการโมเดลมีการอัปเดต พฤติกรรมของโมเดลอาจเปลี่ยนไปแม้จะใช้ Prompt เดิมทุกประการ เมื่อต้องตรวจสอบกรณีที่ "เดือนที่แล้วยังทำงานได้ปกติ" หากไม่ทราบว่าในตอนนั้นใช้เวอร์ชันใด ก็จะไม่สามารถแยกแยะสาเหตุของปัญหาได้
การบันทึกข้อมูลเวอร์ชันของโมเดลควบคู่ไปกับเวอร์ชันของโค้ดฝั่ง Agent จะมีประสิทธิภาพมาก เนื่องจากเทมเพลตของ Prompt และคำนิยามของเครื่องมือ (Tool definitions) ต่างก็เปลี่ยนแปลงไปตามโค้ด การเชื่อมโยงทั้ง "เวอร์ชันของโมเดล" และ "เวอร์ชันของการติดตั้งใช้งาน Agent" เข้าด้วยกัน จะช่วยให้สามารถติดตามได้ว่าการเปลี่ยนแปลงใดกันแน่ที่เป็นสาเหตุทำให้พฤติกรรมเปลี่ยนไป
การตัดสินใจของเอเจนต์ไม่ได้ขึ้นอยู่กับผลลัพธ์จากโมเดลเพียงอย่างเดียว แต่ยังขึ้นอยู่กับผลลัพธ์จากการเรียกใช้เครื่องมือภายนอกและ API อย่างมาก โดยจะต้องบันทึกข้อมูลว่าเรียกใช้เครื่องมือใด ด้วยอาร์กิวเมนต์อะไร และได้รับผลลัพธ์อย่างไร ตามลำดับการเรียกใช้จริงอย่างต่อเนื่อง
ประเด็นสำคัญในที่นี้คือการเก็บ "อาร์กิวเมนต์" และ "ค่าที่ส่งกลับ" ของการเรียกใช้ไว้เป็นคู่ ตัวอย่างเช่น หากมีการเรียกใช้เครื่องมือตรวจสอบสต็อกสินค้า จะต้องบันทึกทั้ง ID ที่ใช้ตรวจสอบและจำนวนสต็อกที่ได้รับกลับมา หากไม่บันทึกค่าที่ส่งกลับไว้ จะไม่สามารถสร้างสถานการณ์จำลองขึ้นมาใหม่เพื่อดูว่า "เอเจนต์ตัดสินใจจากข้อมูลใด" ได้ และไม่สามารถตรวจสอบความถูกต้องของผลลัพธ์ได้
จำเป็นต้องระมัดระวังเป็นพิเศษเนื่องจาก API ภายนอกอาจมีการตอบสนองที่เปลี่ยนแปลงไปตามกาลเวลา เป็นเรื่องปกติที่การเรียกใช้เครื่องมือเดิมด้วยอาร์กิวเมนต์เดิมในวันถัดไปอาจให้ผลลัพธ์ที่แตกต่างออกไป ด้วยเหตุนี้ การบันทึกค่าที่ส่งกลับ ณ ขณะนั้นไว้เป็นหลักฐาน (Audit Trail) จึงเป็นสิ่งที่ทำให้สามารถอธิบายเหตุผลย้อนหลังได้ รวมถึงพฤติกรรมที่ผิดปกติ เช่น ความล้มเหลว (Failure), การหมดเวลา (Timeout) และการลองใหม่ (Retry) ก็ควรได้รับการบันทึกไว้ในระดับความละเอียดเดียวกัน
เมื่อกำหนดรายการที่ต้องการบันทึกได้แล้ว ให้จัดโครงสร้างข้อมูลเหล่านั้นให้อยู่ในรูปแบบที่สามารถสืบค้นย้อนหลังได้ การแสดงผลเป็นข้อความที่กระจัดกระจายเพียงอย่างเดียวจะทำให้ไม่สามารถค้นหาบันทึกที่ต้องการได้ในเวลาที่จำเป็น ในส่วนนี้จะกล่าวถึงหลักการออกแบบ 3 ประการที่ช่วยให้เกิดทั้งความสามารถในการทำซ้ำ (Reproducibility) และความสามารถในการสืบค้น (Searchability)
การดำเนินการของผู้ใช้หนึ่งครั้งอาจถูกแบ่งออกเป็นหลายการเรียกใช้โมเดล (model calls) และการทำงานของเครื่องมือ (tool executions) ภายในระบบ เพื่อให้สามารถติดตามการทำงานทั้งหมดนี้ได้ในภายหลัง จึงจำเป็นต้องสร้าง Trace ID ที่ไม่ซ้ำกัน ณ จุดเริ่มต้นของการทำงาน และกำกับ ID เดียวกันนี้ไว้ในบันทึก (log) ที่เกี่ยวข้องทั้งหมด
หากมี Trace ID เราสามารถรวบรวมบันทึกที่เกี่ยวข้องตามลำดับเวลาได้เพียงแค่ค้นหาด้วย ID นั้น เพื่อดูว่า "เกิดอะไรขึ้นในเหตุการณ์นี้" ในทางกลับกัน หากไม่มี ID เราจะต้องอาศัยเพียงการประทับเวลา (timestamp) และชื่อผู้ใช้ในการปะติดปะต่อบันทึกที่กระจัดกระจาย ซึ่งมักทำให้เกิดความสับสนได้ง่ายเมื่อมีการทำงานหลายอย่างเกิดขึ้นพร้อมกัน
สำหรับเอเจนต์แบบหลายขั้นตอน (multi-step agents) การใช้ Trace ID (สำหรับการทำงานทั้งหมด) ร่วมกับ Span ID (สำหรับแต่ละขั้นตอน) ในรูปแบบลำดับชั้นจะช่วยให้ติดตามได้ง่ายขึ้น การใช้ ID ที่ครอบคลุมการทำงานทั้งหมดร่วมกับ ID ที่ระบุแต่ละขั้นตอน จะช่วยให้สามารถไล่เรียงโครงสร้างแบบต้นไม้ (tree structure) เพื่อดูได้ว่า "การทำงานแยกสาขาออกไปที่ขั้นตอนใด"
การเรียงลำดับบันทึก (Log) ตามลำดับเวลาเท่านั้นที่จะทำให้เราอ่านกระแสการตัดสินใจได้ ในแต่ละเรคคอร์ดจะต้องบันทึก Timestamp ที่ระบุเวลาที่เกิดเหตุการณ์ด้วยความแม่นยำสูงที่สุดเท่าที่จะเป็นไปได้ หากบันทึกเป็นหน่วยวินาที เมื่อมีหลายเหตุการณ์เกิดขึ้นในเสี้ยววินาทีเดียวกัน เราจะไม่สามารถระบุลำดับก่อนหลังได้
อย่างไรก็ตาม Timestamp เพียงอย่างเดียวอาจไม่สามารถรับประกันลำดับความเป็นเหตุเป็นผลได้เสมอไป ในกรณีที่การประมวลผลทำงานข้ามหลายเซิร์ฟเวอร์ ความคลาดเคลื่อนของนาฬิกาในแต่ละเซิร์ฟเวอร์อาจทำให้เหตุการณ์ที่เกิดขึ้นจริงทีหลังถูกบันทึกไว้ก่อนได้
เพื่อหลีกเลี่ยงปัญหานี้ นอกจากเวลาแล้ว ควรเพิ่มหมายเลขลำดับ (Serial Number) ที่แสดงลำดับการทำงานของขั้นตอน หรือตัวระบุ (Identifier) ที่อ้างอิงถึงขั้นตอนก่อนหน้า การบันทึก "เกิดขึ้นเมื่อใด" และ "เกิดขึ้นตามลำดับใด" แยกจากกัน จะช่วยให้สามารถสร้างความสัมพันธ์เชิงเหตุผลขึ้นมาใหม่ได้โดยไม่ขึ้นอยู่กับความคลาดเคลื่อนของนาฬิกา ร่องรอยหลักฐานที่ลำดับผิดพลาดอาจเป็นสาเหตุที่ทำให้ระบุจุดแยก (Branching point) ผิดพลาดได้
การส่งออกบันทึกการตรวจสอบ (Audit Log) เป็นข้อความที่มนุษย์อ่านได้จะทำให้ไม่สามารถค้นหาหรือสรุปผลด้วยเครื่องมืออัตโนมัติได้เมื่อปริมาณข้อมูลเพิ่มขึ้น การกำหนดรูปแบบให้เป็นมาตรฐานแบบโครงสร้าง เช่น JSON-Lines ซึ่งเขียน 1 เหตุการณ์ต่อ 1 บรรทัดในรูปแบบ JSON จะช่วยให้การวิเคราะห์ในภายหลังง่ายขึ้นอย่างมาก
ข้อดีของการทำโครงสร้างข้อมูลคือสามารถคัดกรองข้อมูลตามฟิลด์ได้ หากรูปแบบข้อมูลมีความสอดคล้องกัน การดำเนินการต่างๆ เช่น การดึงข้อมูล "เฉพาะ Trace ID ที่กำหนด" "เฉพาะการเรียกใช้เครื่องมือ" และ "เฉพาะรายการที่ล้มเหลว" สามารถทำได้ด้วยคำสั่ง Query เพียงไม่กี่บรรทัด ในขณะที่บันทึกแบบข้อความอิสระจะต้องใช้แรงงานคนในการอ่านเพื่อทำสิ่งเดียวกัน
เมื่อกำหนดรูปแบบข้อมูล ควรเริ่มจากการกำหนดฟิลด์ทั่วไปให้คงที่ เช่น Trace ID, Timestamp, ประเภทเหตุการณ์ (Event Type), เป้าหมาย และผลลัพธ์ โดยรวมรายละเอียดเฉพาะของแต่ละเหตุการณ์ไว้ภายใต้ฟิลด์เหล่านั้น หากมีการกำหนดชื่อและความหมายของฟิลด์ทั่วไปให้เป็นมาตรฐานตั้งแต่ต้น จะทำให้สามารถจัดการบันทึกข้อมูลข้ามเครื่องมือหรือข้ามทีมได้ ควรมีการจัดทำเอกสาร Schema และระบุเวอร์ชันเมื่อมีการเปลี่ยนแปลงเพื่อรักษาความเข้ากันได้ของข้อมูลไว้
เพียงแค่การเก็บ Log ไว้เฉยๆ ไม่สามารถทำให้เราทราบได้ว่ากำลังเกิดปัญหาขึ้น การมีกลไกที่คอยวัดค่าความไม่แน่นอน (Non-determinism) ในเชิงรุกว่าเกินขอบเขตที่ยอมรับได้หรือไม่เพื่อตรวจจับความผิดปกติจึงเป็นสิ่งจำเป็น ในที่นี้ เราจะมาดูหัวใจสำคัญของการนำไปใช้งาน 3 ประการ เพื่อวัดค่าความผันผวน (Jitter) และนำไปสู่การแจ้งเตือน (Alert)
วิธีพื้นฐานในการตรวจสอบระดับของความไม่แน่นอน (non-determinism) คือการป้อนข้อมูลชุดเดิมซ้ำหลายๆ ครั้ง เพื่อวัดว่าผลลัพธ์มีความคลาดเคลื่อนมากน้อยเพียงใด การรันเพียงครั้งเดียวไม่สามารถแยกแยะได้ว่าผลลัพธ์นั้นมีความเสถียรหรือเป็นเพียงค่าผิดปกติ (outlier) ที่เกิดขึ้นโดยบังเอิญ
ในทางปฏิบัติ เราจะเลือกข้อมูลนำเข้าที่เป็นตัวแทนมาจำนวนหนึ่ง แล้วรันซ้ำแต่ละรายการเพื่อสังเกตการกระจายตัวของผลลัพธ์ หากผลลัพธ์ออกมาใกล้เคียงกันทุกครั้ง แสดงว่ามีความเสถียร แต่หากผลลัพธ์แตกต่างกันมาก ก็สามารถตัดสินได้ว่าพฤติกรรมของเอเจนต์ (agent) ต่อข้อมูลนำเข้านั้นไม่น่าเชื่อถือ
การวัดผลนี้ยังสามารถใช้เป็นการตรวจสอบการถดถอย (regression check) เมื่อมีการเปลี่ยนแปลงโมเดล, พรอมต์ (prompt) หรือคำจำกัดความของเครื่องมือ (tool definition) โดยการป้อนชุดข้อมูลเดิมทั้งก่อนและหลังการเปลี่ยนแปลง เพื่อเปรียบเทียบว่าความคลาดเคลื่อนหรือข้อสรุปมีการเปลี่ยนแปลงไปจากที่คาดไว้หรือไม่ แม้จะไม่สามารถกำจัดความไม่แน่นอนให้หมดไปได้โดยสิ้นเชิง แต่การทราบว่า "ข้อมูลนำเข้าชุดใดที่ทำให้เกิดความผันผวนได้ง่าย" จะช่วยให้เราสามารถมุ่งเน้นการเฝ้าระวังในส่วนที่มีความเสี่ยงสูงได้
เมื่อวัดความแปรปรวนของผลลัพธ์ หากตัดสินเพียงแค่ว่าสตริง (string) ตรงกันหรือไม่ จะทำให้เข้าใจสถานการณ์จริงคลาดเคลื่อนได้ ในขณะที่หากสรุปเหมือนกันแม้สำนวนจะต่างกันก็ไม่มีปัญหา แต่ถ้าข้อความเกือบเหมือนกันแต่ตัวเลขหรือข้อสรุปต่างกันเพียงจุดเดียว ก็อาจกลายเป็นความแตกต่างที่ร้ายแรงได้ สิ่งสำคัญคือการจับให้ได้ว่ามีความแตกต่างกันในระดับความหมายมากน้อยเพียงใด
วิธีหนึ่งในการวัดความแตกต่างเชิงความหมายคือการแปลงผลลัพธ์ให้เป็นเวกเตอร์ฝังตัว (embedding vector) แล้วดูความคล้ายคลึงจากระยะห่างระหว่างเวกเตอร์ วิธีนี้ช่วยให้สามารถจัดการกับความใกล้เคียงของเนื้อหาด้วยตัวเลขได้โดยไม่ถูกรบกวนจากความแตกต่างเล็กน้อยของสำนวน อย่างไรก็ตาม วิธีนี้ไม่ใช่คำตอบสำเร็จรูป เพราะอาจมองข้ามกรณีที่ความหมายดูใกล้เคียงกันแต่ข้อสรุปกลับตรงกันข้ามได้
ดังนั้น สำหรับความแตกต่างที่ส่งผลกระทบต่อธุรกิจ (เช่น การตัดสินใจขั้นสุดท้าย ตัวเลข หรือการดำเนินการ) นอกเหนือจากการวัดความคล้ายคลึงเชิงความหมายแล้ว จำเป็นต้องแยกรายการเหล่านั้นออกมาตรวจสอบอย่างเข้มงวด การกำหนดว่าอะไรคือ "ความผันผวนที่ยอมรับได้" และอะไรคือ "ความแตกต่างที่ห้ามมองข้าม" จำเป็นต้องนิยามตามลักษณะของงานนั้นๆ
แม้จะสร้างกลไกการตรวจจับขึ้นมาได้ แต่ก็ไม่มีใครสามารถเฝ้าดูบันทึก (Log) ได้ตลอดเวลา จึงจำเป็นต้องออกแบบเกณฑ์ (Threshold) และเส้นทางการแจ้งเตือน (Escalation) เพื่อให้ระบบส่งการแจ้งเตือนโดยอัตโนมัติเมื่อความผันผวนหรือความผิดปกติเกินระดับที่กำหนดไว้
ในการตั้งค่าเกณฑ์นั้น หากตั้งค่าเข้มงวดเกินไป การแจ้งเตือนจะดังไม่หยุดจนถูกเพิกเฉย แต่หากตั้งค่าหลวมเกินไปก็จะพลาดความผิดปกติที่เกิดขึ้นจริง วิธีที่สมเหตุสมผลคือเริ่มจากเกณฑ์ที่พอเหมาะ แล้วค่อยปรับจูนตามการแจ้งเตือนที่เกิดขึ้นจริงระหว่างการใช้งาน สำหรับเกณฑ์ในการตัดสินว่าสิ่งใดคือความผิดปกตินั้น นอกจากจะใช้ค่าคงที่แล้ว ยังสามารถกำหนดโดยอิงจากการเบี่ยงเบนของข้อมูลจากค่าปกติได้อีกด้วย
สำหรับการแจ้งเตือน (Escalation) ควรแบ่งระดับตามขนาดของผลกระทบ โดยแยกเส้นทางตามความเหมาะสม เช่น หากมีความผันผวนเล็กน้อยให้เพียงแค่บันทึกไว้, หากเกินขอบเขตที่ยอมรับได้ให้แจ้งเตือนผู้รับผิดชอบ, หรือหากเป็นความผิดปกติที่เกี่ยวข้องกับการดำเนินการที่ไม่สามารถย้อนกลับได้ ให้หยุดกระบวนการและรอการตรวจสอบจากมนุษย์ การออกแบบตั้งแต่การตรวจจับไปจนถึงการกำหนดว่าใครต้องทำอะไร คือจุดเริ่มต้นที่ทำให้การตรวจจับความผิดปกติสามารถป้องกันความเสียหายที่เกิดขึ้นจริงได้
สุดท้ายนี้ ขอยกประเด็นที่มักจะติดขัดในการนำไปใช้งานจริงมา 2 ประเด็น ทั้งสองประเด็นนี้มักจะปรากฏให้เห็นในรูปแบบที่ว่า "ใช้งานได้จริง แต่กลับไม่เป็นประโยชน์ในยามคับขัน" ซึ่งควรหลีกเลี่ยงตั้งแต่ในขั้นตอนการออกแบบ
ความผิดพลาดที่พบบ่อยที่สุดคือการบันทึกเฉพาะผลลัพธ์สุดท้ายไว้ใน Log โดยละทิ้งกระบวนการตัดสินใจระหว่างทางที่นำไปสู่ผลลัพธ์นั้น แม้ดูเผินๆ อาจดูเหมือนว่าแค่รู้ผลลัพธ์ก็เพียงพอแล้ว แต่สำหรับการออกแบบ Agent ที่มีความไม่แน่นอน (Non-deterministic) แนวทางนี้ถือเป็นจุดตาย
หากพยายามหาสาเหตุโดยอาศัยเพียงบันทึกผลลัพธ์ วิธีเดียวที่ทำได้คือ "การป้อนข้อมูลเดิมซ้ำอีกครั้งเพื่อจำลองเหตุการณ์" ทว่าเนื่องจาก Agent อาจให้ผลลัพธ์ที่แตกต่างออกไปแม้จะได้รับข้อมูลขาเข้าชุดเดิม การป้อนข้อมูลซ้ำจึงไม่สามารถจำลองสถานการณ์ในขณะนั้นได้ และจะทำให้การตรวจสอบต้องกลับไปเริ่มต้นใหม่ หากไม่มีการบันทึกข้อมูลระหว่างทางว่าได้เรียกใช้เครื่องมือใด ได้รับผลตอบรับอย่างไร และตัดสินใจอย่างไร ก็จะไม่มีใครสามารถอธิบายความสมเหตุสมผลของผลลัพธ์นั้นได้
หลักการเพื่อหลีกเลี่ยงความผิดพลาดนี้ทำได้ง่ายๆ คือ ให้ละทิ้งสมมติฐานที่ว่า "ค่อยกลับมาป้อนข้อมูลซ้ำภายหลังก็ได้" แล้วหันมาใช้วิธีบันทึกข้อมูลประกอบการตัดสินใจและสถานะระหว่างทางทั้งหมดให้คงที่ไว้ในขณะที่ดำเนินการจริง การลดทอนข้อมูลระหว่างทางโดยอ้างเรื่องต้นทุนของ Storage ถือเป็นการแลกเปลี่ยนที่ทำให้สูญเสียหลักฐานสำคัญในเวลาที่ต้องรับผิดชอบต่อผลลัพธ์มากที่สุด
เมื่อเข้าใจถึงความสำคัญของการเก็บข้อมูลไว้จนถึงระดับกลางแล้ว ปัญหาถัดไปก็จะเกิดขึ้น หากเราเก็บทุกอย่างไว้ด้วยรายละเอียดสูงสุด ปริมาณของ Log จะเพิ่มขึ้นอย่างรวดเร็ว จนส่งผลให้ต้นทุนด้านพื้นที่จัดเก็บ (Storage) และประสิทธิภาพในการค้นหาไม่สามารถใช้งานได้จริงในทางปฏิบัติ
พื้นฐานในการรับมือกับความสมดุลนี้คือการกลับไปใช้แนวคิดเรื่อง Scope ที่กล่าวไว้ข้างต้น โดยการแบ่งระดับความละเอียด (Granularity) ตามความเหมาะสม เช่น การบันทึกรายละเอียดในส่วนของปฏิบัติการที่มีความเสี่ยงสูง และบันทึกในระดับสรุปสำหรับกระบวนการที่มีผลกระทบน้อย การออกแบบที่บันทึกทุกอย่างด้วยความละเอียดสูงสุดอย่างเท่าเทียมกันนั้น ไม่สามารถทำได้ในระยะยาวทั้งในแง่ของต้นทุนและประสิทธิภาพการค้นหา
นอกจากนี้ การจัดลำดับชั้นตามระยะเวลาการจัดเก็บ (Retention Period) ก็เป็นวิธีที่มีประสิทธิภาพ โดยจัดเก็บข้อมูลล่าสุดไว้ในพื้นที่ที่เข้าถึงได้ง่าย (Hot Storage) และย้ายข้อมูลที่ผ่านช่วงเวลาหนึ่งไปไว้ในที่จัดเก็บถาวร (Archive) ที่มีต้นทุนต่ำกว่า สำหรับ Payload ที่มีขนาดใหญ่ (เช่น เนื้อหาทั้งหมดของเอกสารอ้างอิง) อาจใช้วิธีเก็บไว้ในแหล่งจัดเก็บอื่นแล้วบันทึกเพียงการอ้างอิงไว้ใน Log แทน การรักษาต้นทุนและความครบถ้วนของหลักฐานไม่ใช่เรื่องที่ต้องเลือกอย่างใดอย่างหนึ่ง แต่เป็นปัญหาที่ต้องแก้ไขด้วยการออกแบบระดับความละเอียดและระยะเวลาการจัดเก็บให้สอดคล้องกัน
Chi
ศึกษาเอกวิทยาการสารสนเทศที่มหาวิทยาลัยแห่งชาติลาว และระหว่างศึกษาได้มีส่วนร่วมในการพัฒนาซอฟต์แวร์ทางสถิติ สั่งสมพื้นฐานด้านการวิเคราะห์ข้อมูลและการเขียนโปรแกรมอย่างเป็นรูปธรรม ตั้งแต่ปี 2021 ได้ก้าวเข้าสู่เส้นทางการพัฒนา Web และแอปพลิเคชัน และตั้งแต่ปี 2023 เริ่มสั่งสมประสบการณ์การพัฒนาอย่างจริงจังทั้งในด้าน Frontend และ Backend ในบริษัทปัจจุบันรับผิดชอบการออกแบบและพัฒนาบริการ Web ที่ใช้ AI โดยมีส่วนร่วมในโครงการที่นำการประมวลผลภาษาธรรมชาติ (NLP) การเรียนรู้ของเครื่อง (Machine Learning) และ Generative AI รวมถึงโมเดลภาษาขนาดใหญ่ (LLM) มาผสานรวมกับระบบงานจริง มีความกระตือรือร้นในการติดตามเทคโนโลยีล่าสุดอยู่เสมอ และให้ความสำคัญกับความรวดเร็วในการดำเนินงานตั้งแต่การพิสูจน์แนวคิดทางเทคนิคไปจนถึงการนำไปใช้งานจริง