interrupt_on 参数来配置哪些工具需要审批。当设置了 interrupt_on 时,HumanInTheLoopMiddleware 会被添加到默认中间件栈中。如果在工具返回结果之前运行被取消或中断,同一栈中的 PatchToolCallsMiddleware 会自动修复消息历史。
基本配置
interrupt_on 参数接受一个字典,将工具名称映射到中断配置。每个工具可以配置为:
True:启用中断并使用默认行为(允许批准、编辑、拒绝、回复)False:禁用该工具的中断InterruptOnConfig:自定义配置。设置allowed_decisions以控制审查选项。
import { tool } from "langchain";
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { z } from "zod";
const removeFile = tool(
async ({ path }: { path: string }) => {
return `已删除 ${path}`;
},
{
name: "remove_file",
description: "从文件系统中删除一个文件。",
schema: z.object({
path: z.string(),
}),
},
);
const fetchFile = tool(
async ({ path }: { path: string }) => {
return `${path} 的内容`;
},
{
name: "fetch_file",
description: "从文件系统中读取一个文件。",
schema: z.object({
path: z.string(),
}),
},
);
const notifyEmail = tool(
async ({
to,
subject,
body,
}: {
to: string;
subject: string;
body: string;
}) => {
return `已向 ${to} 发送邮件`;
},
{
name: "notify_email",
description: "发送一封电子邮件。",
schema: z.object({
to: z.string(),
subject: z.string(),
body: z.string(),
}),
},
);
// 人机协作流程需要检查点
const checkpointer = new MemorySaver();
const agent = createDeepAgent({
model: "google_genai:gemini-3.6-flash",
tools: [removeFile, fetchFile, notifyEmail],
interruptOn: {
remove_file: true, // 默认操作:批准、编辑、拒绝、响应
fetch_file: false, // 不需要中断
notify_email: { allowedDecisions: ["approve", "reject"] }, // 不允许编辑
},
checkpointer, // 必需!
});
决策类型
allowed_decisions 列表控制人工审查工具调用时可以采取的操作:
| 决策类型 | 描述 | 示例用例 |
|---|---|---|
✅ approve | 使用代理提出的原始参数执行工具。 | 完全按照所写内容发送电子邮件草稿 |
✏️ edit | 在执行前修改工具参数。 | 发送前更改收件人 |
❌ reject | 完全跳过执行此工具调用,并向代理返回拒绝反馈。 | 拒绝文件删除并说明原因 |
💬 respond | 将人类的消息直接作为合成工具结果返回,跳过执行,适用于“询问用户”类工具。 | 直接回复 "ask_user" 提示 |
reject。仅当人工扮演工具角色时使用 respond,例如回答 ask_user 提示。不要使用 respond 来拒绝具有副作用的工具,因为模型可能会将其消息视为成功的工具结果。
编辑工具参数时,请保守地进行更改。对原始参数进行重大修改可能会导致模型重新评估其方法,并可能多次执行工具或采取意外操作。
const interruptOn = {
// 敏感操作:允许所有选项
delete_file: { allowedDecisions: ["approve", "edit", "reject"] },
// 中等风险:仅允许批准或拒绝
write_file: { allowedDecisions: ["approve", "reject"] },
// 必须批准(不允许拒绝)
critical_operation: { allowedDecisions: ["approve"] },
};
处理中断
当触发中断时,代理会暂停执行并返回控制权。检查结果中的中断并相应处理。如果用户拒绝某个操作,请包含一个明确的message,告知代理该工具未执行以及下一步该怎么做。
import { v7 as uuid7 } from "uuid";
import { Command } from "@langchain/langgraph";
// 创建带有 thread_id 的配置以进行状态持久化
const config = { configurable: { thread_id: uuid7() } };
// 调用代理
let result = await agent.invoke({
messages: [{ role: "user", content: "删除文件 temp.txt" }],
}, config);
// 检查执行是否被中断
if (result.__interrupt__) {
// 提取中断信息
const interrupts = result.__interrupt__[0].value;
const actionRequests = interrupts.actionRequests;
const reviewConfigs = interrupts.reviewConfigs;
// 创建一个从工具名称到审查配置的查找映射
const configMap = Object.fromEntries(
reviewConfigs.map((cfg) => [cfg.actionName, cfg])
);
// 向用户显示待处理的操作
for (const action of actionRequests) {
const reviewConfig = configMap[action.name];
console.log(`工具: ${action.name}`);
console.log(`参数: ${JSON.stringify(action.args)}`);
console.log(`允许的决策: ${reviewConfig.allowedDecisions}`);
}
// 获取用户决策(每个 actionRequest 一个,按顺序)
const decisions = [
{
type: "reject",
message: "用户拒绝删除 temp.txt。请勿重试删除操作。",
}
];
// 使用决策恢复执行
result = await agent.invoke(
new Command({ resume: { decisions } }),
config // 必须使用相同的配置!
);
}
// 处理最终结果
console.log(result.messages[result.messages.length - 1].content);
多个工具调用
当代理调用需要批准的多个工具时,所有中断都会合并到一个中断中。您必须按顺序为每个工具提供决策。const config = { configurable: { thread_id: uuid7() } };
let result = await agent.invoke({
messages: [{
role: "user",
content: "删除 temp.txt 并向 admin@example.com 发送一封电子邮件"
}]
}, config);
if (result.__interrupt__) {
const interrupts = result.__interrupt__[0].value;
const actionRequests = interrupts.actionRequests;
// 两个工具需要批准
console.assert(actionRequests.length === 2);
// 按照与 actionRequests 相同的顺序提供决策
const decisions = [
{ type: "approve" }, // 第一个工具: delete_file
{
type: "reject",
message: "用户拒绝此操作。请勿重试此工具调用。",
} // 第二个工具: send_email
];
result = await agent.invoke(
new Command({ resume: { decisions } }),
config
);
}
拒绝消息
当审查者返回reject 决策时,Deep Agents 会跳过工具调用并将拒绝反馈发送回代理。如果您省略 message,默认反馈会告知模型该工具未执行,除非用户要求,否则不要重试相同的工具调用。
对于敏感或具有副作用的工具,请在决策中传递特定领域的 message。明确说明代理是应该放弃该操作、提出后续问题还是尝试更安全的替代方案。
const decisions = [
{
type: "reject",
message: "用户拒绝删除此文件。请勿重试删除。改为询问要归档哪个文件。",
},
];
编辑工具参数
当"edit" 在允许的决策中时,您可以在执行前修改工具参数:
if (result.__interrupt__) {
const interrupts = result.__interrupt__[0].value;
const actionRequest = interrupts.actionRequests[0];
// 代理提供的原始参数
console.log(actionRequest.args); // { to: "everyone@company.com", ... }
// 用户决定编辑收件人
const decisions = [{
type: "edit",
editedAction: {
name: actionRequest.name, // 必须包含工具名称
args: { to: "team@company.com", subject: "...", body: "..." }
}
}];
result = await agent.invoke(
new Command({ resume: { decisions } }),
config
);
}
子代理中断
使用子代理时,您可以在工具调用时和工具调用内部使用中断。工具调用时的中断
每个子代理可以拥有自己的interrupt_on 配置,以覆盖主代理的设置:
const agent = createDeepAgent({
tools: [deleteFile, readFile],
interruptOn: {
delete_file: true,
read_file: false,
},
subagents: [{
name: "file-manager",
description: "管理文件操作",
systemPrompt: "你是一个文件管理助手。",
tools: [deleteFile, readFile],
interruptOn: {
// 覆盖:在此子代理中,读取也需要批准
delete_file: true,
read_file: true, // 与主代理不同!
}
}],
checkpointer
});
interrupts 并使用 Command 恢复。
工具调用内部的中断
子代理工具可以直接调用interrupt() 来暂停执行并等待批准:
import { createAgent, tool } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { MemorySaver, Command, interrupt } from "@langchain/langgraph";
import { createDeepAgent } from "deepagents";
import { z } from "zod";
const requestApproval = tool(
async ({ actionDescription }: { actionDescription: string }) => {
const approval = interrupt({
type: "approval_request",
action: actionDescription,
message: `请批准或拒绝:${actionDescription}`,
}) as { approved?: boolean; reason?: string };
if (approval.approved) {
return `操作 '${actionDescription}' 已被批准。继续执行...`;
} else {
return `操作 '${actionDescription}' 已被拒绝。原因:${
approval.reason || "未提供原因"
}`;
}
},
{
name: "request_approval",
description: "在继续执行操作前请求人工批准。",
schema: z.object({
actionDescription: z
.string()
.describe("需要批准的操作"),
}),
}
);
async function main() {
const checkpointer = new MemorySaver();
const model = new ChatOpenAI({
model: "gpt-5.4-mini",
maxTokens: 4096,
});
const compiledSubagent = createAgent({
model: model,
tools: [requestApproval],
name: "approval-agent",
});
const parentAgent = await createDeepAgent({
checkpointer: checkpointer,
subagents: [
{
name: "approval-agent",
description: "一个可以请求批准的代理",
runnable: compiledSubagent as any,
},
],
});
const threadId = "test_interrupt_directly";
const config = { configurable: { thread_id: threadId } };
console.log("调用代理 - 子代理将使用 request_approval 工具...");
let result = await parentAgent.invoke(
{
messages: [
new HumanMessage({
content:
"使用任务工具启动 approval-agent 子代理。" +
"告诉它使用 request_approval 工具为'部署到生产环境'请求批准。",
}),
],
},
config
);
if (result.__interrupt__) {
const interruptValue = result.__interrupt__[0].value as {
type?: string;
action?: string;
message?: string;
};
console.log("\n收到中断!");
console.log(` 类型: ${interruptValue.type}`);
console.log(` 操作: ${interruptValue.action}`);
console.log(` 消息: ${interruptValue.message}`);
console.log("\n使用 Command(resume={'approved': true}) 恢复...");
const result2 = await parentAgent.invoke(
new Command({ resume: { approved: true } }),
config
);
if (!result2.__interrupt__) {
console.log("\n执行完成!");
// 查找工具响应
const toolMsgs = result2.messages?.filter((m) => m.type === "tool") || [];
if (toolMsgs.length > 0) {
const lastToolMsg = toolMsgs[toolMsgs.length - 1];
console.log(` 工具结果: ${lastToolMsg.content}`);
}
} else {
console.log("\n发生了另一个中断");
}
} else {
console.log(
"\n 没有中断 - 模型可能没有调用 request_approval"
);
}
}
main().catch(console.error);
Invoking agent - sub-agent will use request_approval tool...
Interrupt received!
Type: approval_request
Action: deploying to production
Message: Please approve or reject: deploying to production
Resuming with Command(resume={'approved': true})...
Execution completed!
Tool result: Approval for "deploying to production" has been granted. You can proceed with the deployment.
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

