返回资源广场

Skills 资源 / 技能包

gating-tool-calls-with-approval

给智能体按工具名装人工审批门、按文件路径装权限规则,让危险操作在执行前停下等人拍板,四种决策送回后从断点续跑。Use when 智能体会写文件删数据或执行命令而需要人工把关、合规要求留下审批记录、要按路径一律硬拦某些目录、或排查恢复时报没有检查点保存器、换了会话标识后中断永远恢复不了、审批通过了文件却没写这类问题时。涵盖审批门与检查点保存器配置、中断载荷读法、四种决策语义、路径级权限三模式、副作用重放与会话标识管理。

SKILL.md 技能文档

能力目标

按工具名精确声明「哪一步要人点头」,其余步骤照常自主执行;中断发生时读懂待审载荷、把四种决策之一送回去让智能体从断点续跑;再按文件路径加一层规则,对某些目录一律硬拦、不打扰人。

前置

  • 已能构造智能体(见 bootstrapping-deepagents-env)。
  • 中断状态快照由检查点保存器承载。本地起步用进程内实现,进程一退出状态全丢;生产换成本地文件或数据库实现,只替换这一个对象、其余代码不动。

实操流程

  1. 建带审批门的智能体,两个参数一起给:

    from deepagents import create_deep_agent
    from langgraph.checkpoint.memory import InMemorySaver
    
    checkpointer = InMemorySaver()
    agent = create_deep_agent(
        model=MODEL,
        interrupt_on={"write_file": True},   # 只拦写文件,其余工具自主
        checkpointer=checkpointer,           # 供恢复时找回暂停状态
    )
    

    字典的键是工具名、值是审批配置。True 是简写,等价于把四种决策类型全部打开;字典里没出现的工具完全不受影响。

  2. 触发一次中断并读懂载荷。中断是正常控制流、不是报错——调用正常返回,只是返回里多了一个中断键:

    config = {"configurable": {"thread_id": "step2-hitl"}}
    result = agent.invoke({"messages": [HumanMessage(task)]}, config=config)
    
    interrupt_obj = result["__interrupt__"][0]    # 是列表,可能有多个待审
    payload = interrupt_obj.value                  # 真实载荷在 .value 里
    action_req = payload["action_requests"][0]
    print(action_req["name"], action_req["args"])
    

    载荷里只有「打算用什么参数调用什么工具」,没有结果字段——工具还没执行。

  3. 把决定送回去,用同一个会话标识再次调用。四种决策各自的语义与写法:

    from langgraph.types import Command
    
    # 原样放行
    agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config=config)
    
    # 拒绝并附理由
    agent.invoke(Command(resume={"decisions": [{"type": "reject", "message": "敏感数据,禁止写入文件系统"}]}), config=config)
    
    # 改参数后放行
    agent.invoke(Command(resume={"decisions": [{"type": "edit", "edited_action": {
        "name": "write_file",
        "args": {"file_path": "/safe_log.txt", "content": "审核通过的安全内容"}
    }}]}), config=config)
    
    # 人代替工具作答
    agent.invoke(Command(resume={"decisions": [{"type": "respond", "message": "该项已由人工完成登记"}]}), config=config)
    

    恢复对象来自 LangGraph 的类型模块,不是装具框架自己导出的。

  4. 要限制可用决策或自定义给审批人看的话术,就把 True 换成审批配置对象:

    from deepagents import InterruptOnConfig
    
    custom_config = InterruptOnConfig(
        allowed_decisions=["approve", "reject", "respond"],
        description="⚠️ Agent 想要写入文件,请审核!",
    )
    agent = create_deep_agent(model=MODEL, interrupt_on={"write_file": custom_config},
                              checkpointer=InMemorySaver())
    
  5. 加一层按文件路径的权限规则。三种模式各有适用面:放行是默认;硬拒在工具内部同步返回权限错误、不经过人、不需要检查点保存器;导流则触发中断走同一套审批流程、因此需要检查点保存器:

    from deepagents import FilesystemPermission
    
    deny_rule = FilesystemPermission(operations=["write"], paths=["/secret/**"], mode="deny")
    agent = create_deep_agent(model=MODEL, permissions=[deny_rule])
    
  6. 换自己的危险工具时只改工具名,整套流程不动:

    agent = create_deep_agent(model=MODEL, interrupt_on={"edit_file": True},
                              checkpointer=InMemorySaver())
    

