DeepAgents 人工介入实战|LangGraph 实现 Agent 高危工具人工审批

简介: 本文详解基于 LangChain+LangGraph+DeepAgents 实现 Python 智能体人工介入实战,配置高风险工具中断审批、状态检查点保存与恢复,支持同意 / 拒绝 / 参数编辑,对比 Spring AI Alibaba 方案,附完整可运行源码与生产落地建议。

之前一篇文章里,我使用 Spring AI Alibaba 演示了智能体执行过程中的人工介入能力。那篇文章的核心思路是:当 Agent 准备执行某些高风险动作时,不要让它直接执行,而是先暂停下来,把待执行动作交给人工审批,审批通过后再继续执行。感兴趣的小伙伴可以通过下面链接回顾一下:https://www.lucaju.cn/index.php/archives/165/

这篇文章换一个技术栈,使用 Python 版本来实现同样的能力。

本次示例基于:

  • LangChain:负责模型和工具抽象
  • LangGraph:负责执行状态、检查点和恢复执行
  • DeepAgents:负责创建支持工具调用和中断审批的 Agent
  • 通义千问兼容 OpenAI API:作为底层大模型

一、为什么 Agent 需要人工介入

Agent 最大的价值是可以根据用户目标自主规划并调用工具。

但并不是所有工具都适合完全自动执行。比如:

  • 删除数据库表
  • 删除文件
  • 发起转账
  • 修改线上配置
  • 调用外部系统执行不可逆操作

这些动作一旦执行错误,影响可能非常大。

所以比较合理的模式是:

普通查询类动作可以让 Agent 自动执行,高风险动作必须先进入人工审批。

在本文示例中,我们定义了三个工具:

  • query_table_data:查询表数据,低风险,可以自动执行
  • delete_table:删除数据表,高风险,需要人工审批
  • delete_file:删除文件,高风险,需要人工审批

用户输入的任务是:

先查询product表的数据!再删除user表,最后,删除lucaju.txt文件

Agent 会先分析任务,并尝试依次调用工具。但当执行到删除表、删除文件这类高风险动作时,会被框架中断,等待人工确认。

二、初始化大模型

首先初始化模型:

import os
from langchain.chat_models import init_chat_model

