ใส่ด่านขออนุมัติจากคนไว้ก่อน AI agent ลงมือ ทำได้จริงด้วย LangGraph interrupt_before และ OpenAI Agents SDK
AI agent ที่มีสิทธิ์ลงมือเองอาจทำของพังแบบย้อนไม่ได้ภายในไม่กี่วินาที ทางแก้ไม่ใช่การหวังว่าโมเดลจะไม่พลาด บทความนี้พาใส่ด่านขออนุมัติจากคนไว้ก่อน action เสี่ยง ด้วย LangGraph และ OpenAI Agents SDK ที่ลงมือทำตามได้จริง

Human-in-the-loop (HITL) approval gate คือการวางด่านให้คนตรวจสอบและกดอนุมัติก่อนที่ AI agent จะลงมือกับ action ที่มีความเสี่ยงสูง จากนั้น agent จึงจะทำงานต่อ บทความนี้สรุปวิธีสร้างด่านอนุมัติดังกล่าวด้วยโค้ดจริงจาก 2 เฟรมเวิร์กยอดนิยม ได้แก่ LangGraph ไลบรารีโอเพนซอร์สที่มองการทำงานของ agent เป็นโครงสร้างกราฟ และ OpenAI Agents SDK ชุดเครื่องมือทางการสำหรับสร้าง agent จาก OpenAI
ปัญหาใหญ่ของ AI agent ที่ได้รับสิทธิ์ลงมือทำสิ่งต่างๆ คือมันสามารถสั่งลบฐานข้อมูล production ทั้งก้อนได้ภายในเสี้ยววินาที และไม่มีปุ่ม undo ให้กดย้อนหลัง สิ่งนี้เกิดขึ้นได้เสมอเมื่อเราเชื่อมต่อ agent เข้ากับเครื่องมือที่ส่งผลกระทบต่อโลกจริง เช่น การลบไฟล์ สำรองข้อมูลทับ ส่งอีเมลหาลูกค้า หรือปรับแก้ตัวเลขการเงิน เมื่อโมเดลตีความผิดพลาดเพียงเล็กน้อย action ที่ไม่สามารถย้อนคืนได้ก็เกิดขึ้นไปแล้ว ทางแก้ที่ยั่งยืนจึงไม่ใช่การภาวนาให้โมเดลฉลาดจนไม่พลาด เพราะไม่ว่าโมเดลจะเก่งแค่ไหนก็ย่อมมีโอกาสผิดพลาดได้เสมอ สิ่งที่ต้องทำคือการใส่ approval gate ดักหน้า action อันตรายอย่างเป็นระบบ
ทำไมต้องมีคนอนุมัติ ทั้งที่โมเดลเก่งขึ้นเรื่อยๆ
ความฉลาดของโมเดลกับความปลอดภัยของระบบเป็นคนละเรื่องกัน โมเดลที่แม่นยำขึ้นช่วยลดความถี่ในการพลาด แต่ไม่ได้ลดระดับความเสียหายเมื่อเกิดความผิดพลาดขึ้น งานบางอย่างพลาดแล้วแก้ไขได้ง่าย เช่น พิมพ์ข้อความผิดก็ลบแล้วเขียนใหม่ แต่งานอีกกลุ่มพลาดแล้วเกิดความเสียหายทันที เช่น ลบข้อมูลใน production ส่งอีเมลผิดคน หรือตัดยอดเงินผิดบัญชี
เกณฑ์สำคัญในการออกแบบจึงไม่ใช่ "โมเดลฉลาดพอหรือยัง" แต่เป็น "ถ้า action นี้ผิดพลาด จะย้อนกลับได้หรือไม่" ถ้าคำตอบคือย้อนไม่ได้ จำเป็นต้องมีคนคอยอนุมัติเสมอ approval gate จึงไม่ใช่แค่ตัวป้องกันข้อผิดพลาด แต่ยังเป็นช่องทางให้เราเข้าไปปรับแก้ทิศทางของ agent ได้ทันท่วงทีก่อนที่ความเสียหายจะเกิดขึ้นจริง
แกน 4 จังหวะของ State-Managed Interruption

