人机协作
之前Agent 调用工具时都是自动执行的,但在企业生产中有很多高风险动作,比如:删除数据库表;删除文件;发送邮件;提交订单。模型可能理解错用户意图,也可能选错参数。一旦工具真的执行,可能造成数据丢失或业务事故
所以 DeepAgents 提供了人机协作能力,也就是常说的 HITL:Human In The Loop
Agent 先正常规划任务;一旦遇到被 interrupt_on 标记的高风险工具,就暂停在执行前;人工审核后,可以选择放行、修改参数后放行,或者拒绝执行;最后再从暂停的位置继续往下跑

名词解释:
| 名称 | 可以先这样理解 |
|---|---|
interrupt_on |
哪些工具需要在执行前停下来,交给人工审批 |
checkpointer |
保存“Agent 暂停在哪里”的短期状态 |
thread_id |
找回同一次执行线程的 ID,恢复时必须保持一致 |
__interrupt__ |
第一次执行后返回的中断信息 |
Command(resume=...) |
第二次恢复执行时传入的恢复指令 |
动作按工具分类:
| 类型 | 含义 | 是否通常需要审批 |
|---|---|---|
Create |
创建数据或资源 | 视情况而定 |
Read |
查询、读取数据 | 通常不需要 |
Update |
修改数据或配置 | 通常需要 |
Delete |
删除数据或资源 | 强烈建议需要 |
@tool
def delete_database(table_name: str):
"""
删除数据库表
这是高风险动作,所以会在 interrupt_on 中配置为执行前中断
示例只返回模拟结果,不会真的删除数据库表
"""
print(f"调用 delete_database 工具,准备删除 {table_name} 表")
return f"已删除表:{table_name}"
@tool
def select_database(table_name: str):
"""
查询数据库表数据
查询动作属于低风险读操作,本示例不会对它做人工审批
"""
print(f"调用 select_database 工具,查询 {table_name} 表数据")
return f"已查询表 {table_name} 的数据"
配置人机的三个关键点
- 用
checkpointer保存暂停状态 - 用稳定的
thread_id找回同一次执行 - 用
interrupt_on标记哪些工具需要审批
用checkpointer保存暂停位置
人机协作必须配合检查点使用。因为 Agent 第一次执行到危险动作时会暂停,后面还要继续恢复执行。中间状态必须被保存下来
# 人机协作必须配置 checkpointer
# 第一次执行命中中断点时,Agent 会把暂停位置保存到检查点中
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
# InMemorySaver 适合教学和本地测试。它会把中断状态保存在当前 Python 进程内存中,程序一退出状态就会丢失
# 生产环境中通常会换成 Redis、数据库等持久化检查点,这样中断恢复才能跨进程、跨服务继续工作
用thread_id 找回同一次执行.
# 恢复执行时必须使用相同 thread_id
# 可以把 thread_id 理解成一次任务的唯一执行线程 ID
thread_config = {
"configurable": {
"thread_id": "hitl-approval-demo",
}
}
# 后面第一次执行和第二次恢复执行,都会使用这个 thread_config
配置需要中断的工具
# 创建 Agent 时,通过 interrupt_on 指定哪些工具需要审批
main_agent = create_deep_agent(
model=llm,
tools=[delete_database, delete_file, select_database],
checkpointer=checkpointer,
system_prompt="""
你是一个负责执行数据库和文件操作的智能助手
请根据用户需求调用合适的工具,并使用中文回复执行结果
""",
# interrupt_on 用工具名配置哪些动作需要人工审批
# True 表示使用默认审批选项:approve、edit、reject
# False 表示该工具不需要中断,可以直接执行
interrupt_on={
"delete_database": True,
"delete_file": True,
"select_database": False,
},
)
从中断到恢复
如果模型决定调用 delete_database 或 delete_file,由于这两个工具配置了中断,第一次调用不会真的删除,而是返回中断信息。 中断信息通常在 __interrupt__ 字段中
# 命中人机协作中断时,结果中会出现 __interrupt__
# __interrupt__ 是一个列表,里面保存 Interrupt 对象
interrupts = result_1.get("__interrupt__", [])
if interrupts:
# action_requests 里会包含模型准备执行的动作,例如工具名和参数。
action_requests = interrupts[0].value["action_requests"]
print(
"本次需要审核的工具数量:"
f"{len(action_requests)},具体拦截的工具:"
f"{[action['name'] for action in action_requests]}"
)
可以把它理解成 Agent 在问人:我准备调用 delete_database(table_name="user"),是否允许?
实际的interrupts结构:
# 每个 Interrupt 的 value 可以理解成这样的结构:
{
# action_requests:模型准备执行、但还没真正执行的高风险工具调用
"action_requests": [
{"name": "delete_database", "args": {"table_name": "user"}},
{"name": "delete_file", "args": {"file_name": "zhaoweifeng.txt"}},
],
# review_configs:每个被拦截工具允许的人工决策类型
"review_configs": [
{"action_name": "delete_database", "allowed_decisions": ["approve", "edit", "reject"]},
{"action_name": "delete_file", "allowed_decisions": ["approve", "edit", "reject"]},
],
}
用command恢复执行
# 人工确认后,需要用 Command(resume=...) 恢复执行
from langgraph.types import Command
decisions = []
for action in action_requests:
action_name = action["name"]
# decisions 的顺序要和 action_requests 的顺序保持一致
# reject 表示拒绝执行该工具;approve 表示允许继续执行该工具
if action_name == "delete_database":
decisions.append({"type": "reject"})
elif action_name == "delete_file":
decisions.append({"type": "approve"})
# 第二次 invoke 不再传用户原始问题,而是传 Command(resume=...)
# config 必须继续使用第一次相同的 thread_id,Agent 才能找到之前暂停的位置
result_2 = main_agent.invoke(
# 第二次执行不再传用户原始问题,而是传 Command(...)
Command(
resume={
# decisions 的数量和顺序要和 action_requests 对应上
"decisions": decisions,
}
),
# 第二次执行必须使用第一次相同的 config,尤其是相同的 thread_id
config=thread_config,
)
本节示例的主线可以理解为:第一次 invoke -> 收到 __interrupt__ -> 构造 decisions -> 第二次 invoke(Command) -> reject 删除表、approve 删除文件
整体流程回顾:
配置 checkpointer
-> 配置稳定 thread_id
-> 配置 interrupt_on
-> 第一次 invoke
-> 命中高危工具,返回 __interrupt__
-> 人工审批
-> 第二次 invoke(Command(resume=...))
-> 使用相同 thread_id 恢复执行
三种人工决策:
| 决策类型 | 含义 | 适合场景 | 使用 |
|---|---|---|---|
approve |
按模型原计划继续执行工具 | 工具和参数都没问题,人工确认可以执行 | decisions.append({"type": "approve"}) |
reject |
拒绝这次工具调用 | 动作风险太高,或者模型理解错了用户意图 | decisions.append({"type": "reject"}) |
edit |
修改工具名或参数后再执行 | 工具选对了,但参数需要人工修正 | decisions.append( { "type": "edit", "edited_action": { "name": action_name, "args": { "table_name": "archived_user", }, }, } ) |
举例:
decisions = []
for action in action_requests:
action_name = action["name"]
# edit 表示人工不拒绝这个工具调用,但要先修正工具名或参数
# edited_action 中的 name 是最终要执行的工具名,args 是修正后的工具参数
if action_name == "delete_database":
decisions.append(
{
"type": "edit",
"edited_action": {
"name": action_name,
"args": {
"table_name": "archived_user",
},
},
}
)
elif action_name == "delete_file":
decisions.append({"type": "approve"})

浙公网安备 33010602011771号