llm = init_chat_model(
    model="kimi-k2.5",
    model_provider="openai",
    api_key=os.getenv("AliQwen_API"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model_kwargs={
   "reasoning_effort": "none"},
)

这里使用的是 OpenAI 兼容模式,所以 model_provider 设置为 openai,同时把 base_url 指向阿里云 DashScope 的兼容接口。

需要提前配置环境变量:

export AliQwen_API="你的 API Key"

model_kwargs={"reasoning_effort": "none"} 是模型调用参数,可以根据实际模型支持情况调整。

三、定义工具

示例中定义了三个工具:

from langchain.tools import tool

@tool
def delete_table(table_name: str) -> str:
    """
    删除指定的表
    """
    return f"删除表{table_name}"


@tool
def delete_file(file_name: str) -> str:
    """
    删除指定的文件
    """
    return f"删除文件{file_name}"


@tool
def query_table_data(table_name: str) -> str:
    """
    查询指定表的数据
    """
    return f"查询表{table_name}的数据"

这里为了演示效果,工具内部只是返回字符串,并没有真的连接数据库或删除文件。

在真实项目中,delete_table 可能会执行 SQL,delete_file 可能会操作对象存储或服务器文件系统。这类工具就非常适合加人工审批。

四、为什么必须配置 Checkpointer

Human-in-the-loop 的核心不是简单地打印一句“是否确认”,而是让 Agent 的执行流程真正暂停下来,并且后续可以从暂停点继续执行。

这就需要保存执行状态。

示例代码中使用了 LangGraph 提供的内存检查点:

from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()

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

这里有两个关键点:

  1. checkpointer 用来保存 Agent 的执行状态。
  2. thread_id 用来标识当前会话。

当 Agent 执行到需要人工介入的节点时,LangGraph 会把当前状态保存下来。等人工审批完成后,再通过同一个 thread_id 找回之前的状态,并继续执行。

如果没有检查点,框架就不知道应该从哪里恢复执行。

五、创建支持人工介入的 DeepAgent

接下来创建 Agent:

from deepagents import create_deep_agent

main_agent = create_deep_agent(
    model=llm,
    name="主智能体",
    system_prompt="回答使用中文,调用对应的工具实现对应的功能!",
    tools=[delete_table, delete_file, query_table_data],
    interrupt_on={
   "delete_table": True, "delete_file": True},
    checkpointer=checkpointer
)

这里最关键的是两个参数:

interrupt_on={
   "delete_table": True, "delete_file": True}

以及:

checkpointer=checkpointer

interrupt_on 用来声明哪些工具调用需要中断。

在这个例子中:

  • 调用 query_table_data 不会中断
  • 调用 delete_table 会中断
  • 调用 delete_file 会中断

也就是说,Agent 可以自动查询数据,但不能自动删除表或文件。

这就是人工介入的核心配置。

六、第一次执行:触发中断

第一次执行 Agent:

result_1 = main_agent.invoke(
    {
   
        "messages": [
            {
   
                "role": "user",
                "content": "先查询product表的数据!再删除user表,最后,删除lucaju.txt文件",
            }
        ]
    },
    config=config,
)

这次调用并不一定会完整执行完所有工具。

如果执行链路中包含需要人工审批的工具,Agent 会暂停,并把中断信息放到返回结果的 __interrupt__ 字段中。

示例代码中这样获取:

interrupt = result_1["__interrupt__"]

interrupt 不为空时,说明当前执行过程中存在需要人工介入的动作。

七、查看待审批动作

__interrupt__ 中会包含本次待审批的工具调用信息,例如:

[
    Interrupt(
        value={
   
            "action_requests": [
                {
   
                    "name": "delete_table",
                    "args": {
   "table_name": "user"},
                    "description": "Tool execution requires approval..."
                },
                {
   
                    "name": "delete_file",
                    "args": {
   "file_name": "zhaoweifeng.txt"},
                    "description": "Tool execution requires approval..."
                }
            ],
            "review_configs": [
                {
   
                    "action_name": "delete_table",
                    "allowed_decisions": ["approve", "edit", "reject"]
                },
                {
   
                    "action_name": "delete_file",
                    "allowed_decisions": ["approve", "edit", "reject"]
                }
            ]
        }
    )
]

这里有两个比较重要的字段。

action_requests 表示 Agent 想要执行哪些工具:

  • 工具名称
  • 工具参数
  • 工具描述

review_configs 表示这些动作允许哪些审批结果:

  • approve:同意执行
  • edit:修改参数后执行
  • reject:拒绝执行

这三个动作基本覆盖了常见的人审场景。

八、人工审批:拒绝高危操作

示例代码中把删除表和删除文件都拒绝掉:

decisions = []

action_requests = interrupt[0].value["action_requests"]
print(f"当前人机交互工具:{action_requests}")

for action_request in action_requests:
    if action_request["name"] == "delete_table":
        decisions.append({
   "type": "reject"})
    elif action_request["name"] == "delete_file":
        decisions.append({
   "type": "reject"})

这里的 decisions 顺序需要和 action_requests 对应。

也就是说,如果 Agent 申请了两个动作:

  1. 删除 user
  2. 删除 zhaoweifeng.txt 文件

那么人工审批结果也应该按顺序给出两个 decision。

在这个示例中,两个高风险动作都被拒绝:

[
    {
   "type": "reject"},
    {
   "type": "reject"}
]

九、第二次执行:恢复 Agent

审批完成后,不需要重新传入用户消息,而是通过 Command(resume=...) 恢复执行:

from langgraph.types import Command

result_2 = main_agent.invoke(
    Command(
        resume={
   
            "decisions": decisions
        }
    ),
    config=config,
)

这里一定要继续传入相同的 config,尤其是相同的 thread_id

因为 Agent 要根据 thread_id 找到之前暂停的执行状态。

恢复执行后,框架会根据人工审批结果继续处理:

  • approve 的工具会继续执行
  • reject 的工具不会执行
  • edit 的工具会使用人工修改后的参数执行

最后输出结果:

print(f"最终结果{result_2['messages'][-1].content}")

十、完整代码

完整示例代码如下:

"""
演示human-in-the-loop模式, 必须使用记忆功能
"""

import os
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

# 初始化大模型
llm = init_chat_model(
    model="kimi-k2.5",
    model_provider="openai",
    api_key=os.getenv("AliQwen_API"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model_kwargs={
   "reasoning_effort": "none"},
)


# 删除表工具
@tool
def delete_table(table_name: str) -> str:
    """
    删除指定的表
    """
    return f"删除表{table_name}"


# 删除文件
@tool
def delete_file(file_name: str) -> str:
    """
    删除指定的文件
    """
    return f"删除文件{file_name}"


# 查询表数据
@tool
def query_table_data(table_name: str) -> str:
    """
    查询指定表的数据
    """
    return f"查询表{table_name}的数据"


# 设置检查点
checkpointer = InMemorySaver()

# 配置检查点
config = {
   
    "configurable": {
   
        "thread_id": "123"
    }
}

# 创建deepagent,同时给高危工具设置人机交互
main_agent = create_deep_agent(
    model=llm,
    name="主智能体",
    system_prompt="回答使用中文,调用对应的工具实现对应的功能!",
    tools=[delete_table, delete_file, query_table_data],
    interrupt_on={
   "delete_table": True, "delete_file": True},
    checkpointer=checkpointer
)

# 预执行,本次不会真正的执行所有工具。
# 如果执行链路中存在人机交互节点,框架会暂停,并返回 __interrupt__。
result_1 = main_agent.invoke(
    {
   
        "messages": [
            {
   
                "role": "user",
                "content": "先查询product表的数据!再删除user表,最后,删除lucaju.txt文件",
            }
        ]
    },
    config=config,
)

# 检查本次执行是否存在人机交互动作
interrupt = result_1["__interrupt__"]

if interrupt:
    print("存在人机交互动作")

    # 定义一个列表,存储所有审批结果
    decisions = []

    # 获取所有待审批动作
    action_requests = interrupt[0].value["action_requests"]
    print(f"当前人机交互工具:{action_requests}")

    for action_request in action_requests:
        if action_request["name"] == "delete_table":
            decisions.append({
   "type": "reject"})
        elif action_request["name"] == "delete_file":
            decisions.append({
   "type": "reject"})

    # 再次执行,不需要传会话内容,只需要传审批意见和 config
    result_2 = main_agent.invoke(
        Command(
            resume={
   
                "decisions": decisions
            }
        ),
        config=config,
    )

    print(f"最终结果{result_2['messages'][-1].content}")

python-hitl.png

十一、如果想修改参数后再执行

除了直接拒绝,我们也可以修改工具参数后再执行。

比如 Agent 原本想删除:

zhaoweifeng.txt

人工审批时可以把文件名改成另一个值:

decisions.append({
   
    "type": "edit",
    "edited_action": {
   
        "name": action_request["name"],
        "args": {
   
            "file_name": "new-file.txt"
        }
    }
})

这时框架不会使用 Agent 原始生成的参数,而是使用人工编辑后的参数继续执行工具。

这在实际业务中非常有用。

例如:

  • Agent 选择的表名不准确,人工改成正确表名
  • Agent 生成的文件路径不安全,人工改成允许路径
  • Agent 生成的金额过大,人工改成合理金额
  • Agent 生成的收件人错误,人工改成正确收件人

相比简单的同意或拒绝,edit 让人工介入更灵活。

十二、执行流程总结

整体流程可以概括为:

用户输入任务
    ↓
Agent 分析任务并规划工具调用
    ↓
普通工具自动执行
    ↓
遇到 interrupt_on 配置的高风险工具
    ↓
LangGraph 保存状态并中断执行
    ↓
人工读取 __interrupt__ 中的 action_requests
    ↓
人工给出 approve / edit / reject
    ↓
使用 Command(resume=...) 恢复执行
    ↓
Agent 根据审批结果继续完成任务

这套机制的关键点是:

  • interrupt_on:定义哪些工具需要人工审批
  • checkpointer:保存执行状态
  • thread_id:标识同一次会话
  • __interrupt__:获取待审批动作
  • Command(resume=...):把审批结果送回 Agent 并恢复执行

十三、和 Spring AI Alibaba 版本的对比

从思想上看,Python 版本和 Spring AI Alibaba 版本是一致的:

Agent 不应该无限制地自动执行所有动作,高风险动作需要进入人工审批流程。

但实现方式上有所不同。

Spring AI Alibaba 更偏向 Java 生态,适合和 Spring Boot、企业系统、审批流、权限体系结合。

而 LangChain + LangGraph + DeepAgents 的 Python 方案更偏向实验、原型验证和 Agent 工作流编排。尤其是 LangGraph 的 checkpoint 和 resume 机制,让“暂停后恢复”这件事变得非常自然。

如果项目本身是 Java 技术栈,可以优先考虑 Spring AI Alibaba。

如果项目本身是 Python 技术栈,或者正在做 Agent 编排、工具调用、多步骤任务规划,那么 DeepAgents 这套方式会比较顺手。

十四、真实项目中的建议

在真实项目中使用人工介入时,建议注意以下几点。

第一,高风险工具要显式配置。

不要只依赖 prompt 告诉模型“删除前要确认”。更可靠的方式是像本文一样,在框架层面对工具进行拦截。

第二,审批信息要完整展示。

人工审批时至少要看到:

  • 工具名称
  • 工具参数
  • Agent 为什么要执行这个工具
  • 当前用户是谁
  • 当前会话上下文

第三,审批结果要落库。

生产环境中,审批记录应该持久化,包括:

  • 谁审批的
  • 什么时间审批的
  • 原始参数是什么
  • 修改后的参数是什么
  • 最终是通过、拒绝还是编辑后通过

第四,检查点不要只用内存。

本文为了演示使用的是:

InMemorySaver()

生产环境更建议使用数据库、Redis 或其他持久化存储。否则服务重启后,暂停中的 Agent 状态会丢失。

第五,审批权限要和业务系统打通。

不是所有人都应该可以批准所有工具调用。

比如:

  • 普通用户只能审批自己的任务
  • 管理员可以审批团队任务
  • 涉及资金、删除、发布的动作需要更高权限

人工介入不是简单的弹窗确认,而应该是完整的安全控制链路。

关于DeepAgents的练习代码已经上传到了我的GitHub,欢迎fork & star
https://github.com/Jucunqi/deepagents-practice.git

目录
相关文章
|
22小时前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23254 1
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
2天前
|
人工智能 API 开发工具
Claude Code国内安装:2026最新保姆教程(附cc-switch配置)
Claude Code是我目前最推荐的AI编程工具,没有之一。 它可能不是最简单的,但绝对是上限最高的。一旦跑通安装、接上模型、定好规范,你会发现很多原本需要几小时的工作,现在几分钟就能搞定。 这套方案的核心优势就三个字:可控性。你不用依赖任何不稳定服务,所有组件都在自己手里。模型效果不好?换一个。框架更新了?自己决定升不升。 这才是AI时代开发者该有的姿势——不是被动等喂饭,而是主动搭建自己的生产力基础设施。 希望这篇保姆教程,能帮你顺利上车。做出你自己的作品。
Claude Code国内安装:2026最新保姆教程(附cc-switch配置)
|
10天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
3960 23
|
4天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
2206 4
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
5天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
2612 8
|
22天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
19273 61
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
2天前
|
SQL 人工智能 弹性计算
阿里云发布 Agentic NDR,威胁检测与响应进入智能体时代
欢迎前往阿里云云防火墙控制台体验!
1172 2

热门文章

最新文章