LLM 监控不应依赖阈值告警,而应使用边缘触发(edge-triggered)模式——仅在状态持续异常时才通知。关键是区分「页面告警」和「仪表盘监控」,将危害用户且人类能在几分钟内处理的情况才纳入页面策略。
边缘触发的告警在状态转换时触发:延迟超过 3 秒、错误率飙升。写起来很简单,这也是为什么你的手机在凌晨 3 点会因为一个在 40 秒内就自行恢复的状况而响个不停。状态触发的告警问的是另一个问题——系统当前是否处于不良状态,并且已经持续了足够长的时间以至于需要关注?
具体来说,两者的区别在于:告警条件是在一个时间窗口内进行评估的,描述的是一种持续状态,只有当状态恢复时才会解除告警,而不是在有人确认后就解除。下面的每条规则都是这种形式。任何在单次采集时就触发的东西都不应该出现在分页策略中;应该放到仪表盘里。
这个可靠的划分直接来自常规 SRE 实践,而且完全适用于此:
当用户正在遭受伤害、且人类能够在几分钟内采取行动时触发分页。这是一份简短的清单:功能正在失败、功能慢到不可用、或者钱正以非计划的速率从账户流出。
当某些东西出现降级、趋势变坏、或会在几天内反噬时创建工单。重试率上升。一个提供商比平时慢、而故障转移正在吸收这个情况。归属覆盖率下滑。
除此以外的情况都不需要处理。如果没有人会据此采取行动,那它就是一条图表。
对于 LLM 特性来说,这种区分比普通服务更重要,因为太多有趣的信号都是原因:提供商的 429 率、回退率、缓存命中率下降。如果故障转移正在工作,这些都不会对用户可见,也不应该唤醒任何人。它们正是你希望在早间工单队列中看到的东西。
标准设计——在 Google 的《网站可靠性工作手册》关于 SLO 告警的章节中有描述——是根据你消耗错误预算的速度来告警,而不是根据原始速率。Burn rate 为 1 意味着你将在周期结束时刚好耗尽预算;一小时内的 burn rate 为 14.4 意味着你在一小时内已经消耗了 30 天预算的 2%。
每条规则有两个时间窗口,一个长窗口用于信号,一个短窗口用于让告警能够迅速解除。对于 30 天的 SLO 窗口,标准的阶梯是:
要求两个窗口同时突破才能触发告警,这正是消除误报的原因:两分钟的短暂波动不会撼动一小时的窗口,而一个已解决的事故会立即清除五分钟的窗口,而不会让告警持续挂起一小时。
这种设计的价值在于:所有四条规则都采用同一个参数——来自你 SLO 的错误预算——因此收紧或放宽目标会连贯地调整每个阈值。对比一下手工调整的阈值集合,改变目标意味着重新审视每条规则,并希望它们彼此保持一致。
它还能以绝对阈值无法做到的方式随流量合理扩展。每分钟处理 10 个请求的特性出现 5% 错误率通常是噪声;每分钟处理 10,000 个请求时出现 5% 错误率则是一次故障。Burn rate 将两者表达为同一数量,因此同一规则对新功能和老功能都适用,不需要有人在量级增长时记得重新调参。
# PromQL, for an SLO of 99.5% successful LLM requests.
# error_budget = 1 - 0.995 = 0.005
- alert: LLMFastBurn
expr: |
(
sum(rate(llm_requests_total{status="error"}[1h]))
/ sum(rate(llm_requests_total[1h])) > (14.4 * 0.005)
)
and
(
sum(rate(llm_requests_total{status="error"}[5m]))
/ sum(rate(llm_requests_total[5m])) > (14.4 * 0.005)
)
for: 2m
labels: { severity: page }
- alert: LLMSlowBurn
expr: |
(
sum(rate(llm_requests_total{status="error"}[3d]))
/ sum(rate(llm_requests_total[3d])) > (1 * 0.005)
)
and
(
sum(rate(llm_requests_total{status="error"}[6h]))
/ sum(rate(llm_requests_total[6h])) > (1 * 0.005)
)
for: 1h
labels: { severity: ticket }
除了你已经对其他所有服务都在告警的可用性和延迟之外,有四种情况是模型支撑特性所特有的,确实值得唤醒某人。
花费速率。一个失控的 Agent 循环或一个突然检索十倍上下文的 Prompt 可以在几小时内让账单翻倍,而你的监控中的其他任何东西都不会注意到这一点。根据你设定的上限对滚动小时花费进行告警——一个绝对数字,而不是百分比,因为基于小基线的百分比会不断触发告警。为它配上一个无需人工介入即可行动的硬性花费上限。
结构化输出有效性。如果下游系统会解析模型的输出,那么 Schema 验证失败率就是一种返回 HTTP 200 的面向用户的错误率。它属于 SLI,因此也属于上面的 burn-rate 规则。
没有回退可用时的完整提供商故障。故障转移耗尽与故障转移正在发生是不同的。前者需要分页;后者只需要工单。要明确区分它们,否则你会在每次常规提供商波动时都收到分页。
Guardrail 拦截率。一个安全过滤器或验证器开始拒绝大量合法流量时,这是一场从错误率来看像是一个健康服务拒绝应答的完全中断。要同时告警这个速率的两个方向。
还有另一个不花任何成本的纪律:每个告警都需要在其注解中有 runbook 链接,而 runbook 需要说明告警的含义以及首先应该检查什么。没有人知道如何处理的告警会被静音,而静音的告警比没有告警更糟糕,因为它制造了一种"有东西在监控"的假象。
另一个值得养成的习惯是按一定频率审查已触发的告警——每月一次就够了——并对每一个问一个问题:有人据此采取行动了吗?一个触发了 11 次但没有产生任何行动的告警要么是调参不当,要么应该变成工单,删除它是一种真正的改进而不是失去覆盖。告警集合只会单调增长,除非有东西刻意缩减它们,而一个未经修剪的集合的最终状态是:值班人员无视所有告警。
最后,对管道本身进行告警。如果你的指标停止到达,上面每条规则都会在没有数据的情况下评估,然后安静地报告一切正常——这是一种失败模式,仪表盘显示绿色是因为什么都没有在被测量。对请求计数器进行时效性检查,并在它在你认为不应该为零的时段降为零时触发告警,是这篇指南中最便宜的保险。
The Metrics That Matter: A Minimal LLM Dashboard
Incident Response for AI Features