主题
人工介入总览
智能体如何在工具调用前暂停以等待人工审查,以及逐调用与批量两种中断方式的区别。
人工介入(HITL)在工具调用执行前暂停智能体,交由人工审查。审核者可以批准该调用、编辑其参数、带反馈地拒绝,或代替工具作答。适用于难以撤销或需要签核的操作,例如删除记录、发送邮件或写入文件。
使用 HumanInTheLoopMiddleware,可在 LangChain 中加入审查;也可在 Deep Agents 中用 interrupt_on 参数加入审查。中间件以两种模式之一暂停:
- 逐调用:每个需要审查的工具调用各暂停一次。
- 批量:每个模型轮次暂停一次,覆盖该轮次中的所有工具调用。批量模式是当前默认值。
NOTE
逐调用模式要求 langchain>=1.4.6。Deep Agents 使用批量模式。
从暂停到恢复:一次批准的完整旅程
无论哪种模式,每次批准都遵循相同的步骤:
- 模型提出一个或多个工具调用。
- 中间件将每个调用与你的
interrupt_on策略比对。需要审查的调用会以 interrupt 暂停智能体。 - 检查点保存器保存智能体状态,因此运行可以停下、稍后再恢复,甚至换到另一个进程也一样。
- 你的应用把待处理的调用展示给审核者,并为每个调用收集一个决策。
- 你用
Command(resume=...)恢复智能体。中间件依次应用每个决策,智能体继续执行。
决策共有四种类型:
| 决策类型 | 描述 | 示例使用场景 |
|---|---|---|
✅ approve | 按智能体提出的原始参数执行该工具。 | 按所写内容原样发送邮件草稿 |
✏️ edit | 在执行前修改工具参数。 | 在发送邮件前更改收件人 |
❌ reject | 完全跳过执行此工具调用,并向智能体返回拒绝反馈。 | 拒绝删除文件并说明原因 |
💬 respond | 将人工的消息直接作为合成的工具结果返回,跳过执行,适用于 "ask user" 风格的工具。 | 用直接回复来回应 "ask_user" 提示 |
本页的示例使用一个带有三个工具的智能体。write_file 允许所有决策类型。execute_sql 只允许 approve 与 reject。read_data 无需审查即可执行。在同一个轮次里,模型调用了这三个工具。
逐个审查工具调用
在逐调用模式下,每个需要审查的工具调用在开始时都会各自暂停:
sequenceDiagram
participant App as Your app
participant Agent
participant Tools
App->>Agent: invoke(user message)
Agent->>Tools: write_file, execute_sql, read_data
Tools-->>Agent: read_data result (not gated, runs right away)
Tools-->>App: interrupt for write_file
Tools-->>App: interrupt for execute_sql
App->>Agent: resume {write_file ID: approve, execute_sql ID: reject}
Note over Agent: Each answer is checked against its response_schema before it is saved
Agent->>Tools: run write_file
Agent-->>App: final result
每个 interrupt 值描述单个调用,并带有 type: "tool_approval"。execute_sql 的 interrupt 如下所示:
python
{
"type": "tool_approval",
"tool_call_id": "call_sql",
"name": "execute_sql",
"args": {"query": "DELETE FROM orders WHERE created_at < '2025-01-01'"},
"description": "Tool execution requires approval\n\nTool: execute_sql\nArgs: {'query': \"DELETE FROM orders WHERE created_at < '2025-01-01'\"}",
}每个 interrupt 还携带一个 response_schema,即该调用可接受答案的 JSON schema。对 execute_sql,schema 只允许 approve 与 reject。对 write_file,还允许 respond 与 edit。edit 分支包含 write_file 自身的参数 schema。客户端可以根据 schema 渲染表单,而不必硬编码决策类型。LangChain 指南展示了每种决策类型对应的 schema。
你逐个回答 interrupt,每个 interrupt 用一个决策作答,并以该 interrupt 的 ID 为键:
python
Command(resume={
write_file_interrupt.id: {"type": "approve"},
execute_sql_interrupt.id: {"type": "reject", "message": "Archive the rows before deleting them."},
})LangGraph 会在保存前拒绝无效答案,因此审核者可以更正后重新回答。未回答的 interrupt 保持待处理,审核者可以逐个作答。
一次审查整个轮次
在批量模式下,中间件在模型响应之后、任何工具执行之前暂停一次。read_data 虽然没有被拦截,也要等待这次审查。一个 interrupt 在 action_requests 中列出所有被拦截的调用,并在单独的 review_configs 列表中列出每个调用允许的决策:
python
{
"action_requests": [
{"name": "write_file", "args": {"path": "orders_archive.csv", "content": "id,total\n1,20\n"}, "description": "..."},
{"name": "execute_sql", "args": {"query": "DELETE FROM orders WHERE created_at < '2025-01-01'"}, "description": "..."},
],
"review_configs": [
{"action_name": "write_file", "allowed_decisions": ["approve", "edit", "reject", "respond"]},
{"action_name": "execute_sql", "allowed_decisions": ["approve", "reject"]},
],
}你用一个决策列表回答,顺序与 action_requests 一致:
python
Command(resume={"decisions": [
{"type": "approve"},
{"type": "reject", "message": "Archive the rows before deleting them."},
]})该 interrupt 没有 response_schema。中间件只在应用每个决策时才校验它,此时 LangGraph 已保存了答案。
对比批量模式的问题
批量模式按位置将答案与调用配对,并且每个答案先保存、后校验。下表每行是示例智能体在批量模式下会遇到的一个问题,以及逐调用模式如何避免它:
| 在批量模式下 | 会发生什么 | 在逐调用模式下 |
|---|---|---|
| 决策按位置与调用配对 | 审核者本想批准 write_file、拒绝 execute_sql,却发出了 [reject, approve]。DELETE 被执行了。 | 每个决策以其调用的 interrupt ID 为键。 |
允许的决策放在 review_configs 里 | 客户端按位置配对两个列表,并为每种决策类型硬编码一个按钮。 | 每个 interrupt 的 response_schema 列出其决策与可编辑参数。 |
| 答案先保存后校验 | 两个调用只收到一个决策时抛出 ValueError: Number of human decisions (1) does not match number of hanging tool calls (2). 更正后的答案仍以同样的错误失败,线程就此卡住。 | 答案先校验。修正后重新回答即可。 |
| 编辑后的参数未经校验 | 对 write_file 的编辑缺少 content 时工具仍会执行,模型为一次自己从未发起的调用收到错误。 | 该编辑被拒绝,报 edit.edited_action.args.content Field required。 |
选择中断模式
| Per-call | Batched (default) | |
|---|---|---|
| When it pauses | As each gated tool call starts | After the model responds, before any tool runs |
| Interrupts per turn | One per gated tool call | One for the whole turn |
| Ungated calls in the turn | Run right away | Wait for the review |
| Answer | One decision per interrupt, keyed by interrupt ID | A list of decisions, matched by position |
| Best for | New agents, one approval card or message per call, forms built from the schema | Approval UIs that already read action_requests |
新的智能体请使用逐调用模式。LangChain 计划在下一个大版本中将其设为默认。
如果你的审批 UI 读取批量模式格式(例如基于 useStream 审批模式搭建的),请暂时保留批量模式。
逐调用模式还会改变中间件排序与多次回答恢复的行为。切换前请先阅读为逐调用审查排列中间件顺序与按 ID 逐个恢复调用。
另请参阅
- LangChain 中的人工介入:配置中间件并处理各类决策。
- Deep Agents 中的人工介入:为深度智能体及其子智能体设置审批。
- 定义中断响应 schema:LangGraph 如何校验恢复值。