ก่อนลงมือเขียนโค้ด มาทำความเข้าใจแกนการทำงานร่วมกันก่อน ทุกเฟรมเวิร์กใช้แนวคิดเดียวกันที่เรียกว่า state-managed interruption หรือการสั่งหยุดระบบชั่วคราวโดยบันทึกสถานะทั้งหมดไว้อย่างสมบูรณ์ ซึ่งประกอบด้วย 4 จังหวะหลัก:
- หยุด (Interrupt): ปล่อยให้ agent ทำงานไปเรื่อยๆ จนถึงจุดก่อนเริ่ม action ที่มีความเสี่ยง แล้วสั่งพักการทำงานไว้ตรงนั้นทันที
- เซฟสถานะ (Save State): ในจังหวะที่หยุด ระบบจะบันทึกตัวแปร บริบทการสนทนา ความจำ และแผนงานทั้งหมดเก็บไว้ เหมือนการเซฟเกมก่อนเข้าด่านสำคัญ ข้อมูลทุกอย่างจะคงอยู่ครบถ้วนพร้อมกลับมาทำงานต่อ
- รอคนตัดสินใจ (Human Decision): ระบบส่งรายละเอียดของ action นั้นให้คนตรวจสอบ เช่น agent กำลังจะเรียกเครื่องมืออะไร ด้วยพารามิเตอร์แบบไหน เพื่อให้คนเลือกว่าจะอนุมัติหรือปฏิเสธ
- เดินต่อ (Resume): หากอนุมัติ agent จะเริ่มทำ action นั้นแล้วดำเนินงานตามแผนต่อ แต่หากปฏิเสธ agent จะยกเลิกคำสั่งนั้นและนำข้อเสนอแนะ (feedback) กลับไปคิดแผนใหม่
จุดเด่นของระบบนี้คือจังหวะเซฟสถานะ เพราะการบันทึกสถานะไว้อย่างครบถ้วนทำให้เราไม่ต้องรีบกดอนุมัติแบบ real-time จะปล่อยค้างไว้ข้ามวัน ปิดโปรแกรม แล้วค่อยกลับมาเปิดอนุมัติในภายหลังก็ทำได้ นี่คือสิ่งที่ทำให้ approval gate แตกต่างจากการเขียนคำสั่ง if รอรับ input ธรรมดา
วิธีทำด้วย LangGraph: ดักหน้า Node ด้วย interrupt_before
LangGraph ออกแบบการทำงานของ agent เป็นโครงสร้างกราฟที่ประกอบด้วย node ต่างๆ เชื่อมต่อกัน แนวทางของ LangGraph คือเราสามารถระบุได้ล่วงหน้าว่าต้องการให้ระบบหยุดก่อนเริ่มทำงานที่ node ไหน
โครงสร้างหลักคือ state ที่ส่งต่อระหว่าง node เปรียบเสมือนไฟล์ข้อมูลกลางของกราฟ ตัวอย่างเช่น agent ร่างอีเมล จะมี draft_node สำหรับเตรียมข้อความ และ send_node สำหรับส่งอีเมลจริง โดย send_node จะตรวจสอบค่า state["approved"] และจะทำงานเมื่อได้รับอนุมัติแล้วเท่านั้น
ขั้นตอนสำคัญอยู่ที่การ compile กราฟ โดยกำหนด 2 ส่วนหลัก:
app = graph.compile(
checkpointer=MemorySaver(),
interrupt_before=["send_message"],
)checkpointer=MemorySaver() ทำหน้าที่บันทึกสถานะ (checkpoint) ส่วน interrupt_before=["send_message"] คือคำสั่งแจ้งว่า "ให้หยุดการทำงานก่อนที่จะเข้าสู่ node ส่งข้อความ" การกำหนดค่านี้ตั้งแต่ตอน compile ช่วยรับประกันว่าระบบจะหยุดตรวจเช็กตรงจุดนี้ทุกครั้งอย่างสม่ำเสมอ
เมื่อรันด้วย app.stream(initial_state, config) ตัว agent จะทำงานมาจนถึงหน้า send_node แล้วหยุดรอ เราสามารถเรียกดูข้อมูลสถานะปัจจุบันได้ด้วย app.get_state(config) เพื่อตรวจดูว่ามีข้อความร่างหน้าตาอย่างไร เมื่อตรวจสอบเรียบร้อยแล้ว ก็ส่งผลการอนุมัติกลับเข้าไปใน state:
app.update_state(config, {"approved": True})
app.stream(None, config)update_state จะบันทึกผลการอนุมัติลงใน checkpoint และ app.stream(None, config) จะสั่งให้กราฟเริ่มทำงานต่อจากจุดเดิม การส่ง None คือสัญญาณบอกให้ระบบใช้สถานะที่มีอยู่เดิมทำงานต่อ agent ก็จะเข้าสู่ send_node และส่งอีเมลออกไปตามปกติ
วิธีทำด้วย OpenAI Agents SDK: กำหนด needs_approval ที่ Tool

