Agent tự động hoàn toàn nghe hấp dẫn, nhưng với hành động rủi ro — gửi email cho khách, xóa dữ liệu, thanh toán — bạn cần con người duyệt trước khi thực thi. LangGraph hỗ trợ điều này nhờ interrupt: tạm dừng graph, chờ input người, rồi tiếp tục đúng chỗ đã dừng. Cơ chế này dựa trực tiếp trên checkpoint ở bài 5, nên checkpointer là điều kiện bắt buộc.
Hai cách tạm dừng
- Static interrupt: khai báo dừng trước/sau một node ngay lúc compile.
- Dynamic interrupt: gọi hàm
interrupt()ngay trong node khi cần, linh hoạt theo dữ liệu thực tế.
Static interrupt
app = builder.compile(
checkpointer=checkpointer,
interrupt_before=["send_email"] # dừng ngay trước node này
)
config = {"configurable": {"thread_id": "t-1"}}
app.invoke(inputs, config) # chạy tới trước send_email rồi dừng
# ... người xem xét state, quyết định ...
app.invoke(None, config) # None = tiếp tục từ chỗ dừng
Khi graph dừng, state đã được checkpoint. Bạn kiểm tra bằng app.get_state(config) để xem agent "định làm gì", rồi resume bằng cách invoke với None.
Dynamic interrupt
from langgraph.types import interrupt, Command
def review_node(state) -> dict:
decision = interrupt({"draft": state["draft"]}) # ném ra chờ người
return {"approved": decision == "ok"}
# resume với dữ liệu người nhập
app.invoke(Command(resume="ok"), config)
interrupt(payload) gửi payload ra ngoài cho người xem, và khi bạn resume bằng Command(resume=value), chính value đó trở thành giá trị trả về của interrupt(). Nhờ vậy người dùng có thể duyệt, sửa, hoặc từ chối một cách linh hoạt.
Ba mẫu human-in-the-loop hay dùng
- Approve/Reject: dừng trước hành động rủi ro, người bấm duyệt hay hủy.
- Edit state: người sửa trực tiếp nội dung agent tạo trước khi tiếp tục (dùng
app.update_state). - Cung cấp thông tin thiếu: agent hỏi, người trả lời, agent dùng câu trả lời đi tiếp.
# Mẫu edit state: người sửa bản nháp rồi mới chạy tiếp
app.update_state(config, {"draft": "Bản nháp đã người chỉnh"})
app.invoke(None, config)
Các bước triển khai một bước duyệt
- Xác định node rủi ro cần chặn (gửi/xóa/thanh toán).
- Bật checkpointer — bắt buộc, vì interrupt cần lưu state để chờ.
- Chọn static (
interrupt_before) hay dynamic (interrupt()) tùy độ linh hoạt cần thiết. - Xây UI hoặc kênh để người xem state đang chờ và ra quyết định.
- Resume: invoke với
None(static) hoặcCommand(resume=...)(dynamic). - Ghi log ai đã duyệt, lúc nào — phục vụ audit và truy vết.
Checklist an toàn HITL
- [ ] Mọi hành động không thể hoàn tác đều có bước duyệt trước đó.
- [ ] Checkpointer bền được bật (không dùng Memory nếu phải chờ lâu).
- [ ]
thread_idgiữ nguyên giữa lúc dừng và lúc resume. - [ ] Người duyệt thấy đủ ngữ cảnh để quyết định.
- [ ] Có đường "từ chối" rõ ràng, không chỉ có "đồng ý".
Sai lầm thường gặp
- Bật interrupt mà quên checkpointer: không có nơi lưu state chờ, cơ chế không hoạt động.
- Resume với
thread_idkhác lúc dừng: agent không tìm thấy phiên đang chờ. - Chờ người bằng
MemorySaver: nếu process restart trong lúc chờ, mất toàn bộ tiến trình. Dùng store bền. - Chỉ thiết kế nhánh "đồng ý": khi người từ chối, agent không biết làm gì. Luôn xử lý cả trường hợp reject.
- Đặt bước duyệt sai chỗ — sau khi hành động rủi ro đã chạy — thì hoàn toàn vô nghĩa. Phải chặn trước hành động.