某些工具操作可能是敏感的,需要在执行前获得人工审批。Deep Agents 通过 LangGraph 的中断功能支持人工介入工作流。您可以使用 interrupt_on 参数配置哪些工具需要审批。

基本配置

interrupt_on 参数接受一个字典,将工具名称映射到中断配置。每个工具可以配置为:
  • True:启用中断,使用默认行为(允许批准、编辑、拒绝)
  • False:禁用此工具的中断
  • {"allowed_decisions": [...]}:自定义配置,指定允许的决策
from langchain.tools import tool
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver

@tool
def delete_file(path: str) -> str:
    """Delete a file from the filesystem."""
    return f"Deleted {path}"

@tool
def read_file(path: str) -> str:
    """Read a file from the filesystem."""
    return f"Contents of {path}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    return f"Sent email to {to}"

# Checkpointer is REQUIRED for human-in-the-loop
checkpointer = MemorySaver()

agent = create_deep_agent(
    model="claude-sonnet-4-6",
    tools=[delete_file, read_file, send_email],
    interrupt_on={
        "delete_file": True,  # Default: approve, edit, reject
        "read_file": False,   # No interrupts needed
        "send_email": {"allowed_decisions": ["approve", "reject"]},  # No editing
    },
    checkpointer=checkpointer  # Required!
)

决策类型

allowed_decisions 列表控制在审阅工具调用时人工可以采取的操作:
  • "approve":使用 Agent 提议的原始参数执行工具
  • "edit":在执行前修改工具参数
  • "reject":完全跳过此工具调用的执行
您可以为每个工具自定义可用的决策:
interrupt_on = {
    # 敏感操作:允许所有选项
    "delete_file": {"allowed_decisions": ["approve", "edit", "reject"]},

    # 中等风险:仅批准或拒绝
    "write_file": {"allowed_decisions": ["approve", "reject"]},

    # 必须批准(不允许拒绝)
    "critical_operation": {"allowed_decisions": ["approve"]},
}

处理中断

当触发中断时,Agent 暂停执行并返回控制权。检查结果中的中断并相应处理。
from langchain_core.utils.uuid import uuid7
from langgraph.types import Command

# 创建带有 thread_id 的配置以实现状态持久化
config = {"configurable": {"thread_id": str(uuid7())}}

# 调用 Agent
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Delete the file temp.txt"}]},
    config=config,
    version="v2",
)

# 检查执行是否被中断
if result.interrupts:
    # 提取中断信息
    interrupt_value = result.interrupts[0].value  
    action_requests = interrupt_value["action_requests"]
    review_configs = interrupt_value["review_configs"]

    # 从工具名称到审阅配置的查找映射
    config_map = {cfg["action_name"]: cfg for cfg in review_configs}

    # 向用户显示待处理的操作
    for action in action_requests:
        review_config = config_map[action["name"]]
        print(f"工具:{action['name']}")
        print(f"参数:{action['args']}")
        print(f"允许的决策:{review_config['allowed_decisions']}")

    # 获取用户决策(每个 action_request 一个,按顺序)
    decisions = [
        {"type": "approve"}  # 用户批准了删除
    ]

    # 使用决策恢复执行
    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,  # 必须使用相同的配置!
        version="v2",
    )

# 处理最终结果
print(result.value["messages"][-1].content)

多个工具调用

当 Agent 调用多个需要审批的工具时,所有中断都会在单个中断中批量处理。您必须按顺序为每个中断提供决策。
config = {"configurable": {"thread_id": str(uuid7())}}

result = agent.invoke(
    {"messages": [{
        "role": "user",
        "content": "Delete temp.txt and send an email to admin@example.com"
    }]},
    config=config,
    version="v2",
)

if result.interrupts:
    interrupt_value = result.interrupts[0].value  
    action_requests = interrupt_value["action_requests"]

    # 两个工具需要审批
    assert len(action_requests) == 2

    # 按与 action_requests 相同的顺序提供决策
    decisions = [
        {"type": "approve"},  # 第一个工具:delete_file
        {"type": "reject"}    # 第二个工具:send_email
    ]

    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,
        version="v2",
    )

