คุณตื่นมาพบกับรายงานบั๊กวิกฤต 5 รายการในเช้าวันจันทร์ เครื่องมือตรวจสอบของคุณทำงานได้ดีเยี่ยม มันตรวจจับรายงานการแครช (crash report) ทุกรายการ รีวิวหนึ่งดาวที่เต็มไปด้วยความโกรธแค้นทุกอัน และข้อความ "แอปค้างเมื่อฉันกดบันทึก" ทุกข้อความ คุณรู้แน่ชัดว่าอะไรที่เสีย แต่สิ่งที่คุณไม่รู้คือต้องไปดูที่ไหน

นั่นคือกำแพงที่ผมเจอหลังจากสร้างไพป์ไลน์แรกของผม มันตรวจสอบรีวิวแอปและล็อกการแครช (crash logs) ที่เข้ามาได้โดยไม่มีปัญหา โดยการจัดหมวดหมู่ฟีดแบ็กแต่ละชิ้นลงในถังที่จัดระเบียบไว้อย่างดี: บั๊ก, การแครช หรือการขอฟีเจอร์ใหม่ แดชบอร์ดดูเหมือนจะปกติดี แต่กระบวนการดีบั๊ก (debugging) จริงๆ นั้นไม่ใช่เลย

การรู้ว่ามีบั๊กอยู่เป็นเพียงก้าวแรกจากระยะทางหนึ่งไมล์ ผมยังต้องเปิด IDE, ใช้ grep ไล่ดูโมดูลต่างๆ, ตรวจสอบ stack traces เทียบกับโค้ดปัจจุบัน และสร้างเส้นทางการเกิดข้อผิดพลาดขึ้นมาใหม่ในหัว เมื่อตั๋ว (tickets) เริ่มกองพะเนินและกาแฟยังร้อนอยู่ การขุดค้นทางโบราณคดีด้วยมือแบบนั้นมันเผาผลาญเวลาที่คุณไม่มี ผมต้องการให้ไพป์ไลน์ทำได้มากกว่าแค่การแจ้งปัญหา ผมต้องการให้มันช่วยสืบสวนด้วย

ดังนั้นผมจึงสร้างระบบขึ้นมาใหม่โดยมีเป้าหมายเดียว: รับรายงานบั๊กดิบๆ มาแล้วส่งผลการวินิจฉัยที่ผ่านการตรวจสอบแล้วกลับมา ไม่ใช่แค่ย่อหน้าความคิดเห็นของ LLM แต่เป็นผลลัพธ์ที่มีโครงสร้าง ซึ่งระบุชื่อไฟล์, ชี้ไปยังบรรทัด, ประเมินความเสี่ยง และแนะนำวิธีแก้ไข นี่คือวิธีที่มันถูกสร้างขึ้นมา

ทำไมโครงสร้างถึงดีกว่าบันทึกการแชท

ผมสร้าง investigating agent ด้วย PydanticAI เหตุผลนั้นเรียบง่าย เมื่อคุณขอให้โมเดลภาษาใช้เหตุผลเกี่ยวกับโค้ด ผลลัพธ์เริ่มต้นของมันคือข้อความที่ไหลลื่นและเป็นกันเอง นั่นอาจช่วยผู้อ่านที่เป็นมนุษย์ได้ แต่สำหรับสคริปต์ที่ทำงานต่อจากนั้น มันไร้ประโยชน์สิ้นดี ผมต้องการข้อตกลงที่เครื่องสามารถอ่านได้ (machine-readable contract)

Agent จะส่งคืนโมเดลข้อมูลที่ผ่านการตรวจสอบแล้วซึ่งประกอบด้วยสี่ฟิลด์เฉพาะ: สาเหตุหลัก (root cause), ไฟล์ที่ได้รับผลกระทบ, การเปลี่ยนแปลงที่เสนอ และการประเมินความซับซ้อนและความเสี่ยง หากโมเดลขาดฟิลด์ใดฟิลด์หนึ่งหรือเกิดอาการหลอน (hallucinates) เกี่ยวกับเส้นทางไฟล์ การตรวจสอบความถูกต้องจะล้มเหลวและผมจะตรวจพบได้ทันที ความเข้มงวดนี้ช่วยให้ไพป์ไลน์ทำงานได้อย่างถูกต้องแม่นยำ

