人工审批与高风险操作防护:给 Agent 加一道安全闸门
上一篇实现了有状态、有限步数的单 Agent 循环,但“能执行”不等于“应该执行”。查询天气失败,通常只是一次普通错误;删除数据、发送邮件、修改权限等操作,则可能造成不可逆的业务影响。只要 Agent 能调用工具,就应该把高风险操作从普通自动执行路径中分离出来。本篇只聚焦一个核心问题:如何在工具真正执行前增加人工审批闸门。
为什么需要人工审批
模型输出本质上是不可信的建议。即使提示词写着“发送前请确认”,模型仍可能误解上下文、选错工具,或者被用户输入中的诱导内容影响。程序可以检查参数是否符合类型,却无法单独判断一次操作是否符合当前业务意图。
因此,工具执行至少要经过三层判断:工具是否在白名单中,参数是否通过校验,以及这次操作是否需要审批。查询只读数据可以自动执行;写入、外发和删除等有副作用的操作,默认应暂停,等待明确的人工决定。审批不是让人重新完成全部工作,而是让人确认“谁要对什么目标做什么事”。
先给工具声明风险等级
不要根据工具名称猜风险,也不要让模型返回一个 approved 字段就直接相信。风险策略应由应用代码维护,并和具体工具绑定。下面定义一个最小工具注册表:
1 | from dataclasses import dataclass |
这里的函数只返回示例文本,不会访问真实系统,因此可以离线运行。真正的删除函数仍应在内部做权限和资源归属检查;风险等级只是调度策略,不能代替业务授权。
把审批请求做成明确的数据
审批界面或命令行提示必须展示可核对的信息,而不是只显示“Agent 想继续吗”。最小审批请求应包括工具名、经过校验的参数、风险级别和请求人。审批函数返回布尔值,拒绝或无法得到确认时都按拒绝处理:
1 | def request_approval( |
生产系统不应把任意敏感参数原样打印到日志或审批页面,例如访问令牌和完整个人信息应脱敏。更重要的是,展示给审批人的参数必须是程序校验后的版本,不能直接复用模型的原始 JSON。否则审批人确认的内容,可能和最后执行的内容不一致。
在执行器中设置闸门
下面的执行函数模拟 Agent 已经提出一个动作。它先验证动作结构,再查白名单,最后按风险决定是否审批。顺序不能颠倒:未知工具不应该进入审批流程,未通过参数校验的动作也不应该让人替它背书。
1 | def validate_arguments(name: str, arguments: Any) -> dict[str, Any]: |
运行文件后,输入 APPROVE 才会调用删除函数;输入其他内容、直接回车或审批函数发生异常,都不会执行高风险工具。示例中的“删除”只是打印文本,所以不会改变文件或数据库。这个可运行的离线例子验证的是控制流程,而不是模拟真实删除结果。
审批还要防哪些绕过
第一,审批状态要绑定一次具体操作。不要让一次“同意删除草稿”永久授权同类操作,也不要只按工具名缓存批准结果;参数、用户、资源和有效期都应参与绑定。第二,执行前再次检查权限和资源状态。审批等待期间,用户权限可能变化,目标资源也可能已经被别人修改。
第三,设置超时和幂等策略。审批过期就拒绝,网络重试不能导致邮件重复发送或扣款重复发生。对于不可逆操作,最好采用“预览—审批—执行”的两阶段流程,并保留请求 ID、审批人、审批时间、决策和执行结果,便于审计。日志需要脱敏,但不能因为脱敏而丢失定位一次操作所需的关联信息。
第四,审批人不应被模型生成的文字催促或替代。审批页面应把事实字段结构化展示,例如目标资源、变更前后值和影响范围;模型生成的解释只能作为辅助说明。高风险场景还可以要求双人审批、限制可审批角色,或完全禁止 Agent 自动发起某些操作。
常见问题
只在提示词中要求确认可以吗? 不可以。提示词是行为指导,不是安全边界。真正的拦截必须位于工具执行前的程序路径中。
读操作是否永远不需要审批? 不是。读取个人资料、密钥或大范围数据也可能是高风险操作,应按数据敏感度和访问者权限单独分级。
模型说已经获得批准,能否跳过询问? 不能。批准必须来自应用信任的审批渠道,并关联到当前动作,模型输出只能携带请求,不能产生授权。
审批后还要校验参数吗? 要。审批确认的是结构化、规范化后的参数;执行前仍应做最终权限、状态和业务规则检查。
小结
人工审批的核心不是增加一个 input,而是建立不可绕过的职责边界:模型提出动作,程序验证动作,审批人确认高风险意图,工具最后执行。用工具注册表声明风险、用白名单限制能力、用规范化参数生成审批摘要,并让拒绝和超时默认安全,才能把 Agent 从“自动操作脚本”变成可控的应用组件。下一篇可以继续研究日志、Tracing 与可观测性,让每次决策和审批都能被定位与复盘。