编辑工具参数

"edit" 在允许的决策中时,您可以在执行前修改工具参数:
if result.interrupts:
    interrupt_value = result.interrupts[0].value  
    action_request = interrupt_value["action_requests"][0]

    # 来自 Agent 的原始参数
    print(action_request["args"])  # {"to": "everyone@company.com", ...}

    # 用户决定编辑收件人
    decisions = [{
        "type": "edit",
        "edited_action": {
            "name": action_request["name"],  # 必须包含工具名称
            "args": {"to": "team@company.com", "subject": "...", "body": "..."}
        }
    }]

    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config=config,
        version="v2",
    )

子代理中断

使用子代理时,您可以在工具调用上使用中断在工具调用内使用中断

工具调用上的中断

每个子代理可以有自己的 interrupt_on 配置,覆盖主 Agent 的设置:
agent = create_deep_agent(
    tools=[delete_file, read_file],
    interrupt_on={
        "delete_file": True,
        "read_file": False,
    },
    subagents=[{
        "name": "file-manager",
        "description": "Manages file operations",
        "system_prompt": "You are a file management assistant.",
        "tools": [delete_file, read_file],
        "interrupt_on": {
            # 覆盖:此子代理中读取需要审批
            "delete_file": True,
            "read_file": True,  # 与主 Agent 不同!
        }
    }],
    checkpointer=checkpointer
)
当子代理触发中断时,处理方式相同——检查结果上的 interrupts 并使用 Command 恢复。

工具调用内的中断

子代理工具可以直接调用 interrupt() 来暂停执行并等待审批:
from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic
from langchain.messages import HumanMessage
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, interrupt

from deepagents.graph import create_deep_agent
from deepagents.middleware.subagents import CompiledSubAgent


@tool(description="Request human approval before proceeding with an action.")
def request_approval(action_description: str) -> str:
    """使用 interrupt() 原语请求人工审批。"""
    # interrupt() 暂停执行并返回传递给 Command(resume=...) 的值
    approval = interrupt({
        "type": "approval_request",
        "action": action_description,
        "message": f"请批准或拒绝:{action_description}",
    })

    if approval.get("approved"):
        return f"Action '{action_description}' approved. Proceeding..."
    else:
        return f"Action '{action_description}' rejected. Reason: {approval.get('reason', 'No reason provided')}"


def main():
    checkpointer = InMemorySaver()
    model = ChatAnthropic(
        model_name="claude-sonnet-4-6",
        max_tokens=4096,
    )

    compiled_subagent = create_agent(
        model=model,
        tools=[request_approval],
        name="approval-agent",
    )

    parent_agent = create_deep_agent(
        checkpointer=checkpointer,
        subagents=[
            CompiledSubAgent(
                name="approval-agent",
                description="可以请求审批的 Agent",
                runnable=compiled_subagent,
            )
        ],
    )

    thread_id = "test_interrupt_directly"
    config = {"configurable": {"thread_id": thread_id}}

    print("调用 Agent — 子代理将使用 request_approval 工具...")

    result = parent_agent.invoke(
        {
            "messages": [
                HumanMessage(
                    content="使用 task 工具启动 approval-agent 子代理。告诉它使用 request_approval 工具请求对 'deploying to production' 的审批。"
                )
            ]
        },
        config=config,
        version="v2",
    )

    # 检查中断
    if result.interrupts:
        interrupt_value = result.interrupts[0].value  
        print(f"\n收到中断!")
        print(f"  类型:{interrupt_value.get('type')}")
        print(f"  操作:{interrupt_value.get('action')}")
        print(f"  消息:{interrupt_value.get('message')}")

        print("\n使用 Command(resume={'approved': True}) 恢复...")
        result2 = parent_agent.invoke(
            Command(resume={"approved": True}),
            config=config,
            version="v2",
        )

        if not result2.interrupts:
            print("\n执行完成!")
            # 查找工具响应
            tool_msgs = [m for m in result2.value.get("messages", []) if m.type == "tool"]
            if tool_msgs:
                print(f"  工具结果:{tool_msgs[-1].content}")
        else:
            print("\n发生了另一个中断")