AI 学习笔记(二十九):LLM runtime drift detection 最小实践

上一篇我们把 policy as code 接进了提交、发布、运行时三道闸口。

但真正在生产里跑几周,你会发现另一个现实问题:

上线那一刻是合规的,不代表运行一周后还是合规的。

常见现象非常像:

  1. 发布时工具白名单是租户级,几次紧急变更后变成了全局级
  2. 预算阈值原本按 workspace 控制,后续为了“先恢复服务”被临时放宽后没回收
  3. 一条 fallback 路由只打算事故窗口开启,最后长期在线但没人再复核

这些都不是“发布漏检”,而是运行时漂移(runtime drift)

这篇继续沿着生产运维主线,给出一套最小可落地方案:

定义可检测的治理基线,周期扫描偏差,按风险等级自动处置或升级人工处理。

1. 什么是 runtime drift

在 LLM 系统里,drift 不只指模型效果漂移,还包括治理状态漂移。

我建议把 runtime drift 定义成:

生产实际状态与“可审计基线”之间,持续超过容忍阈值的偏差。

这里的“状态”至少包含四类:

  1. 配置状态:模型版本、工具白名单、策略开关、预算阈值
  2. 权限状态:租户范围、写操作权限、外发渠道权限
  3. 流量状态:路由比例、fallback 启用范围、灰度占比
  4. 证据状态:例外单有效期、owner、审计日志完整度

如果只盯延迟和错误率,会漏掉大量治理退化问题。

2. 先定基线,不要先写扫描器

很多团队一上来就写 cron 扫描,然后很快发现“扫出来很多异常,但不知道哪个是真的问题”。

根因通常是基线定义不清。

我建议先固定一份最小 baseline contract:

  1. 字段清单:哪些字段必须比对
  2. 容忍策略:每个字段允许的偏差范围
  3. 时效窗口:偏差持续多久才算 drift
  4. 处置动作:检测到 drift 后怎么做

例如:

  • tool_scopetenant 漂到 global:高风险,立即阻断高危调用
  • cost_cap_usd 比基线上浮 10% 以内:中风险,告警并要求 24h 内回收
  • exception_expires_at 已过期仍生效:高风险,自动降级或冻结新增发布

只有先写清这张契约,后续检测结果才有稳定解释。

3. 一个最小可执行数据模型

下面这个模型足够支撑第一版 drift 检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
type DriftSeverity = 'low' | 'medium' | 'high';

interface RuntimeSnapshot {
env: 'staging' | 'prod';
toolScope: 'tenant' | 'workspace' | 'global';
fallbackGlobalEnabled: boolean;
costCapUsd: number;
exceptionId: string | null;
exceptionExpiresAt: string | null;
}

interface BaselineSpec {
toolScope: 'tenant' | 'workspace' | 'global';
allowGlobalFallback: boolean;
costCapUsd: number;
costCapTolerancePct: number;
requiresValidException: boolean;
}

interface DriftItem {
field: string;
expected: string | number | boolean | null;
actual: string | number | boolean | null;
severity: DriftSeverity;
reason: string;
}

function detectDrift(snapshot: RuntimeSnapshot, baseline: BaselineSpec, nowIso: string): DriftItem[] {
const drift: DriftItem[] = [];

if (snapshot.toolScope !== baseline.toolScope) {
drift.push({
field: 'toolScope',
expected: baseline.toolScope,
actual: snapshot.toolScope,
severity: snapshot.toolScope === 'global' ? 'high' : 'medium',
reason: 'tool scope diverges from baseline',
});
}

if (snapshot.fallbackGlobalEnabled && !baseline.allowGlobalFallback) {
drift.push({
field: 'fallbackGlobalEnabled',
expected: false,
actual: true,
severity: 'high',
reason: 'global fallback enabled outside approved baseline',
});
}

const maxAllowed = baseline.costCapUsd * (1 + baseline.costCapTolerancePct / 100);
if (snapshot.costCapUsd > maxAllowed) {
drift.push({
field: 'costCapUsd',
expected: `<= ${maxAllowed}`,
actual: snapshot.costCapUsd,
severity: 'medium',
reason: 'cost cap exceeds baseline tolerance',
});
}

if (baseline.requiresValidException) {
const expired = snapshot.exceptionExpiresAt ? snapshot.exceptionExpiresAt < nowIso : true;
if (!snapshot.exceptionId || expired) {
drift.push({
field: 'exception',
expected: 'active exception',
actual: snapshot.exceptionId ?? 'none',
severity: 'high',
reason: 'exception missing or expired',
});
}
}

return drift;
}

关键不是字段多少,而是三件事:

  1. 比对逻辑可回放
  2. 严重级别可解释
  3. 输出能直接映射处置动作

4. 处置策略:先自动收敛高风险,再派单跟进中低风险

drift 检测最怕“只报警不收敛”。

我建议第一版就做分级动作:

高风险 drift

  1. 自动降级到安全默认值(例如关闭全局 fallback)
  2. 触发 on-call 告警并附带变更证据
  3. 阻断高风险发布,直到 owner 确认

中风险 drift

  1. 创建治理工单,要求 24h 内回收
  2. 在发布闸门加入“有条件放行”义务
  3. 超时未处理自动升级高风险

低风险 drift

  1. 进入周报和趋势面板
  2. 连续 N 次出现自动升为中风险

这样才能避免系统进入“告警疲劳 + 风险堆积”。

5. 与现有 policy-as-code 的衔接方式

runtime drift detection 不是替代 policy-as-code,而是补上“上线后”这一段。

建议按这条链路衔接:

  1. Policy 决定基线:发布通过时固化 baseline snapshot
  2. Runtime 周期比对:每 5-15 分钟扫描一次关键字段
  3. Drift 决定动作:按 severity 自动收敛或创建义务
  4. 复盘回写策略:高频 drift 反推 policy 或发布流程修订

这条闭环跑起来后,治理就不是“一次审批”,而是持续执行系统。

6. 一周最小落地顺序

如果你现在还没有专门治理平台,一周内可以先做到这一步:

  1. Day 1:定义 baseline contract(字段、阈值、动作)
  2. Day 2:在发布时落盘 baseline(JSON 即可)
  3. Day 3:实现定时扫描与 drift 输出(先不自动修复)
  4. Day 4:接入高风险自动收敛动作(1-2 条最关键规则)
  5. Day 5:把 drift 工单、告警、发布闸门串起来
  6. Day 6-7:复盘误报漏报,调优阈值与分级标准

先把“可检测 + 可处置”做通,再考虑引入更复杂的策略平台。

7. 常见误区

误区 A:把 drift 当成纯监控问题

drift 是治理问题,不只是可观测性问题。没有 owner 和处置 SLA,监控再多也只是堆日志。

误区 B:阈值一刀切

同样的偏差,在 prod-globalstaging-tenant 的风险级别不同。阈值必须结合 scope。

误区 C:只修当前值,不修来源

如果每次都靠手工回调配置,但不修发布模板、权限模型或例外流程,drift 会反复出现。

总结

policy-as-code 解决的是“发布前如何一致决策”,
runtime drift detection 解决的是“上线后如何持续一致”。

这件事可以从最小闭环开始:

  1. 定义基线契约
  2. 定期检测偏差
  3. 分级自动处置
  4. 把结果回写到策略和流程

当系统能持续发现并收敛治理漂移时,生产稳定性和合规性才会真正进入可控区间。

参考资料

本文永久链接: https://www.mulianju.com/learning-notes/ai-learning-notes-llm-runtime-drift-detection-minimal-practice/