人机协作

之前Agent 调用工具时都是自动执行的,但在企业生产中有很多高风险动作,比如:删除数据库表;删除文件;发送邮件;提交订单。模型可能理解错用户意图,也可能选错参数。一旦工具真的执行,可能造成数据丢失或业务事故

所以 DeepAgents 提供了人机协作能力,也就是常说的 HITL:Human In The Loop

Agent 先正常规划任务;一旦遇到被 interrupt_on 标记的高风险工具,就暂停在执行前;人工审核后,可以选择放行、修改参数后放行,或者拒绝执行;最后再从暂停的位置继续往下跑

image

名词解释:

名称 可以先这样理解
interrupt_on 哪些工具需要在执行前停下来,交给人工审批
checkpointer 保存“Agent 暂停在哪里”的短期状态
thread_id 找回同一次执行线程的 ID,恢复时必须保持一致
__interrupt__ 第一次执行后返回的中断信息
Command(resume=...) 第二次恢复执行时传入的恢复指令

动作按工具分类:

类型 含义 是否通常需要审批
Create 创建数据或资源 视情况而定
Read 查询、读取数据 通常不需要
Update 修改数据或配置 通常需要
Delete 删除数据或资源 强烈建议需要
@tool
def delete_database(table_name: str):
    """
    删除数据库表

    这是高风险动作,所以会在 interrupt_on 中配置为执行前中断
    示例只返回模拟结果,不会真的删除数据库表
    """
    print(f"调用 delete_database 工具,准备删除 {table_name} 表")
    return f"已删除表:{table_name}"

@tool
def select_database(table_name: str):
    """
    查询数据库表数据

    查询动作属于低风险读操作,本示例不会对它做人工审批
    """
    print(f"调用 select_database 工具,查询 {table_name} 表数据")
    return f"已查询表 {table_name} 的数据"

配置人机的三个关键点

  1. 用 checkpointer 保存暂停状态
  2. 用稳定的 thread_id 找回同一次执行
  3. 用 interrupt_on 标记哪些工具需要审批

checkpointer保存暂停位置

人机协作必须配合检查点使用。因为 Agent 第一次执行到危险动作时会暂停,后面还要继续恢复执行。中间状态必须被保存下来

#  人机协作必须配置 checkpointer
# 第一次执行命中中断点时,Agent 会把暂停位置保存到检查点中

from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
 # InMemorySaver 适合教学和本地测试。它会把中断状态保存在当前 Python 进程内存中,程序一退出状态就会丢失
 # 生产环境中通常会换成 Redis、数据库等持久化检查点,这样中断恢复才能跨进程、跨服务继续工作

用thread_id 找回同一次执行.

# 恢复执行时必须使用相同 thread_id
# 可以把 thread_id 理解成一次任务的唯一执行线程 ID
thread_config = {
    "configurable": {
        "thread_id": "hitl-approval-demo",
    }
}
# 后面第一次执行和第二次恢复执行,都会使用这个 thread_config

配置需要中断的工具

# 创建 Agent 时,通过 interrupt_on 指定哪些工具需要审批

main_agent = create_deep_agent(
    model=llm,
    tools=[delete_database, delete_file, select_database],
    checkpointer=checkpointer,
    system_prompt="""
    你是一个负责执行数据库和文件操作的智能助手
    请根据用户需求调用合适的工具,并使用中文回复执行结果
    """,
    # interrupt_on 用工具名配置哪些动作需要人工审批
    # True 表示使用默认审批选项:approve、edit、reject
    # False 表示该工具不需要中断,可以直接执行
    interrupt_on={
        "delete_database": True,
        "delete_file": True,
        "select_database": False,
    },
)

从中断到恢复

如果模型决定调用 delete_database 或 delete_file,由于这两个工具配置了中断,第一次调用不会真的删除,而是返回中断信息。 中断信息通常在 __interrupt__ 字段中