เพื่อทำการสืบสวนจริงๆ Agent จะได้รับเครื่องมือแบบ read-only เพียงสี่อย่างและไม่มีอย่างอื่นเลย มันสามารถค้นหาโค้ดผ่าน grep, อ่านช่วงบรรทัดที่ระบุจากไฟล์, แสดงรายการเนื้อหาในไดเรกทอรี และระบุตำแหน่งของสัญลักษณ์ (symbols) เช่น คลาสหรือฟังก์ชัน การเป็น read-only คือส่วนที่สำคัญ ผมไม่ต้องการให้ Agent ที่มีสิทธิ์เขียน (write access) เดินเพ่นพ่านไปทั่ว repository ของผมตอนตีสอง เข้าใจก่อน แล้วค่อยแก้ไข

Repo Map: บริบทก่อนเครื่องมือ

Agent เวอร์ชันแรกมีความแม่นยำแต่ใช้ทรัพยากรสูงมาก มันเผาผลาญโทเคน (tokens) เหมือนนักท่องเที่ยวที่เดินวนเป็นวงกลม โมเดลจะเรียก list-dir จากนั้น grep แล้วอ่านไฟล์ แล้วก็เรียก list-dir อีกครั้ง ค่อยๆ ประกอบโมเดลทางความคิดของโครงสร้างโปรเจกต์ทีละโทเคนซึ่งมีราคาแพง

วิธีแก้คือการสร้าง repo map ที่กะทัดรัดขึ้นมาก่อนที่ Agent จะเริ่มทำงาน แผนผังนี้คือภาพรวมที่กลั่นกรองมาแล้วของ repository: ไฟล์สำคัญ, ฟังก์ชันหรือคลาสหลักของไฟล์เหล่านั้น และการเชื่อมต่อกันของโมดูลหลักๆ ให้คิดซะว่าเป็นการยื่น GPS ให้กับ Agent แทนที่จะขอให้มันค้นหาเส้นทางด้วยการลองผิดลองถูก

เมื่อมีแผนผังนั้นอยู่ใน context window ของมัน Agent ก็จะไม่เสียเวลาเรียกใช้เครื่องมือเพื่อหาว่า src/utils/parser.ts มีอยู่จริงหรือไม่ เพราะมันรู้ภูมิประเทศอยู่แล้ว มันจะมุ่งตรงไปยังสันเขาที่มีควันไฟพวยพุ่งขึ้นมาทันที การเปลี่ยนแปลงเพียงอย่างเดียวนี้ช่วยตัดขั้นตอนการเดินหลงทางออกไปได้ทั้งหมด

Tool Funnel: การบังคับให้ได้ข้อสรุป

แม้จะมีแผนผัง แต่ Agent ก็ยังอาจลังเลได้ มันอาจจะพบไฟล์ที่น่าสงสัย จากนั้นก็เริ่มไม่แน่ใจในตัวเอง แล้วก็ค้นหาใหม่ แล้วก็อ่านไฟล์อื่น ติดอยู่ในลูปไม่สิ้นสุดของการ "ขอเช็คอีกแค่นิดเดียว" ผมจึงต้องการวิธีที่จะสร้างแรงขับเคลื่อน (momentum) ให้มัน

ผมได้นำระบบ tool funnel แบบสามระยะมาใช้ ซึ่งจะจำกัดสิ่งที่ Agent ทำได้เมื่อมันดำเนินขั้นตอนไปข้างหน้า

ระยะแรกคือการสำรวจ (exploration) Agent จะสามารถเข้าถึงเครื่องมือทั้งสี่อย่างได้อย่างเต็มที่ มันสามารถค้นหา, เรียกดู และอ่านอะไรก็ตามที่จำเป็นเพื่อจำลองบั๊กในการใช้เหตุผลของมัน

ระยะที่สองคือการเจาะลึก (deep-dive) เมื่อ Agent ระบุจุดที่น่าจะเป็นปัญหาได้แล้ว มันจะสูญเสียเครื่องมือในการค้นหาไป มันจะสามารถอ่านไฟล์ได้เท่านั้น ไม่มีการ grep อีกต่อไป ไม่มีการแสดงรายการไดเรกทอรี ในขั้นตอนนี้มันต้องศึกษาโค้ดที่มันพบแล้วและสร้างชุดหลักฐานของมันขึ้นมา

