53 Human-in-the-loop:让关键动作暂停、确认与恢复
Human-in-the-loop:让关键动作暂停、确认与恢复
HITL 不是弹一个“确定吗”,而是让工作流在关键动作前持久化暂停,并在确认后安全恢复。
[TOC]
1. 先把 Agent 想清楚
很多人一听到 Agent,就会想到“让大模型自己决定下一步”。这个理解只对了一半。
在真实项目里,Agent 更像是一个被工程规则约束住的任务执行器:
模型负责理解自然语言、提出候选动作
代码负责权限校验、状态流转、工具执行
业务接口负责最终事实和写操作
日志审计负责事后追踪
如果等待用户确认的状态只保存在内存里,服务一重启,任务就丢了;如果确认接口还能改参数,就存在篡改风险。
所以这一阶段的博客不要写成“模型越来越聪明”。更准确的写法是:我们正在学习如何把模型的灵活性放进一个可控的工作流里。
2. 一个业务场景
Agent 准备创建工单,先展示订单、问题类型、联系方式和摘要。用户确认后,系统从检查点恢复,用冻结参数创建工单。
这个场景里面最重要的不是模型能不能说出一段好听的话,而是:
它选了哪个工具
为什么能调用这个工具
参数从哪里来
权限在哪里校验
失败后状态怎么变
用户确认前会不会产生真实写入
把这些问题问清楚,Agent 项目才不会变成一个不可解释的聊天黑盒。
这一节要先判断什么时候不能自动化。
判断顺序可以这样写:
1. 高风险写操作、低置信度判断、资料冲突和用户投诉升级,都应进入人工确认
2. 人工介入前要保存当前 state、工具结果、模型建议和候选动作
3. 人工确认后才能执行真实业务动作,确认结果也要写回状态
4. 用户等待时要返回明确状态,而不是让请求无限挂起
5. 最后用转人工、人工拒绝、人工修改和超时样例验证
它解决的是风险控制问题:HITL 不是降低智能程度,而是给高风险决策加一道确定性闸门。
3. 核心概念
1. 暂停点
高风险动作前保存检查点和待确认动作。
这一点在项目里要落到具体规则:先保存再等待。
2. 冻结参数
确认的是预览时冻结的参数,不是确认时重新传的参数。
这一点在项目里要落到具体规则:防止确认后变更。
3. 恢复执行
确认后从检查点继续,并重新校验权限和业务状态。
这一点在项目里要落到具体规则:恢复也要校验。
4. 拒绝和超时
用户拒绝或超时都要有明确终态,不产生写操作。
这一点在项目里要落到具体规则:不确认不执行。
4. 整体流程
graph TD;
A["生成动作预览"] --> B["保存 pendingAction"]; B["保存 pendingAction"] --> C["返回确认摘要"];
C["返回确认摘要"] --> D["用户确认或拒绝"];
D["用户确认或拒绝"] --> E["恢复检查点"];
E["恢复检查点"] --> F["重新校验"];
F["重新校验"] --> G["执行或终止"];
这张图里,模型只是其中一个节点。工具、状态、权限、确认和审计同样重要。
如果一个 Agent 图里只有“用户 -> 模型 -> 答案”,那它还只是 Chat,不是能进入业务系统的 Agent。
5. 落地步骤
1. 设计 pendingAction
保存 actionId、冻结参数、摘要、用户、过期时间和状态。
完成后应该能看到:pendingAction 结构。
2. 确认接口原子更新
从 pending 改 confirmed 要原子化,防止重复确认。
完成后应该能看到:状态更新语句。
3. 恢复前再校验
等待期间订单状态可能变化,执行前必须重新检查。
完成后应该能看到:恢复校验逻辑。
4. 处理拒绝和过期
拒绝和超时写入审计,并清理可执行状态。
完成后应该能看到:终态记录。
6. 状态、接口或工具字段
| 项目 | 说明 |
|---|---|
| actionId | 待确认动作 ID |
| frozenPayload | 冻结参数 |
| payloadDigest | 参数摘要 |
| expiresAt | 过期时间 |
| status | pending/confirmed/rejected/expired/executed |
字段设计要尽量结构化。不要让下游通过一段自然语言去猜“成功了没有”“下一步要干什么”。
7. 示例
pendingAction
- actionId
- actionType
- frozenPayload
- payloadDigest
- requestedBy
- createdAt
- expiresAt
- status
- checkpointId
示例里的字段不一定照搬,但思想要保留:输入、状态、输出、错误和审计信息要分开。
8. 哪些事不能交给模型自由发挥
模型不能把“用户看起来同意了”当成确认。确认必须来自明确 UI 操作或可信接口,并绑定当前用户。
这一节特别重要。Agent 项目最容易踩坑的地方,就是把“理解意图”和“执行业务动作”混成一件事。
模型可以建议动作,但最终执行必须经过代码和业务系统的规则。
9. 测试路径
建议至少准备下面几类测试:
正常路径:输入完整,工具正常返回
缺字段路径:订单号、联系方式、问题描述不完整
权限路径:查询别人的订单、访问无权知识库
失败路径:工具超时、模型输出非法、检索为空
恢复路径:等待确认时服务重启、重复确认、超时确认
Agent 的测试不能只看最终回答。你要检查状态、工具调用、审计日志和是否产生真实副作用。
10. 工程取舍
- HITL 增加交互成本,但保护高风险动作。
- 冻结参数增加存储,但避免确认后篡改。
- 恢复前重校验会多一次查询,但更安全。
取舍讲清楚以后,读者才能知道你为什么这样设计,而不是以为所有问题都要上 Agent。
11. 常见坑
- 只在进程内等待用户输入。
- 确认接口接收新参数。
- 用户拒绝后仍能恢复执行。
- 重复点击创建多次。
这些坑本质上都和一个问题有关:没有把模型输出当成“不可信的候选结果”。
12. 验收清单
- ☐ 暂停前持久化状态
- ☐ 确认绑定冻结参数
- ☐ 重复确认不会重复执行
- ☐ 拒绝和超时有终态
- ☐ 恢复前重新校验
如果这些检查项没过,先不要急着加更多工具。工具越多,边界越乱,排查越难。
13. 落地清单
- ☐ 设计 pendingAction 字段
- ☐ 写确认状态流
- ☐ 模拟服务重启后恢复
- ☐ 测试重复确认
练习时可以先用 Mock,不要一开始就连真实订单或工单系统。等状态、权限和幂等设计清楚后,再接真实接口。
14. 小结
HITL 的本质是把人作为工作流中的一个可靠节点,而不是把确认当成一句聊天内容。
Agent 的价值来自灵活性,但安全来自边界。能把这两件事同时讲清楚,才算真正理解 Agent 工程化。