# 命中人机协作中断时,结果中会出现 __interrupt__
# __interrupt__ 是一个列表,里面保存 Interrupt 对象
interrupts = result_1.get("__interrupt__", [])

if interrupts:
    # action_requests 里会包含模型准备执行的动作,例如工具名和参数。
    action_requests = interrupts[0].value["action_requests"]
    print(
        "本次需要审核的工具数量:"
        f"{len(action_requests)},具体拦截的工具:"
        f"{[action['name'] for action in action_requests]}"
    )

 可以把它理解成 Agent 在问人:我准备调用 delete_database(table_name="user"),是否允许?

实际的interrupts结构:

# 每个 Interrupt 的 value 可以理解成这样的结构:
{
    # action_requests:模型准备执行、但还没真正执行的高风险工具调用
    "action_requests": [
        {"name": "delete_database", "args": {"table_name": "user"}},
        {"name": "delete_file", "args": {"file_name": "zhaoweifeng.txt"}},
    ],
    # review_configs:每个被拦截工具允许的人工决策类型
    "review_configs": [
        {"action_name": "delete_database", "allowed_decisions": ["approve", "edit", "reject"]},
        {"action_name": "delete_file", "allowed_decisions": ["approve", "edit", "reject"]},
    ],
}

用command恢复执行 

# 人工确认后,需要用 Command(resume=...) 恢复执行
from langgraph.types import Command

decisions = []

for action in action_requests:
    action_name = action["name"]

    # decisions 的顺序要和 action_requests 的顺序保持一致
    # reject 表示拒绝执行该工具;approve 表示允许继续执行该工具
    if action_name == "delete_database":
        decisions.append({"type": "reject"})
    elif action_name == "delete_file":
        decisions.append({"type": "approve"})

# 第二次 invoke 不再传用户原始问题,而是传 Command(resume=...)
# config 必须继续使用第一次相同的 thread_id,Agent 才能找到之前暂停的位置
result_2 = main_agent.invoke(
    # 第二次执行不再传用户原始问题,而是传 Command(...)
    Command(
        resume={
            # decisions 的数量和顺序要和 action_requests 对应上
            "decisions": decisions,
        }
    ),
    # 第二次执行必须使用第一次相同的 config,尤其是相同的 thread_id
    config=thread_config,
)

本节示例的主线可以理解为:第一次 invoke -> 收到 __interrupt__ -> 构造 decisions -> 第二次 invoke(Command) -> reject 删除表、approve 删除文件

整体流程回顾:

配置 checkpointer
  -> 配置稳定 thread_id
  -> 配置 interrupt_on
  -> 第一次 invoke
  -> 命中高危工具,返回 __interrupt__
  -> 人工审批
  -> 第二次 invoke(Command(resume=...))
  -> 使用相同 thread_id 恢复执行

 三种人工决策:

决策类型 含义 适合场景 使用
approve 按模型原计划继续执行工具 工具和参数都没问题,人工确认可以执行 decisions.append({"type": "approve"})
reject 拒绝这次工具调用 动作风险太高,或者模型理解错了用户意图 decisions.append({"type": "reject"})
edit 修改工具名或参数后再执行 工具选对了,但参数需要人工修正 decisions.append( { "type": "edit", "edited_action": { "name": action_name, "args": { "table_name": "archived_user", }, }, } )

举例:

decisions = []

for action in action_requests:
    action_name = action["name"]

    # edit 表示人工不拒绝这个工具调用,但要先修正工具名或参数
    # edited_action 中的 name 是最终要执行的工具名,args 是修正后的工具参数
    if action_name == "delete_database":
        decisions.append(
            {
                "type": "edit",
                "edited_action": {
                    "name": action_name,
                    "args": {
                        "table_name": "archived_user",
                    },
                },
            }
        )
    elif action_name == "delete_file":
        decisions.append({"type": "approve"})

 

posted @ 2026-06-22 15:29  幻影之舞  阅读(10)  评论(0)    收藏  举报