logo

Human-in-the-loop:高风险动作前的审批设计

Published on

Human-in-the-loop:高风险动作前的审批设计

“需要人工确认”很容易写进产品需求,也很容易被做成一个没有意义的按钮。Agent 准备删除 200 个文件,界面只显示“是否继续”;用户不知道文件是什么、为什么删、能不能恢复,最后只能凭感觉点击确认。另一个系统则在用户确认以后重新规划了一遍,参数已经变了,用户确认的其实不是最后执行的那件事。

我见过这种流程以后,开始把人工确认当成一种安全协议,而不是 UI 装饰。Agent 可以帮助收集信息、生成计划和准备参数,但真正产生副作用的动作,需要让人看见具体影响,并且确认的内容必须和最终执行严格对应。

Human-in-the-loop 的目的不是让人替模型点击每一步,而是在不可逆、高风险或不确定的节点,把决定权交还给人。

一、先给动作做风险分级

风险不能只按工具名称判断。同一个“发送消息”工具,发送给自己和发送给一万个客户,风险完全不同;同一个“更新数据库”操作,修改测试记录和修改支付状态,也不是一个级别。

type ActionRisk = {
  reversibility: 'reversible' | 'partially_reversible' | 'irreversible'
  dataSensitivity: 'public' | 'internal' | 'sensitive'
  affectedUsers: number
  financialImpact: number
  externalCommunication: boolean
}

可以根据动作的不可逆程度、影响人数、数据敏感度、金额和外部传播范围计算风险等级,但高风险规则最好有明确的硬条件:删除、付款、权限变更、公开发布和跨租户访问默认需要更高等级审批。

二、确认前先生成可审查的计划

不要只让 Agent 说“我准备执行这个操作”。它应该给出对象、参数、来源、预计影响和失败处理:

type ApprovalRequest = {
  approvalId: string
  taskId: string
  action: string
  target: string
  parameters: Record<string, unknown>
  reason: string
  evidence: string[]
  impact: string[]
  rollback?: string
  expiresAt: string
}

例如删除文件时,展示文件列表、总大小、来源规则、是否有备份和无法恢复的部分;发送邮件时展示收件人数量、主题、正文摘要和附件;修改权限时展示当前权限、目标权限和受影响用户。

计划不是执行。系统要在审批通过后冻结关键参数,防止 Agent 在等待期间重新规划,再执行一组不同的动作。

三、确认必须绑定具体版本

审批对象应该有内容哈希或版本号:

const approvalHash = hash({
  action,
  target,
  parameters,
  evidenceVersion,
})

执行前重新计算并比较。如果目标文件、收件人列表、金额、权限或证据已经变化,就让审批失效,重新生成预览。不能让用户确认了“给 A 发 100 元”,系统实际却执行了“给 A 和 B 各发 100 元”。

这也是处理并发变化的关键。审批等待期间,另一个管理员可能已经修改了资源;版本不一致时,正确动作是暂停,而不是假设旧计划仍然安全。

四、确认界面要讲人话

用户不是来审查 JSON 的。界面应该先说结论和影响,再提供细节:

即将发送一封邮件给 238 位客户
原因:通知活动延期
附件:活动说明.pdf(1 个)
不可撤回:发送后无法保证收件人未阅读

[查看完整收件人] [取消] [确认发送]

按钮不能使用模糊文字,例如“继续”“执行一下”。确认动作要明确写出动词和对象。高风险操作可以要求二次确认、输入资源名称或选择明确的审批人,但不要通过一堆无意义弹窗制造确认疲劳。

如果动作影响很多对象,默认展示数量、范围和异常项。用户应该能快速发现“目标不是我以为的那一批”。

五、谁来确认是权限问题

不是“有人点击了确认”就够了。审批人必须具备处理该动作的权限,不能让普通用户替管理员批准权限变更,也不能让 Agent 自己模拟一个审批。

function canApprove(user: User, request: ApprovalRequest) {
  return (
    user.roles.some((role) => request.allowedRoles.includes(role)) &&
    !user.id.startsWith('agent-') &&
    request.tenantId === user.tenantId
  )
}