ระยะที่สามคือการแสดงผล (output) เครื่องมือทั้งหมดจะถูกล็อกไว้ Agent ไม่สามารถสอบถาม codebase ได้อีกต่อไป มันต้องนั่งลงและเขียนรายงาน สิ่งนี้ช่วยป้องกันวงจร "ขอเช็คอีกอย่างหนึ่ง" ที่ไม่สิ้นสุด

ระบบ funnel นี้ช่วยลดจำนวนการเรียกใช้เครื่องมือเฉลี่ยจากมากกว่าสี่สิบครั้งต่อการวิเคราะห์หนึ่งครั้ง ลงเหลือเพียงประมาณสิบครั้ง Agent ทำงานได้เร็วขึ้น ถูกลง และที่น่าแปลกคือมันมีความมั่นใจมากขึ้น เพราะมันถูกบังคับให้ต้องตัดสินใจเลือกข้อสรุป

การทำให้ Backend สามารถสลับเปลี่ยนได้

ผมไม่อยากผูกระบบไว้กับผู้ให้บริการโมเดลเพียงรายเดียว ผมใช้เอนจินที่แตกต่างกันไปตามลักษณะงาน บางครั้งก็ใช้ Claude Code บางครั้งก็ใช้ Grok Build หรือบางครั้งก็ใช้ตัวที่ราคาถูกที่สุดในขณะนั้น เพื่อให้ตรรกะหลักไม่ยึดติดกับผู้ให้บริการรายใดรายหนึ่ง (provider-agnostic) ผมจึงแบ่งการทำงานออกเป็นสองขั้นตอน

ขั้นตอนแรกคือการสำรวจ (exploration) ตัว coding agent ซึ่งจะเป็นโมเดลที่มีความสามารถใดก็ได้ จะทำหน้าที่อ่าน repo map, ใช้เครื่องมือต่างๆ และสร้างรายงานในรูปแบบ markdown ดิบออกมา นี่คือส่วนที่ต้องใช้การคิดซึ่งมีต้นทุนสูง

ขั้นตอนที่สองคือการจัดโครงสร้าง (structuring) โดยใช้ LLM ที่ราคาถูกและรวดเร็ว นำ markdown นั้นมาจัดรูปแบบใหม่ให้เป็น Pydantic model ที่เข้มงวด ขั้นตอนนี้แทบไม่ต้องใช้การใช้เหตุผล (reasoning) เลย เป็นเพียงการดึงข้อมูล (extraction) และการจัดรูปแบบ (formatting) เท่านั้น จึงสามารถรันบนฮาร์ดแวร์ที่มีสเปกไม่สูงมากได้

เนื่องจากมีการแบ่งขอบเขตที่ชัดเจน ผมจึงสามารถสลับ backend ได้โดยไม่ต้องไปแตะต้องตรรกะการตรวจสอบ (validation logic) เลย รายงาน markdown ทำหน้าที่เป็นตัวแปลงสัญญาณสากล (universal adapter) ระหว่าง "สมองส่วนสำรวจ" กับ "ผลลัพธ์ที่มีโครงสร้าง" ที่ผมนำไปใช้งานจริง

สิ่งที่ใช้งานได้จริง

การตั้งค่าแบบนี้เปลี่ยนวิธีการจัดการกับ issue ที่เข้ามาของผม เลเยอร์การจำแนกประเภท (classification layer) ยังคงทำหน้าที่แยกแยะ bug ออกจาก feature request เหมือนเดิม แต่ตอนนี้เลเยอร์การวิเคราะห์ (analysis layer) จะทำงานต่อทันที กว่าผมจะเปิด editor ขึ้นมา ผมก็มีทั้งเส้นทางไฟล์ (file path), ช่วงบรรทัด (line range) และข้อเสนอการเปลี่ยนแปลงที่รออยู่แล้ว ผมยังคงตรวจสอบทุกอย่างด้วยตัวเอง นี่คือการช่วยเหลือ ไม่ใช่ระบบขับเคลื่อนอัตโนมัติ (autopilot) แต่การรวบรวมบริบทที่เคย