OpenAI Agents SDK ใช้มุมมองที่ต่างออกไป แทนที่จะคอยคุม flow ของกราฟ SDK นี้เปิดให้เราติดป้ายกำกับที่ตัวฟังก์ชันหรือเครื่องมือ (tool) โดยตรงว่าเครื่องมือใดต้องขออนุมัติก่อนทำงาน
วิธีตั้งค่าง่ายที่สุดคือการใส่พารามิเตอร์ needs_approval=True ลงใน decorator:
@function_tool(needs_approval=True)
def delete_database(name: str): ...เมื่อ agent พยายามเรียกใช้ tool นี้ การทำงานจะหยุดลงทันที และ Runner.run(agent, ...) จะคืนค่าผลลัพธ์พร้อมข้อมูล .interruptions ซึ่งเก็บอ็อบเจกต์ ToolApprovalItem ที่ระบุชัดเจนว่า agent ตัวใดกำลังเรียก tool อะไร พร้อม arguments ที่ส่งเข้ามา ทำให้เรามีข้อมูลครบถ้วนสำหรับตัดสินใจ
ข้อได้เปรียบของ OpenAI Agents SDK คือความสะดวกในการแปลงสถานะเป็น JSON หรือสตริง:
state = result.to_state()
saved = state.to_json() # หรือ state.to_string()เราสามารถนำ JSON ที่บันทึกไว้กลับมาโหลดใหม่ได้ทุกเมื่อผ่าน RunState.from_json() หรือ RunState.from_string() ทำให้รองรับกระบวนการรออนุมัติข้ามระบบ ข้ามเซิร์ฟเวอร์ หรือหลังรีสตาร์ตโปรเซส เมื่อผู้ดูแลตัดสินใจแล้ว ก็สั่งการตาม interruption ที่เกิดขึ้น:
state.approve(interruption) # หรือ state.reject(interruption)จากนั้นสั่งให้ทำงานต่อด้วย Runner.run(agent, state) หากยังมี tool ตัวอื่นที่ต้องขออนุมัติค้างอยู่ ระบบก็จะวนกลับเข้าสู่ด่านตรวจสอบถัดไปโดยอัตโนมัติ
ปฏิเสธแล้วไม่ควรจบงาน แต่ต้องส่ง Feedback ให้ Agent แก้ไข
การปฏิเสธ (Reject) คำสั่ง ไม่ควรทำให้กระบวนการทั้งหมดหยุดชะงัก การปฏิเสธที่ดีควรส่งเหตุผลกลับไปให้ agent รับทราบ เพื่อให้มันนำไปปรับปรุงแผนงานใหม่ ใน OpenAI Agents SDK เราสามารถแนบข้อความชี้แจงเมื่อปฏิเสธได้ โดยกำหนดค่ารวมผ่าน RunConfig.tool_error_formatter หรือระบุเฉพาะเจาะจงรายครั้งด้วย state.reject(interruption, rejection_message=...)
ความแตกต่างระหว่าง "การสั่งยกเลิกเฉยๆ" กับ "การปฏิเสธพร้อมอธิบายเหตุผล" มีผลต่อประสิทธิภาพอย่างมาก หากบอกเพียงไม่อนุมัติ agent จะทำได้เพียงหยุดทำงาน แต่หากเราชี้แจงว่า "ห้ามส่งอีเมลหาลูกค้าทั้งหมด ให้เลือกส่งเฉพาะลูกค้าที่ยืนยันตัวตนแล้วเท่านั้น" agent จะนำเงื่อนไขใหม่ไปปรับแก้แผนและส่งข้อเสนอใหม่กลับมาให้เราตรวจอีกครั้ง Approval gate จึงกลายเป็นเครื่องมือที่ช่วยปรับแต่งพฤติกรรม agent ไปในตัว
ข้อควรระวังเมื่อออกแบบระบบรันระยะยาว
การบันทึกสถานะเพื่อรอการอนุมัติเป็นเวลานาน มีประเด็นทางเทคนิคที่ต้องวางแผนล่วงหน้า:
- การจัดการข้อมูลสำคัญ (Secrets & Sensitive Data): เมื่อแปลงสถานะเป็น JSON บริบท (context) ทั้งหมดจะถูกบันทึกไปด้วยเสมอ เอกสารทางการของ OpenAI Agents SDK แนะนำให้ระวังเรื่องการใส่ API key หรือความลับลงใน context เพราะข้อมูลเหล่านั้นจะติดไปกับไฟล์สถานะที่บันทึกไว้
- ความเข้ากันได้ของเวอร์ชัน (Version Drift): หากคำขออนุมัติถูกพักค้างไว้นาน ตัวโค้ด prompt หรือ schema ของ tool อาจถูกอัปเดตไปแล้วในโปรดักชัน เมื่อโหลดสถานะเก่าขึ้นมาอาจเกิดข้อผิดพลาดได้ ควรบันทึกหมายเลขเวอร์ชันของ agent และ SDK กำกับไว้กับไฟล์สถานะเสมอเพื่อความปลอดภัย
- Interruption ซ้อนใน Subagents: สำหรับ OpenAI Agents SDK เมื่อนำ agent ตัวหนึ่งไปรันเป็น tool ของอีกตัว (
Agent.as_tool()) แล้วเกิด interruption ภายใน ตอนกลับมาทำงานต่อให้สั่ง resume ผ่าน agent ตัวนอกสุดเสมอ เนื่องจากระบบจะส่งสถานะ interruption ทั้งหมดขึ้นมารวมที่ตัวนอกสุดให้อัตโนมัติ
3 ขั้นตอนเริ่มต้นนำไปใช้งานจริง
ไม่จำเป็นต้องรื้อระบบใหม่ทั้งหมดเพื่อเริ่มทำ human-in-the-loop สามารถเริ่มได้ตามลำดับนี้:
- ลิสต์ Action ที่มีความเสี่ยงสูงในระบบ: ระบุคำสั่งที่ส่งผลกระทบถาวร เช่น การลบไฟล์ การแก้ไขฐานข้อมูล การโอนเงิน หรือการส่งอีเมลออกภายนอก ส่วนคำสั่งอ่านข้อมูลหรือค้นหาเอกสารสามารถปล่อยผ่านได้
- ใส่ Approval Gate ให้กับ Action เสี่ยงตัวแรก: หากใช้ OpenAI Agents SDK ให้เพิ่ม
@function_tool(needs_approval=True)หรือหากใช้ LangGraph ให้ระบุ node นั้นลงในinterrupt_beforeตอน compile - ทดสอบและขยายผล: รันระบบเพื่อยืนยันว่า agent หยุดรอคำสั่งจริง และข้อมูลที่ส่งมามีพารามิเตอร์ครบถ้วนสำหรับให้คนตัดสินใจ จากนั้นจึงค่อยๆ เพิ่มด่านครอบคลุม action เสี่ยงตัวอื่นๆ
นอกจาก 2 เฟรมเวิร์กนี้ ยังมีตัวเลือกสำเร็จรูปอย่าง LangChain ที่รองรับโหมด human-in-the-loop ในตัว หรือเครื่องมืออย่าง HumanLayer ที่ใช้ decorator @require_approval (ปัจจุบันเว็บหลักของ HumanLayer เน้นผลิตภัณฑ์ชื่อ CodeLayer แต่หลักการ approval gate ยังคงเดิม) สามารถเลือกปรับใช้ตามโครงสร้างโปรเจกต์ที่มีอยู่ได้ทันที
ยิ่งเรามอบหมายให้ AI agent มีสิทธิ์ลงมือทำสิ่งต่างๆ มากเท่าไร ขอบเขตระหว่าง "งานที่ทำได้ทันที" กับ "งานที่ต้องขออนุมัติก่อน" ยิ่งเป็นสถาปัตยกรรมสำคัญที่ต้องออกแบบอย่างตั้งใจตั้งแต่ต้น
ที่มา:
- บทความ Building a 'Human-in-the-Loop' Approval Gate for Autonomous Agents จาก MachineLearningMastery
- เอกสาร Human-in-the-loop จาก OpenAI Agents SDK
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
Vibe Coding สำหรับคนไม่ใช่โปรแกรมเมอร์ ใช้ Claude Code สร้าง landing page, mini app และ prototype จริงโดยไม่ต้องเขียนโค้ด
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
สร้าง AI Automation Pipeline ทุกแบบ ด้วย Agents และ Skills

ปูจากพื้นฐาน prompt, context และ cost ไปจนปั้น Skill สั่ง Agent กับ Sub-agent แล้วต่อทุกอย่างเป็น pipeline อัตโนมัติที่ออกแบบเองได้ ดูฟรี 7 บทก่อนตัดสินใจ


