AI 学习笔记(二十八):LLM policy-as-code enforcement 最小实践
上一篇我们把 formalize / sunset / convergence 讲清楚了。
但线上治理往前走一步,马上会撞到一个更现实的问题:
规则都写在文档里,事故却还是按老路发生。
常见场景很熟:
- 某次模型切换明明要求补评估报告,PR 还是直接合了
- 某条高风险工具白名单明明只允许单租户灰度,发布时却被带进全局配置
- 某个成本上限明明写在 runbook 里,批任务一扩容,账单还是先飞出去
这时候真正缺的,是把制度变成机器会执行的东西。
这篇继续沿着生产运维主线往下推,聊一件很多团队都拖得太久的事:
把 LLM 治理规则写成 policy as code,让提交、发布、运行时都能按同一套条件做决策。
1. formalize 之后,为什么系统还是容易失控
很多团队做到 formalize 这一步,就以为治理已经落地了。
实际没有。
因为 formalize 解决的是“规则有没有被说清楚”,不是“规则有没有被执行”。
只要下面三种情况还在,治理就还是半成品:
- 审批条件藏在文档和聊天记录里,机器看不见
- 发布闸门和运行时只看技术状态,不看治理状态
- 例外单、评估报告、成本阈值、权限边界分散在不同系统,没有统一输入
人肉检查在小团队、低频发布时还能勉强撑住。
一旦进入多模型、多环境、多团队并行,问题会变得很直接:
- 同一条规则今天拦得住,明天拦不住
- 值班同学知道应该卡住,但没有硬阻断点
- 事后复盘能指出制度缺口,事前链路却没有任何自动保护
我更愿意把这类问题叫“治理断路”。
规则在纸面成立,执行链路里却断了。
2. 什么东西该优先写成 policy
别一上来就想把所有治理都塞进策略引擎。
那样十有八九会做成一个没人愿意维护的平台。
更稳的做法,是只挑高频、可结构化、会直接影响风险面的规则先写。
我建议 LLM 系统先抓下面四类:
1) 变更准入条件
- 哪类模型切换必须补 eval
- 哪类 prompt guard 变更必须二次审批
- 哪类工具权限扩容不能直接合并
2) 发布闸门条件
- 是否存在未过期但未获批的例外
- 是否存在核心评估指标回退超过阈值
- 是否存在成本增幅、延迟增幅、错误率增幅超线
3) 运行时执行条件
- 哪类租户允许调用高风险工具
- 哪些环境允许联网、写库、外发消息
- 哪些 fallback 路由只能在事故窗口打开
4) 追责与证据条件
- owner 是否明确
- waiver / exception 是否存在且未过期
- 发布记录、审批记录、评估记录是否齐全
这里有个判断标准很好用:
只要某条规则一旦失守,就可能把风险直接带进生产,它就值得优先写成 policy。
3. 不要先选引擎,先把输入和输出钉死
很多人一提 policy as code,脑子里马上跳到 OPA、Cedar、Gatekeeper。
这些工具当然有用,但真正常见的失败点不在引擎,而在输入太乱。
你至少要先把三件事说清楚:
- 输入是什么
是一次 PR?一次发布申请?一次运行时调用?还是一次例外续期? - 输出是什么
是allow / deny,还是allow with obligations? - 证据在哪里
owner、评估报告、例外单、成本预算、环境标签,分别从哪来?
如果这三件事没钉住,再强的策略引擎也会退化成“帮你执行模糊规则”。
下面给一个最小数据模型示例。
字段不需要一开始就很全,关键是让策略输入尽量结构化、少靠隐含上下文。
1 | type Stage = 'pull_request' | 'release' | 'runtime'; |
这个模型有三个工程好处:
- 条件显式,出了问题能回放
deny和allow with obligations能分开- 输入结构稳定后,后面换引擎成本没那么高
4. 三个 enforcement 点里,发布闸门最先做
很多团队第一反应是把策略塞进运行时。
我不太建议一开始这么干。
原因很简单:运行时最敏感、回滚最难、误伤成本最高。
真正适合起步的,通常是这三个层次。
层 A:PR / 配置准入
这层适合拦:
- 缺 owner 的高影响变更
- 没有评估附件的模型替换
- 越过模板约束的工具权限扩容
它的价值在于把明显不合规的东西挡在 merge 之前。
但它不适合承接所有业务判断,因为很多信息要到发布前才完整。
层 B:发布闸门
这是我最建议优先落地的地方。
因为发布时你通常已经拿得到比较完整的上下文:
- 本轮变更范围
- 评估结果
- 是否存在活动例外
- 成本和容量预算
- 目标环境与租户范围
这时 policy 的输出也最容易转成硬动作:
- 允许发布
- 阻断发布
- 允许灰度但要求人工确认
层 C:运行时调用
运行时策略很有价值,但应该晚一点做。
它更适合处理:
- 工具调用白名单
- 网络、文件、外发通道限制
- 特定租户或环境的高风险能力开关
别把还没在发布阶段说清楚的规则,直接扔给运行时兜底。
那样多半会把事故从“错误上线”换成“线上随机拒绝”。
5. 真正有用的 policy,不只会说 deny
只会 allow / deny 的策略很快会逼团队绕过系统。
因为现实发布并不是非黑即白。
我更推荐把决策拆成三类:
allowdenyallow with obligations
第三类特别关键。
例如下面这些情况,直接一刀切并不划算:
- 成本涨幅超阈值,但只是灰度到 5% 租户
- eval 有轻微回退,但已补人工确认和回滚计划
- 高风险工具要开放给指定 workspace,且例外单仍在有效期
这时更合理的动作是:
- 要求补成本评审
- 要求缩小发布范围
- 要求补充事故回退 owner
- 要求写入到期时间和复盘任务
换句话说,policy 不只是“卡住一切”,它还要能把剩余风险翻译成明确义务。
6. 一周最小落地顺序
如果你现在系统里还没有成熟策略引擎,也不用把事情搞太大。
一周内做出第一版完全够用。
Day 1
盘点三类高风险变更:
- 模型切换
- 工具权限扩容
- prompt guard / fallback 路由变更
Day 2
给这三类变更定义统一输入结构:
- owner
- scope
- eval 结果
- 成本变化
- 例外单 ID
Day 3
先在 CI 或发布脚本里跑 dry-run,只输出决策,不阻断。
Day 4
把两三条最确定的规则改成硬阻断:
- global change 无 owner
- 高风险变更无有效例外
- 核心评估指标跌破红线
Day 5
把 allow with obligations 接进发布面板、发布记录或工单系统。
别让“允许但有条件”停在控制台日志里。
Day 6 - Day 7
复盘一轮误判和漏判,再决定要不要引入专门策略引擎。
这样做的好处很实际:
- 先把规则跑起来
- 先积累真实输入样本
- 再决定规则写在 TypeScript、Rego 还是 Cedar 里
7. 三个常见误区
误区 A:把 policy 写成新的审批系统
策略代码负责做条件判断,不负责承载全部流程编排。
审批、工单、告警、审计可以接进来,但别把 policy engine 本身做成大一统平台。
误区 B:策略只看配置,不看风险上下文
同样一条模型变更,影响 1 个租户和影响全站,不该用同一条规则直接拍板。
scope、owner、例外状态、回滚能力,这些上下文必须一起进输入。
误区 C:只拦上线,不看运行时漂移
你把发布拦住了,不代表系统以后不会漂。
高风险工具、代理配置、预算阈值、例外有效期,还是要做周期性校验和 runtime spot check。
8. 冷思考:policy as code 的价值,在于把执行做一致
很多团队一听到 policy as code,会本能觉得这件事只会让发布更慢。
我倒觉得,真正的价值不在“更严”,而在“更一致”。
同一条规则:
- 不该今天靠资深同学拦住,明天换个值班人就放过去
- 不该在 A 团队靠经验执行,在 B 团队靠运气执行
- 不该每次事故后都说“其实文档里写过”
只要系统已经进入多团队、多环境、多模型并行,治理最怕的不是某一条规则太弱,
而是同一条规则在不同链路里表现不一致。
policy as code 干的,就是把这种不一致压掉。
总结
做到 formalize 之后,下一步该做的是把关键规则拉进执行链路。
这件事我会建议从三条最小原则开始:
- 先把输入和输出结构化
- 先把发布闸门做成第一落点
- 先支持
allow with obligations,别只会一刀切
当模型切换、工具权限、例外状态、成本阈值都能被同一套 policy 解释时,
治理才算真的从“会开会”走到“会执行”。