HITL 不只是审批:构建安全、可控的 AI Agent 人机协作系统
随着 AI Agent 获得文件写入、命令执行、邮件发送、数据库操作和线上部署等能力,它已经不再只是一个“会聊天的模型”,而是开始真实地改变外部世界。
能力越强,错误的代价也越高。
用户可能只是让 Agent 清理临时文件,模型却错误理解成删除整个目录;用户要求整理邮件草稿,Agent 却直接把未确认的内容发送给客户。模型并没有恶意,但自然语言存在歧义,大模型也可能产生幻觉,工具参数还可能在多轮规划中逐渐偏离最初目标。
因此,生产级 Agent 不能只关注“能不能完成任务”,还需要回答另一个问题:
在什么情况下,Agent 必须停下来,把控制权交还给人类?
这就是 HITL 要解决的问题。
一、什么是 HITL?
HITL 全称是 Human-in-the-Loop,常见中文翻译包括“人在回路”“人工介入”和“人机协同”。
它的核心含义是:
在 AI 的决策或执行流程中保留明确的人类参与节点,让人类能够审核、批准、修改、纠正、接管或终止任务。
很多人把 HITL 直接理解成“审批弹窗”。这种说法不算错,但不够完整。工具执行前审批只是 HITL 最常见的一种形式。
一个完整的 HITL 系统通常覆盖:
- 任务开始前确认目标;
- 执行计划审核;
- 危险操作审批;
- 工具参数修改;
- 模糊问题澄清;
- 异常任务升级;
- 执行过程暂停和接管;
- 最终结果复核;
- 操作记录与责任审计;
- 人工反馈沉淀。
因此,审批属于 HITL,但 HITL 不只等于审批。
二、为什么 Agent 需要 HITL?
传统聊天机器人主要输出文本,即使回答错误,影响通常也停留在屏幕上。Agent 可以调用工具,错误可能直接变成外部动作。
常见风险包括:
| 风险类型 | 示例 |
|---|---|
| 不可逆操作 | 删除目录、覆盖文件、清空数据库 |
| 外部传播 | 发送邮件、发布公告、提交工单 |
| 权限与安全 | 修改访问控制、暴露密钥、开放端口 |
| 资金影响 | 下单、退款、转账、调整广告预算 |
| 生产环境影响 | 部署代码、重启服务、修改配置 |
| 理解偏差 | 操作了错误文件、项目、账号或收件人 |
| 多 Agent 扩散 | 一个错误计划被多个 Worker 并行执行 |
HITL 的目标不是证明 AI“不可信”,而是为高风险、不确定或不可逆操作增加一道确定性的控制边界。
可以把 Agent 的自动化程度理解为一条连续光谱:
完全人工
→ AI 提建议、人工执行
→ AI 生成动作、人工批准
→ AI 自动执行高置信任务、异常时升级
→ 高度自动化、人工负责监督和审计
不同业务应该选择不同位置,而不是一味追求“全自动”。
三、HITL 最常见的功能
1. 任务目标确认
在任务启动前,让用户确认 Agent 对目标的理解。
例如:
用户:清理项目中的旧文件。
Agent:我理解的范围是删除 build/ 和临时日志,
不修改 src/、配置文件和 Git 历史。是否继续?
这种确认适合目标模糊、范围较大或可能产生破坏性结果的任务。
2. 计划审核
Plan-and-Execute Agent 通常会先生成执行计划。HITL 可以在计划开始前暂停:
计划:
1. 扫描重复文件
2. 删除超过 30 天的缓存
3. 修改构建配置
4. 重新运行构建
请选择:
[批准计划] [修改步骤] [删除步骤] [取消]
相比每一个工具调用都弹窗,计划级审批可以降低打断频率。不过计划批准不能自动代表所有具体参数都安全,高风险动作仍可能需要二次审批。
3. 危险工具执行前审批
这是最典型的 HITL:
Agent 生成工具调用
→ 策略引擎计算风险
→ 命中审批规则
→ 暂停工具执行
→ 人类作出决定
→ 执行、修改、跳过或拒绝
例如以下操作通常需要审批:
- 写入或覆盖重要文件;
- 删除文件和目录;
- 执行 Shell 命令;
- 修改数据库;
- 发送邮件或消息;
- 创建、删除云资源;
- 部署到生产环境;
- 进行支付或退款。
4. 参数预览与修改
用户不一定要在“批准”和“拒绝”之间二选一,还可以修改 Agent 生成的参数。
例如:
Agent 原计划:
path = /tmp/config.json
用户修改为:
path = /project/config.json
系统使用修改后的参数执行。这种能力可以在不中断整个任务的情况下纠正局部错误。
5. 澄清问题
当关键信息缺失时,Agent 不应该猜测。
例如:
Agent:发现两个名为 production 的集群:
1. production-eu
2. production-us
请选择需要部署的目标。
澄清并不一定涉及危险操作,但它可以防止 Agent 因实体、范围或业务意图不明确而执行错误动作。
6. 异常升级
Agent 可能遇到权限不足、工具失败、结果冲突或置信度过低等情况。系统可以把任务升级给人类:
状态:等待人工处理
原因:连续三次部署失败
已尝试:重新构建、重新认证、回滚依赖
需要决策:是否回滚到上一版本
这类机制通常被称为 escalation,也就是“升级处理”。
7. 人工暂停、接管和恢复
对于长时间运行的 Agent,需要提供:
- 暂停任务;
- 终止任务;
- 人工完成某一步;
- 修改任务状态;
- 从指定步骤恢复执行。
例如,客服 Agent 遇到复杂投诉时可以把对话转交人工客服;人工处理完成后,再把结果交回 Agent 继续完成记录和后续流程。
8. 最终结果复核
某些操作不适合在工具调用层逐一审批,更适合在最终发布前统一审核:
- 邮件草稿生成后,发送前确认;
- 合同分析完成后,由律师复核;
- 代码修改完成后,合并前审查;
- 报告生成后,发布前确认;
- 营销内容生成后,品牌负责人审核。
这属于“输出闸门”:Agent 可以自由生成草稿,但不能绕过最终发布审批。
9. 审批授权范围管理
为了避免连续弹窗,系统通常支持多种授权范围:
| 授权方式 | 含义 |
|---|---|
| 单次批准 | 只批准当前操作 |
| 同类批准 | 当前会话放行同一工具 |
| 参数范围批准 | 只允许指定路径、项目或资源 |
| 时间范围批准 | 在有限时间内放行 |
| 计划范围批准 | 只批准当前计划中的已知步骤 |
| 永久规则 | 保存为后续策略,需要谨慎使用 |
“全部放行”不应该简单做成全局开关。批准 write_file 不代表批准 execute_command,批准写入 /tmp/project 也不代表允许覆盖任意系统文件。
10. 审计追踪
HITL 系统应记录完整的决策链:
- 谁发起了操作;
- 哪个 Agent、任务和步骤触发;
- 原始参数是什么;
- 风险等级和命中规则;
- 谁进行了审批;
- 审批结果和原因;
- 参数是否被修改;
- 最终是否执行成功;
- 发生时间和关联任务。
审计日志既用于安全追溯,也可以帮助优化审批策略。
11. 人工反馈闭环
人工拒绝和修改不是一次性数据,它们可以帮助系统改进后续行为。
例如用户多次把:
/tmp/project
修改为:
/workspace/project
系统可以在获得用户明确同意后,把“项目默认创建在 /workspace”保存为长期偏好。需要注意:反馈学习不能绕过权限系统,也不能因为某次批准就永久放宽高风险策略。
四、HITL 的完整执行流程
一个较完整的工具审批流程如下:
用户目标
→ Agent 生成计划
→ 可选:计划审核
→ Agent 生成工具调用
→ 参数校验
→ 风险策略判断
→ 安全操作直接执行
→ 高风险操作创建审批请求
→ 人类批准、拒绝、修改或跳过
→ 工具执行或 Agent 重新规划
→ 记录审计日志
→ 可选:最终结果复核
这里最重要的原则是:
审批必须发生在实际副作用之前,并且所有危险工具都必须经过同一个不可绕过的拦截入口。
如果只在 Agent 主流程里调用审批,但子代理、计划执行器或其他工具入口仍能直接执行,就会留下绕过路径。
五、风险判断:静态规则还是 LLM?
风险判断通常有三种方式。
静态规则
private static final Set<String> APPROVAL_REQUIRED = Set.of(
"write_file",
"delete_file",
"execute_command",
"send_email",
"deploy_production"
);
优点:
- 确定、快速;
- 容易测试和审计;
- 不受模型随机性影响。
缺点:
- 粒度较粗;
- 同一工具的不同参数可能风险差异很大。
例如 execute_command("pwd") 与 execute_command("rm -rf ...") 显然不应被视为相同风险。
参数化规则
在工具名称之外继续检查参数:
write_file + /tmp/** → 低风险
write_file + 当前项目目录 → 中风险
write_file + 项目目录之外 → 高风险
execute_command + 只读命令 → 低风险
execute_command + 删除或提权命令 → 高风险
这种方式更适合生产环境。规则可以结合:
- 工具名称;
- 文件路径;
- 命令类型;
- 环境;
- 数据敏感级别;
- 资源价值;
- 用户角色;
- 操作是否可逆;
- 影响范围。
LLM 辅助判断
LLM 可以用于解释风险、归纳参数和发现规则未覆盖的异常,但不宜作为唯一安全边界。
更稳妥的设计是:
硬规则负责阻断
+ 参数规则负责分级
+ LLM 负责补充风险说明
+ 人类负责最终高风险决策
安全控制应尽量确定、可重复和可审计。
六、审批请求应该展示什么?
一个有用的审批请求不能只问“是否允许”。用户需要足够的信息做判断。
public record ApprovalRequest(
String requestId,
String agentId,
String taskId,
String stepId,
String toolName,
String arguments,
String riskLevel,
String riskDescription,
String expectedImpact,
boolean reversible,
Instant expiresAt
) {}
建议展示:
- 谁要执行:主 Agent 还是某个子代理;
- 为什么执行:对应哪个目标和步骤;
- 要执行什么:工具名称和关键参数;
- 会影响哪里:文件、账号、环境或收件人;
- 风险有多高;
- 是否可逆;
- 如何回滚;
- 审批何时失效。
示例:
需要人工审批
调用者:deploy-agent
任务:发布订单服务 2.3.0
环境:production-eu
操作:deploy_service
风险:高
影响:将替换 6 个生产实例
回滚:可回滚至 2.2.4
[批准一次] [修改参数] [拒绝] [取消任务]
如果审批框缺少调用者和业务上下文,用户即使看见参数,也可能无法判断操作是否合理。
七、审批结果如何设计?
常见决策包括:
public enum Decision {
APPROVED,
APPROVED_SCOPED,
REJECTED,
MODIFIED,
SKIPPED,
PAUSED,
CANCELLED,
ESCALATED
}
它们分别表示:
APPROVED:批准当前操作;APPROVED_SCOPED:在指定范围内放行;REJECTED:拒绝并允许 Agent 根据原因重新规划;MODIFIED:修改参数后执行;SKIPPED:跳过当前步骤;PAUSED:暂停任务,等待后续处理;CANCELLED:取消整个任务;ESCALATED:转交具有更高权限或专业能力的人。
拒绝原因应该作为结构化结果返回 Agent:
{
"decision": "REJECTED",
"reason": "目标路径错误,只允许写入当前项目目录",
"retryAllowed": true
}
这样 Agent 可以重新规划,而不是不断重复相同请求。
八、工具拦截层的设计
HITL 最适合放在统一的工具执行入口:
public final class HitlToolExecutor implements ToolExecutor {
private final ToolExecutor delegate;
private final ApprovalPolicy policy;
private final HitlHandler hitlHandler;
private final AuditLogger auditLogger;
@Override
public ToolResult execute(ToolCall call, ExecutionContext context) {
RiskAssessment risk = policy.assess(call, context);
if (!risk.requiresApproval()) {
return delegate.execute(call, context);
}
ApprovalRequest request =
ApprovalRequestFactory.create(call, context, risk);
ApprovalResult decision =
hitlHandler.requestApproval(request);
auditLogger.record(request, decision);
return switch (decision.decision()) {
case APPROVED ->
delegate.execute(call, context);
case MODIFIED ->
delegate.execute(call.withArguments(
decision.modifiedArguments()), context);
case SKIPPED ->
ToolResult.skipped("用户跳过了当前步骤");
case REJECTED ->
ToolResult.rejected(decision.reason());
case CANCELLED ->
ToolResult.cancelled("用户取消了任务");
default ->
ToolResult.waiting("等待进一步人工处理");
};
}
}
这种装饰器式拦截比把审批代码散落在 Agent、SubAgent 和 PlanExecutor 中更容易保证一致性。
核心要求是:
- 所有真实工具调用都经过这一入口;
- 参数修改后重新执行校验;
- 审批通过不能绕过底层权限检查;
- 超时、断线和异常默认不执行;
- 审批令牌只能使用一次;
- 审批内容与最终执行参数必须一致。
九、同步审批与异步审批
命令行工具通常使用同步审批:
弹出请求 → 等待输入 → 得到结果 → 继续执行
企业工作流更常使用异步审批:
创建审批任务
→ Agent 状态变为 WAITING_FOR_APPROVAL
→ 通知审批人
→ 审批人稍后处理
→ 系统验证审批结果
→ Agent 从检查点恢复
异步审批需要额外处理:
- 任务持久化;
- 审批超时;
- 审批人身份验证;
- 重复提交;
- 请求过期;
- 恢复时环境是否变化;
- 执行前重新校验参数和资源状态。
例如,上午批准的部署任务如果晚上才恢复,生产版本可能已经变化,系统不能直接使用过时审批继续执行。
十、Multi-Agent 场景下的 HITL
多个 Worker 并发执行时,HITL 会更加复杂。
审批请求必须包含调用者
至少包含:
Agent ID
子任务 ID
计划步骤
父任务
工具和参数
否则用户无法区分哪个子代理正在操作什么。
避免多个审批框争抢输入
终端环境中,可以将审批请求放入队列并串行展示:
public synchronized ApprovalResult requestApproval(
ApprovalRequest request) {
return promptUser(request);
}
更成熟的系统可以提供统一审批中心,而不是让多个线程直接读取同一个标准输入。
审批范围不能无限扩散
某个子代理获准写入特定目录,不代表其他子代理自动获得相同权限。授权范围应该绑定:
- Agent;
- 任务;
- 工具;
- 参数范围;
- 时间窗口。
拒绝后避免重复申请
如果用户已经拒绝相同操作,子代理不应无休止重新申请。主代理需要根据拒绝原因修改计划,或者终止对应分支。
十一、HITL 的 Fail-Safe 原则
安全系统应该采用“没有明确批准,就不执行”的默认策略。
以下情况通常都应视为拒绝或等待:
- 用户输入无法识别;
- 审批超时;
- 网络中断;
- 审批服务不可用;
- 请求已经过期;
- 参数在审批后发生变化;
- 审批人权限不足;
- 无法验证审批结果;
- 任务状态与审批时不一致。
不要因为审批系统故障而自动放行危险操作。对高风险工具来说,“失败时关闭”比“失败时开放”更安全。
十二、常见设计误区
误区一:只按工具名判断风险
同一工具的不同参数风险可能完全不同,应进一步检查路径、环境和影响范围。
误区二:一次批准永久有效
审批应有明确的 Agent、任务、资源和时间作用域。
误区三:审批通过等于跳过权限
HITL 是额外安全层,不替代操作系统权限、沙箱、最小权限和访问控制。
误区四:让 LLM 决定自己是否需要审批
模型可以提供风险解释,但不应该拥有绕过硬规则的最终决定权。
误区五:只展示工具名
用户需要看到实际参数、业务目的、影响范围和回滚方案。
误区六:参数修改后直接执行
修改后的参数必须重新做 schema 校验、权限检查和风险评估。
误区七:所有操作都要求审批
审批过多会产生“确认疲劳”,用户最终可能无脑点击批准。系统应重点拦截真正高风险或不确定的操作。
误区八:只记录审批结果
还要记录原始请求、修改内容、审批原因和最终执行结果,才能形成完整审计链。
十三、如何减少审批疲劳?
HITL 不是弹窗越多越安全。过度审批会让用户失去警惕。
可以采用:
- 只读操作默认放行;
- 按风险等级设置不同策略;
- 对低风险、可逆操作使用批量批准;
- 对确定路径设置范围授权;
- 对同一计划合并相似审批;
- 清晰展示参数差异;
- 高风险审批使用更强确认方式;
- 根据历史拒绝数据优化规则;
- 使用沙箱降低操作风险,从而减少审批。
例如:
读取项目文件 → 自动执行
在临时目录创建文件 → 自动执行并记录
覆盖项目配置 → 请求确认
删除目录 → 请求确认并二次提示
修改生产数据库 → 指定角色审批
大额支付 → 双人审批
风险越高,审批强度越高。
十四、如何测试 HITL?
HITL 测试至少应该覆盖:
策略测试
- 安全工具是否绕过审批;
- 危险工具是否一定被拦截;
- 参数变化是否影响风险等级;
- 不同环境和用户角色是否正确处理。
决策测试
- 批准后是否执行原参数;
- 修改后是否执行新参数;
- 拒绝后是否停止;
- 跳过后是否进入下一步;
- 取消后是否终止整个任务。
Fail-Safe 测试
- 输入无效;
- 审批超时;
- 审批处理器抛出异常;
- 请求过期;
- 执行前参数被篡改;
- 审批服务不可用。
并发测试
- 多个子代理同时触发审批;
- 审批请求是否串行或正确排队;
- 同类授权缓存是否线程安全;
- 一个任务取消后,其他等待请求是否失效。
审计测试
- 原始参数和最终参数是否都被记录;
- 审批人、时间和原因是否完整;
- 工具执行结果是否能关联回审批请求。
接口隔离可以让测试不依赖真实终端:
HitlHandler handler = mock(HitlHandler.class);
when(handler.requestApproval(any()))
.thenReturn(ApprovalResult.approved());
verify(handler, times(1))
.requestApproval(any());
Mock 在这里相当于审批处理器的“测试替身”,可以精确模拟批准、拒绝、超时和异常。
十五、生产级 HITL 的推荐架构
一套更完整的 HITL 系统通常包含:
| 组件 | 职责 |
|---|---|
| Policy Engine | 根据工具、参数、环境和身份评估风险 |
| Tool Interceptor | 在副作用发生前统一拦截 |
| Approval Service | 创建、持久化和处理审批请求 |
| Identity & RBAC | 验证审批人身份和权限 |
| Task Checkpoint | 保存暂停时的 Agent 状态 |
| Notification | 通知对应审批人 |
| Audit Log | 记录请求、决策和执行结果 |
| Resume Engine | 审批后安全恢复任务 |
| Feedback Store | 保存人工纠正和改进信号 |
HITL 也必须和其他安全机制配合:
最小权限
+ 沙箱隔离
+ 参数校验
+ 风险策略
+ 人工审批
+ 审计日志
+ 回滚能力
人工审批不是万能安全措施。如果底层工具拥有无限权限,即使用户偶尔批准错误,也可能造成严重影响。最可靠的系统应该首先限制 Agent“最多能做什么”,再决定“哪些动作需要人批准”。
十六、衡量 HITL 是否有效
可以关注以下指标:
- 审批请求数量;
- 批准率和拒绝率;
- 参数修改率;
- 平均等待时间;
- 审批超时率;
- 重复申请率;
- 人工接管率;
- 因审批避免的错误操作数;
- 用户无脑批准比例;
- 不同策略规则的命中分布。
如果某类请求几乎永远被批准,可能应该缩小审批范围;如果某类请求经常被拒绝,说明 Agent 规划、默认参数或权限设计需要改进。
总结
HITL 的本质不是在 Agent 旁边加一个“确认按钮”,而是建立一套人类可以随时影响 AI 决策和执行的人机协作机制。
它常见的能力包括:
目标确认
+ 计划审核
+ 危险操作审批
+ 参数修改
+ 澄清提问
+ 异常升级
+ 暂停与接管
+ 最终结果复核
+ 授权范围管理
+ 审计与反馈
在一个可靠的 Agent 系统中,低风险、可逆且确定的操作可以自动完成;高风险、不可逆或存在歧义的操作必须把控制权交还给人类。
真正优秀的 HITL,不是让用户为 Agent 的每一步机械地点“批准”,而是在最需要人类判断的时刻暂停,提供足够信息,让人能够做出清晰、可追踪、可撤销的决定。