对于高影响动作,可以要求双人审批、职责分离或特定角色确认。审批人和执行人是否可以是同一个人,要按风险决定。所有授权判断都必须在服务端完成,不能相信前端传来的 approved: true

六、审批状态要进入任务状态机

审批不能只是一个弹窗状态。任务需要明确记录:

running → waiting_approval
waiting_approval → approved → executing → succeeded
                 ├─→ rejected
                 └─→ expired
type ApprovalState =
  | 'pending'
  | 'approved'
  | 'rejected'
  | 'expired'
  | 'revoked'
  | 'executing'
  | 'completed'

用户拒绝后,Agent 应该停止当前高风险动作,或者根据产品规则生成替代计划;不能默默换一种方式执行同样的事情。审批过期后需要重新检查证据和参数,不能沿用旧批准。

七、等待期间也要处理取消和变化

用户可能在审批等待时取消任务,资源可能被别人修改,权限也可能被撤销。执行前必须重新检查:

  • 审批是否仍然有效;
  • 当前用户权限是否仍然存在;
  • 目标资源版本是否一致;
  • 风险评估是否发生变化;
  • 幂等键是否已经执行过。
async function executeApproved(request: ApprovalRequest) {
  assertApprovalIsValid(request)
  await assertResourceVersion(request.target, request.targetVersion)
  await assertPermission(request.approver, request.action)
  return tool.execute({
    ...request.parameters,
    idempotencyKey: request.approvalId,
  })
}

“用户刚刚确认过”不是永久权限。确认只对一组具体、短期有效的参数负责。

八、审批和自动化要有分层策略

不是所有动作都需要人工。低风险、可逆、影响范围小的动作可以自动完成;中风险动作可以批量确认;高风险动作需要逐项确认或双人审批。

低风险:读取、搜索、生成草稿 → 自动
中风险:修改少量内部数据 → 用户确认
高风险:付款、删除、公开发布 → 强确认 / 双人审批

分层能避免人工确认疲劳。如果 Agent 每次读取一个文件都弹窗,用户最终会习惯性点击确认,真正危险的动作反而得不到注意。把人的注意力留给不可逆和高影响节点,协作才有效。

九、审计记录要能还原决定

审批记录至少包含请求内容、预览版本、审批人、时间、来源设备、执行结果和失败原因:

type ApprovalAudit = {
  approvalId: string
  taskId: string
  requestedBy: string
  approvedBy?: string
  actionHash: string
  decision: 'approved' | 'rejected' | 'expired'
  decidedAt?: string
  executedAt?: string
  result?: string
}

审计日志本身也要保护,不能让普通用户看到其他租户的审批内容。高敏感参数可以脱敏,但要保留足够信息让授权人员复核。发生争议时,团队应该能回答“用户当时看到了什么、确认了什么、系统最终执行了什么”。

十、用故障和误操作测试审批流程

不要只测用户点击确认的顺利路径。至少测试:

  • 用户确认后参数发生变化;
  • 两个审批人同时操作;
  • 审批链接过期;
  • 用户权限在等待期间被撤销;
  • 工具执行超时但外部动作可能已发生;
  • Agent 在被拒绝后尝试绕过审批;
  • 客户端重复提交确认。

这些测试要验证状态机、幂等、权限和审计是否一致。一个“确认按钮点了两次却发了两封邮件”的问题,本质不是前端防抖,而是审批执行没有幂等保护。

总结:把决定权交给人,也要把信息交给人

Human-in-the-loop 最容易被做成“模型说要做,人点一下就行”。真正有效的人机协作需要更严格:先分级风险,再生成清晰计划;审批绑定具体参数和版本;执行前重新检查权限、资源和幂等;状态可过期、可拒绝、可撤销;最终留下完整审计。

我现在看到一个“需要人工确认”的需求,会继续问:人到底确认了什么?影响范围是否看得见?等待期间变化怎么办?拒绝以后 Agent 会不会换路绕过?执行结果能否证明和预览一致?

人不是自动化系统的橡皮图章。好的 Agent 会把复杂信息整理成可以判断的选项,把低风险重复劳动自动完成,把真正不可逆的决定交还给有权限的人。确认不是为了让系统免责,而是为了让人拥有知情、选择和停止的能力。

🤪 您也可以编辑此页: