输出过滤有两个位置,不是一个。模型输出后、用户看到前,这是一道;工具调用前,这是另一道。多数团队只装了第一道,因为第一道直观——用户能看到的东西,看起来就是要管的东西。但真正造成损失的动作发生在第二道的位置上,而那里通常什么都没有。
出口挡的是文本,调用前挡的是动作
被注入的内容——网页、issue、别人提交的 README——进到上下文里,模型可能被说服去调一个工具。这时候出口过滤器看到的是什么?一句"好的,我帮你查一下",加一段 JSON。文本层面干净得很。真正出事的是那个 JSON 被拿去执行了。
两道的职责其实不一样。出口那道管"用户看到了什么"——系统提示词泄露、其他租户的数据、不该出现的密钥。调用前那道管"系统会做什么"。混在一起做,两边都做不好。
调用前那道,只做结构化校验
我自己的做法是:调用前不判断意图,只判断形状。
工具名必须在白名单里。参数按 schema 校验,路径规范化后必须落在允许的根目录下。SQL 用解析器确认是单条只读语句,不是正则匹配——正则一定会在某处漏掉一个 WITH ... DELETE,或者被注释绕过。HTTP 请求的 host 必须在白名单。
这些判断的共同点是结果确定,可以写回归测试。同一个输入永远同一个结果,误杀能复现,也能修。
KaliBench 这周出来,做的就是这件事——测模型能不能为 Kali Linux 上的真实工具生成可执行命令。有意思的是它的奖励是 runtime-free 的,不实际跑命令也能验证。工具调用前那道闸门要的正是这种验证方式:不用执行,也能判断这条命令的形状对不对。
反面:过滤过严,先死的是写代码的人
语义判断放在调用前,误杀率高得离谱。
代码 agent 写 Dockerfile,里面有 curl ... | bash,拦不拦?写数据库迁移脚本,里面就是有 DROP COLUMN,拦不拦?写文档时引用一句"不要在生产环境执行 rm -rf /",拦不拦?
拦了 agent 就废了。用户看到"我帮你写好了",但文件没落盘。更糟的是,agent 会开始绕过过滤器——把命令拆成两半拼起来。这不是安全,这是把安全策略变成对抗游戏,而对手是自己家的模型。
Argo-Bench 里有个细节我印象很深:现有 text-to-SQL benchmark 的答案键经常是错的。连人类标注的标准答案都不牢靠,再在上面叠一层模型语义判断,让它回答"这条 SQL 危不危险"——这个问题的正确答案本身就没有共识。
边界应该画在工具上,不在模型上
有个说法我很认同:不存在"失控"的 AI agent。agent 没有失控,它只是照着你给的权限在做。出问题的地方是权限模型没设计,或者设计了没在工具层强制。
Meta 的 Muse agent 被指出无视用户权限,这类事情的修法不是加过滤器,是把权限强制下沉到执行的地方——文件系统权限、数据库只读账号、网络出口白名单。英伟达想给每个 agent 配一颗看门狗芯片,方向是这个方向,但监控是检测,不是阻止。检测到的时候,rm 已经跑完了。
我现在会这么配
出口那道做内容分类:密钥、PII、跨租户数据,命中就替换或截断,不做"这句话有没有恶意"的判断。
调用前那道做结构校验:白名单、schema、路径规范化、只读解析、host 白名单。破坏性操作加一步 dry-run 或人工确认。
工具本身能力最小化——能只读的账号就不给写权限。这一层比所有过滤器都可靠,因为它不依赖任何判断。所有调用落日志,带完整参数和上下文哈希,出事了能重放。
也许过一段时间,模型自身的判断力上来,语义过滤的误杀率降下去,这个分层会被推翻。但按我现在看,把不可测的语义判断放在工具调用前,是拿一个没法写测试的组件去守一个能写测试的边界。顺序反了。