校验回路

  • 中断成立:返回的键里含中断键,载荷里的工具名与参数正是你要拦的那一步。
  • 放行后文件出现在返回的文件状态里;拒绝后该路径不在文件状态里,且消息里多出一条状态为错误、内容是你填的理由的工具消息,智能体没有崩溃、而是继续推理并向用户解释。
  • 做有无审批门的并排对照当效果铁证:同一任务、同一模型,不配审批门时目标文件被写入,配了审批门并拒绝时该文件不存在。
  • 混合工具任务里,审批列表只含被声明的那个工具;同一轮里并行发出的其他工具直接自动放行、不出现在审批列表里。
  • 改参数放行后,新路径写入成功、原危险路径不存在。

常见陷阱

  • 漏配检查点保存器:中断的触发不依赖它——不配也照样能拿到中断键。它真正的作用是让恢复那一步找回暂停状态。缺了它,恢复时抛「不能在没有检查点保存器的情况下使用恢复命令」的运行时错误。症状随通道而异:按工具名的审批门缺它是显式报错;按路径的导流模式缺它是单向中断,能中断不能恢复、而且不报错,更隐蔽。
  • 恢复时用错会话标识:不报错,而是静默开一个全新的空线程——智能体收到空历史、答非所问,而原来那个真正等待审批的任务永远不会被恢复。这比报错更危险。用回正确的标识时,原中断状态一直挂着、仍能恢复。把会话标识当关键参数严格管理。
  • 把人代答当成工具执行了:它注入的是一条状态为成功的合成工具消息,工具根本没跑。实测智能体会据此回复「已成功写入」,而文件终态其实是不存在——它以为成功了。用这一决策时,要么由人真正完成了那件事,要么在消息里如实说明,否则智能体后续推理会建立在一个不存在的成功之上。
  • 以为中断像协程那样原地冻结:实际是把整张图的状态序列化存好、抛中断退出执行栈;恢复时从对应节点顶部重新执行、已解决的中断值从缓存填回。推论:节点顶部到中断点之间的副作用会被重放一次。实测一次完整流程里那段逻辑被调用三次——中断前一次、恢复重放一次、最终回复时一次,其中真正的重放只有中间那次。生产含义很直接:审批门之前的副作用必须幂等,写库、扣款、发请求要么挪到恢复之后,要么保证可安全重复执行。
  • 混淆拒绝与硬拒:拒绝是审批层的人工决定、发生在运行中、需要检查点保存器;硬拒是路径规则在工具执行前同步拦截、不需要人也不需要检查点保存器。两者都注入状态为错误的工具消息,但触发时机与是否打扰人完全不同。想让人逐次审批用导流或按工具名的审批门,想对某些路径一律硬拦用硬拒。
  • 拿模型会自我审查的路径做演示:让它写系统敏感文件时,模型的安全过滤会让它直接拒绝、根本不产生工具调用,中断永远不触发、断言失败。演示用普通的业务路径。
  • 改参数放行后智能体又重试原意图:这一决策只改了这一次调用的参数,用户的原始请求还在推理历史里。要么在系统提示里补上新规则,要么改用拒绝并说明理由。
  • 不同工具的参数名不同:做改参数决策时要查目标工具的参数结构(例如写文件与编辑文件的内容字段名就不一样),照抄另一个工具的字段名会失败。

适用范围与前置条件

  • 已能构造智能体(见 bootstrapping-deepagents-env)。
  • 中断状态快照由检查点保存器承载。本地起步用进程内实现,进程一退出状态全丢;生产换成本地文件或数据库实现,只替换这一个对象、其余代码不动。

怎么使用

