人类中断(HITL):Agent 执行中的人类审批
人类中断(HITL):Agent 执行中的人类审批
学习目标
理解 CopilotKit 的 HITL 实现:
- LangGraph Interrupt 事件的协议定义和传输
- 前端如何接收和渲染中断事件
- 用户审批后的恢复流程
- HITL 场景下的线程锁管理
前置知识
本章涉及护栏系统和事件驱动架构的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 CopilotKit 的 HITL 实现。
项目实践
Interrupt 事件协议
当 LangGraph Agent 执行到 interrupt() 节点时,通过 AG-UI 的 MetaEvent 通知前端。CopilotKit 定义了三种中断相关事件:
| 事件类型 | 方向 | 说明 |
|---|---|---|
LangGraphInterruptEvent | Agent → 前端 | Agent 暂停,携带中断信息 |
LangGraphInterruptResumeEvent | 前端 → Agent | 用户审批完成,携带恢复数据 |
CopilotKitLangGraphInterruptEvent | Agent → 前端 | 增强版,携带完整消息历史和工具调用 |
事件载荷结构(源码验证):
LangGraphInterruptEvent:{ type: "MetaEvent", name: "LangGraphInterruptEvent", value: "中断信息(字符串)"}
CopilotKitLangGraphInterruptEvent:{ type: "MetaEvent", name: "CopilotKitLangGraphInterruptEvent", data: { value: "中断信息", messages: [TextMessage | ActionExecutionMessage | ResultMessage] }}前端中断处理
React hook useLangGraphInterrupt 接收和处理中断事件:
const { interrupt, // 当前中断事件(或 null) setInterruptResponse // 设置用户审批结果,触发恢复} = useLangGraphInterrupt();
if (interrupt) { // 渲染审批 UI return ( <ApprovalDialog message={interrupt.value} onApprove={() => setInterruptResponse("approved")} onReject={() => setInterruptResponse("rejected")} /> );}QueuedInterruptEvent 队列管理(源码验证):
当 Agent 可能连续触发多个中断时,前端通过队列管理:
- 第一个中断事件到达 → 渲染审批 UI
- 用户审批 →
setInterruptResponse将结果传回 Agent - Agent 继续执行,可能触发下一个中断
- 新的中断事件进入队列,等待下一个审批
中断恢复流程
用户审批后,恢复流程:
关键细节:
- 恢复请求通过
interruptResumeAPI 发送 - 响应体包含用户审批结果(任意 JSON 值)
- Agent 接收到恢复数据后,从断点处继续执行
线程锁管理
在 HITL 场景中,Agent 的执行被暂停,但线程锁仍然有效。CopilotKit 的 Intelligence 模式通过以下机制管理:
| 参数 | 默认值 | 最大值 | 说明 |
|---|---|---|---|
lockTtlSeconds | 20s | 3600s(1 小时) | 锁的过期时间 |
lockHeartbeatIntervalSeconds | 15s | 3000s(50 分钟) | 心跳续约间隔 |
lockKeyPrefix | 默认 | 自定义 | Redis key 前缀 |
防泄漏设计:
- 即使用户永久不响应,锁最终会因 TTL 过期而自动释放
- 在用户审批期间,心跳续约保持锁有效
- 最大 TTL 限制为 1 小时,防止无限期锁定
问题与规避
| 问题 | 规避策略 |
|---|---|
| 中断事件在 SSE 流中的时序问题 | 中间件通过 middleware-sse-parser 解析 SSE 消息边界,确保中断事件完整到达 |
| 用户拒绝后的 Agent 行为 | Agent 需要处理中断响应为”拒绝”的情况(如回退到安全操作或终止 run) |
| 中断超时 | Intelligence 模式的 Redis 锁 TTL 是最终保障;前端可额外实现超时提示 |
| 多个中断的串行处理 | QueuedInterruptEvent 确保一次只处理一个中断,后续中断排队 |
设计取舍
HITL vs Guardrails
| 维度 | HITL | Guardrails |
|---|---|---|
| 触发方式 | Agent 主动请求 | 自动规则匹配 |
| 决策主体 | 人类 | 代码逻辑 |
| 适用场景 | 高风险操作(删除数据、发送消息) | 内容安全、格式校验 |
| 恢复方式 | 审批结果注入 Agent,Agent 继续推理 | 直接阻断或替换输出 |
组合使用:Guardrails 做自动的初级过滤,HITL 做人类的高级决策。
同步中断 vs 异步中断
同步中断(LangGraph 默认):
- Agent 执行到
interrupt()时完全暂停 - 必须等待人类响应才能继续
- 适合:必须人类确认的操作
异步中断(自定义 MetaEvent):
- Agent 继续执行,同时通知前端
- 前端可选择是否干预
- 适合:信息通知类场景
参考来源
packages/react-core/src/hooks/use-langgraph-interrupt.tspackages/runtime/src/service-adapters/events.ts(RuntimeEventTypes.MetaEvent)- LangGraph 人类中断文档