某些工具操作可能涉及敏感操作,需要在执行前获得人工审批。Deep Agents 通过 LangGraph 的中断功能支持人在环工作流。您可以使用以下方式配置需要审批的工具 interrupt_on 参数。当 interrupt_on 被设置时, HumanInTheLoopMiddleware 被添加到 默认中间件堆栈。如果运行在工具返回结果之前被取消或中断,则 PatchToolCallsMiddleware 同一堆栈中的会自动修复消息历史。
graph LR
Agent[Agent] --> Check{Interrupt?}
Check --> |no| Execute[Execute]
Check --> |yes| Human{Human}
Human --> |approve| Execute
Human --> |edit| Execute
Human --> |reject| ToolMessage[ToolMessage]
Human --> |respond| ToolMessage
Execute --> Agent
ToolMessage --> Agent
classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef decision fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
classDef alert fill:#F8E8E6,stroke:#B27D75,stroke-width:2px,color:#634643
class Agent trigger
class Check,Human decision
class Execute process
class ToolMessage process
基本配置
该 interrupt_on 参数接受一个将工具名称映射到中断配置的字典。每个工具可以配置:
- - **
True**:启用中断,具有默认行为(允许批准、编辑、拒绝、响应) - - **
False**:禁用此工具的中断 - - **
InterruptOnConfig**:自定义配置。设置allowed_decisions来控制审查选项。
决策类型
该 allowed_decisions 列表控制人类在审查工具调用时可以采取的操作:
当人类拒绝提议的操作时使用 reject 当人类拒绝提议的操作时使用 respond 仅当人类作为工具行事时使用,例如回答 ask_user 提示。不要使用 respond 来拒绝有副作用的工具,因为模型可能会将其消息视为成功的工具结果。
您可以为每个工具自定义可用的决策:
const interruptOn = {
// Sensitive operations: allow all options
delete_file: { allowedDecisions: ["approve", "edit", "reject"] },
// Moderate risk: approval or rejection only
write_file: { allowedDecisions: ["approve", "reject"] },
// Must approve (no rejection allowed)
critical_operation: { allowedDecisions: ["approve"] },
};
处理中断
当触发中断时,代理会暂停执行并返回控制权。检查结果中的中断并相应处理。如果用户拒绝某个操作,请包含一条清晰的 message 消息,告知代理该工具未被执行以及下一步该怎么做。
// Create config with thread_id for state persistence
const config = { configurable: { thread_id: uuid7() } };
// Invoke the agent
let result = await agent.invoke({
messages: [{ role: "user", content: "Delete the file temp.txt" }],
}, config);
// Check if execution was interrupted
if (result.__interrupt__) {
// Extract interrupt information
const interrupts = result.__interrupt__[0].value;
const actionRequests = interrupts.actionRequests;
const reviewConfigs = interrupts.reviewConfigs;
// Create a lookup map from tool name to review config
const configMap = Object.fromEntries(
reviewConfigs.map((cfg) => [cfg.actionName, cfg])
);
// Display the pending actions to the user
for (const action of actionRequests) {
const reviewConfig = configMap[action.name];
console.log(`Tool: ${action.name}`);
console.log(`Arguments: ${JSON.stringify(action.args)}`);
console.log(`Allowed decisions: ${reviewConfig.allowedDecisions}`);
}
// Get user decisions (one per actionRequest, in order)
const decisions = [
{
type: "reject",
message: "User rejected deleting temp.txt. Do not retry deletion.",
}
];
// Resume execution with decisions
result = await agent.invoke(
new Command({ resume: { decisions } }),
config // Must use the same config!
);
}
// Process final result
console.log(result.messages[result.messages.length - 1].content);
多次工具调用
当代理调用多个需要审批的工具时,所有中断都会被合并到单个中断中。您必须按顺序为每个中断提供决策。
const config = { configurable: { thread_id: uuid7() } };
let result = await agent.invoke({
messages: [{
role: "user",
content: "Delete temp.txt and send an email to admin@example.com"
}]
}, config);
if (result.__interrupt__) {
const interrupts = result.__interrupt__[0].value;
const actionRequests = interrupts.actionRequests;
// Two tools need approval
console.assert(actionRequests.length === 2);
// Provide decisions in the same order as actionRequests
const decisions = [
{ type: "approve" }, // First tool: delete_file
{
type: "reject",
message: "User rejected this action. Do not retry this tool call.",
} // Second tool: send_email
];
result = await agent.invoke(
new Command({ resume: { decisions } }),
config
);
}
拒绝消息
当审阅者返回 reject 决策时,Deep Agents会跳过工具调用并向代理发送拒绝反馈。如果省略 message,默认反馈会告诉模型该工具未被执行,并且除非用户要求,否则不要重试相同的工具调用。
对于敏感或有副作用的工具,请传递一个特定领域的 message 以及决策。请明确说明代理应该放弃该操作、提出后续问题,还是尝试更安全的替代方案。
const decisions = [
{
type: "reject",
message: "User rejected deleting this file. Do not retry deletion. Ask which file to archive instead.",
},
];
编辑工具参数
当 "edit" 在允许的决策中时,您可以在执行前修改工具参数:
if (result.__interrupt__) {
const interrupts = result.__interrupt__[0].value;
const actionRequest = interrupts.actionRequests[0];
// Original args from the agent
console.log(actionRequest.args); // { to: "everyone@company.com", ... }
// User decides to edit the recipient
const decisions = [{
type: "edit",
editedAction: {
name: actionRequest.name, // Must include the tool 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: "Manages file operations",
systemPrompt: "You are a file management assistant.",
tools: [deleteFile, readFile],
interruptOn: {
// Override: require approval for reads in this subagent
delete_file: true,
read_file: true, // Different from main agent!
}
}],
checkpointer
});
并使用 interrupts 恢复执行 Command.
工具调用内部的中断
子代理工具可以直接调用 interrupt() 来暂停执行并等待审批:
const requestApproval = tool(
async ({ actionDescription }: { actionDescription: string }) => {
const approval = interrupt({
type: "approval_request",
action: actionDescription,
message: `Please approve or reject: ${actionDescription}`,
}) as { approved?: boolean; reason?: string };
if (approval.approved) {
return `Action '${actionDescription}' was APPROVED. Proceeding...`;
} else {
return `Action '${actionDescription}' was REJECTED. Reason: ${
approval.reason || "No reason provided"
}`;
}
},
{
name: "request_approval",
description: "Request human approval before proceeding with an action.",
schema: z.object({
actionDescription: z
.string()
.describe("The action that requires approval"),
}),
}
);
async function main() {
const checkpointer = new MemorySaver();
const model = new ChatOpenAI({
model: "gpt-4o-mini",
maxTokens: 4096,
});
const compiledSubagent = createAgent({
model: model,
tools: [requestApproval],
name: "approval-agent",
});
const parentAgent = await createDeepAgent({
checkpointer: checkpointer,
subagents: [
{
name: "approval-agent",
description: "An agent that can request approvals",
runnable: compiledSubagent as any,
},
],
});
const threadId = "test_interrupt_directly";
const config = { configurable: { thread_id: threadId } };
console.log("Invoking agent - sub-agent will use request_approval tool...");
let result = await parentAgent.invoke(
{
messages: [
new HumanMessage({
content:
"Use the task tool to launch the approval-agent sub-agent. " +
"Tell it to use the request_approval tool to request approval for 'deploying to production'.",
}),
],
},
config
);
if (result.__interrupt__) {
const interruptValue = result.__interrupt__[0].value as {
type?: string;
action?: string;
message?: string;
};
console.log("\nInterrupt received!");
console.log(` Type: ${interruptValue.type}`);
console.log(` Action: ${interruptValue.action}`);
console.log(` Message: ${interruptValue.message}`);
console.log("\nResuming with Command(resume={'approved': true})...");
const result2 = await parentAgent.invoke(
new Command({ resume: { approved: true } }),
config
);
if (!result2.__interrupt__) {
console.log("\nExecution completed!");
// Find the tool response
const toolMsgs = result2.messages?.filter((m) => m.type === "tool") || [];
if (toolMsgs.length > 0) {
const lastToolMsg = toolMsgs[toolMsgs.length - 1];
console.log(` Tool result: ${lastToolMsg.content}`);
}
} else {
console.log("\nAnother interrupt occurred");
}
} else {
console.log(
"\n No interrupt - the model may not have called 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.