使用步骤

  1. 建带审批门的智能体,两个参数一起给:

    from deepagents import create_deep_agent
    from langgraph.checkpoint.memory import InMemorySaver
    
    checkpointer = InMemorySaver()
    agent = create_deep_agent(
        model=MODEL,
        interrupt_on={"write_file": True},   # 只拦写文件,其余工具自主
        checkpointer=checkpointer,           # 供恢复时找回暂停状态
    )
    

    字典的键是工具名、值是审批配置。True 是简写,等价于把四种决策类型全部打开;字典里没出现的工具完全不受影响。

  2. 触发一次中断并读懂载荷。中断是正常控制流、不是报错——调用正常返回,只是返回里多了一个中断键:

    config = {"configurable": {"thread_id": "step2-hitl"}}
    result = agent.invoke({"messages": [HumanMessage(task)]}, config=config)
    
    interrupt_obj = result["__interrupt__"][0]    # 是列表,可能有多个待审
    payload = interrupt_obj.value                  # 真实载荷在 .value 里
    action_req = payload["action_requests"][0]
    print(action_req["name"], action_req["args"])
    

    载荷里只有「打算用什么参数调用什么工具」,没有结果字段——工具还没执行。

  3. 把决定送回去,用同一个会话标识再次调用。四种决策各自的语义与写法:

    from langgraph.types import Command
    
    # 原样放行
    agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config=config)
    
    # 拒绝并附理由
    agent.invoke(Command(resume={"decisions": [{"type": "reject", "message": "敏感数据,禁止写入文件系统"}]}), config=config)
    
    # 改参数后放行
    agent.invoke(Command(resume={"decisions": [{"type": "edit", "edited_action": {
        "name": "write_file",
        "args": {"file_path": "/safe_log.txt", "content": "审核通过的安全内容"}
    }}]}), config=config)
    
    # 人代替工具作答
    agent.invoke(Command(resume={"decisions": [{"type": "respond", "message": "该项已由人工完成登记"}]}), config=config)
    

    恢复对象来自 LangGraph 的类型模块,不是装具框架自己导出的。

  4. 要限制可用决策或自定义给审批人看的话术,就把 True 换成审批配置对象:

    from deepagents import InterruptOnConfig
    
    custom_config = InterruptOnConfig(
        allowed_decisions=["approve", "reject", "respond"],
        description="⚠️ Agent 想要写入文件,请审核!",
    )
    agent = create_deep_agent(model=MODEL, interrupt_on={"write_file": custom_config},
                              checkpointer=InMemorySaver())
    
  5. 加一层按文件路径的权限规则。三种模式各有适用面:放行是默认;硬拒在工具内部同步返回权限错误、不经过人、不需要检查点保存器;导流则触发中断走同一套审批流程、因此需要检查点保存器:

    from deepagents import FilesystemPermission
    
    deny_rule = FilesystemPermission(operations=["write"], paths=["/secret/**"], mode="deny")
    agent = create_deep_agent(model=MODEL, permissions=[deny_rule])
    
  6. 换自己的危险工具时只改工具名,整套流程不动:

    agent = create_deep_agent(model=MODEL, interrupt_on={"edit_file": True},
                              checkpointer=InMemorySaver())
    

继续探索

全部资源
Skills 资源 / 技能包

bootstrapping-deepagents-env

在一台干净机器上装好 DeepAgents 运行环境、接入一个 OpenAI 兼容大模型凭证,并跑通第一个 create_deep_agent 工具调用闭环。Use when 需要初始化 DeepAgents 开发环境、系统 Python 版本不达标装不上包、不确定装到了哪个版本、接 DeepSeek 之类国产模型报 ImportError 或 404 这类环境层故障时。涵盖解释器版本核对、虚拟环境置备、主包与提供方包安装、版本核验、凭证注入、最小示例验收;不含 Agent 各项能力的用法(见 tracking-task-progress-with-todos 等能力型 skill)。

Skills 资源 / 技能包

inspecting-agent-graph-and-tools

把一个 create_deep_agent 建出来的智能体拆开看:列出执行图节点、列出实际挂载的工具、捕获框架预装的中间件清单、抓取每轮真正发给模型的工具集。Use when 需要确认某项能力是否真的挂上了、排查「我的工具去哪了 / 这些工具哪来的 / 内置工具到底几个」、验证自定义中间件是否进了图、或要在改配置前后做结构对照时。涵盖图节点自省、工具清单反查、中间件清单捕获、编译期与运行期工具集差异;不含具体能力的用法。

Skills 资源 / 技能包

tracking-task-progress-with-todos

让智能体把多步任务拆成结构化待办清单写进状态,并从调用结果里取出清单、渲染成实时进度、兜底检测「勾完清单却没给答案」的失败形态。Use when 需要给长任务做进度面板、想稳定触发 write_todos、发现规划没被触发、或要把 todos 推给前端 UI 与日志时。涵盖稳定触发写法、取清单的两条路径、三态进度渲染、失败形态检测;不含子任务委派(见 delegating-subtasks-to-subagents)。