跳转到内容

人在回路(HITL):Agent 流程中的确认与编辑

人在回路(HITL):Agent 流程中的确认与编辑

学习目标

本章要解决什么问题:当 Agent 需要人类确认、编辑或拒绝其操作时,如何在不中断整个 Agent 流程的情况下插入人工审查环节?

读者将学到:

  • LangChain HITL 中间件的核心概念(ActionRequest、Decision、ReviewConfig)
  • 四种决策类型(approve/edit/reject/respond)的使用场景
  • 如何使用 LangGraph 的 interrupt() 实现人工等待

前置知识

本章是 LangChain 特有的交互模式实现,不涉及通用知识原理。

项目实践

HITL 的核心数据结构

# 伪代码
class ActionRequest(TypedDict):
name: str # 动作名称
args: dict # 动作参数
description: str # 动作描述(供人类审查)
class ReviewConfig(TypedDict):
action_name: str # 关联的动作名称
allowed_decisions: list[Literal["approve", "edit", "reject", "respond"]] # 允许的决策
args_schema: dict # 参数 JSON Schema(用于编辑时的表单校验)

四种决策类型

决策含义适用场景
approve批准执行操作看起来安全
edit修改参数后执行操作方向正确但参数需要调整
reject拒绝执行操作不安全或不相关
respond直接回复用户想提供额外信息而非审查操作

基于 LangGraph interrupt() 的实现

HITL 中间件利用 LangGraph 的 interrupt() 函数实现人工等待:

# 伪代码
def hitl_middleware(state, runtime):
# 1. 从模型消息中提取工具调用请求
action_requests = extract_action_requests(state["messages"])
# 2. 构建 HITL 请求
hitl_request = HITLRequest(
action_requests=action_requests,
review_configs=[...], # 每个动作的审查配置
)
# 3. 中断 Agent 执行,等待人类输入
human_response = interrupt(value=hitl_request)
# 4. 根据人类决策处理
for decision in human_response["decisions"]:
if decision["type"] == "approve":
# 继续执行原工具调用
pass
elif decision["type"] == "edit":
# 使用修改后的参数重新构建工具调用
modify_tool_call_args(decision["args"])
elif decision["type"] == "reject":
# 注入拒绝消息作为工具结果
inject_tool_message(f"Action rejected: {decision.get('reason')}")
elif decision["type"] == "respond":
# 将用户响应注入对话
inject_human_message(decision["content"])

关键机制interrupt() 会暂停图的执行并等待外部输入。外部系统(如 Web UI)通过 LangGraph 的 API 提供人类决策,然后图从暂停处恢复。

ReviewConfig 的 per-action 配置

不同动作可以有不同的审查策略:

# 伪代码
review_configs = [
{
"action_name": "send_email",
"allowed_decisions": ["approve", "edit", "reject"],
"args_schema": { # 编辑时校验的 Schema
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
},
{
"action_name": "search_db",
"allowed_decisions": ["approve", "reject"], # 不允许编辑
},
]

问题与规避

interrupt() 的持久化依赖

问题interrupt() 需要配合 checkpointer 使用,否则中断状态无法恢复。

对策:使用 HITL 中间件时,create_agent() 必须传入 checkpointer 参数(如 SqliteSaverMemorySaver)。

超时处理

问题:人类可能长时间不响应中断,导致 Agent 挂起。

对策:在上层应用中设置中断超时,超时后自动注入 reject 决策或终止 Agent 执行。

设计取舍

interrupt() vs 工具模拟

方案优势代价
interrupt()图级别暂停、状态持久化、恢复简单需要 checkpointer、整个图暂停
工具模拟细粒度控制、可以只暂停特定工具实现复杂、需要模拟工具处理中断

LangChain 选择 interrupt() 方案,因为它利用了 LangGraph 内置的状态持久化机制,确保 Agent 在人类审查期间的状态不会丢失。

参考来源