现代大语言模型做根因分析时,真正的瓶颈可能已经不再是“模型会不会推理”,而是“系统能不能把正确的上下文送到模型面前”。当日志、指标、链路、变更记录和服务拓扑彼此割裂时,再强的模型也只能在不完整的证据上做猜测。
Coroot 围绕这一观点进行了覆盖 11 个模型的实验,为“模型已经具备相当的根因分析能力,工程难点转向遥测数据关联”提供了早期证据。这并不意味着模型可以脱离工程系统自动排障,而是说明排障平台的核心价值正在从单纯选择模型,转向构建高质量、可验证、时间一致的上下文。
模型推理之外的真正难题
一次生产故障通常同时留下多种信号:
- 指标显示错误率上升或延迟抖动;
- 日志记录异常堆栈、超时和重试;
- 分布式链路揭示请求在哪个服务之间变慢;
- 服务拓扑说明依赖关系和影响范围;
- 部署、配置或数据库变更提供时间上的因果线索。
这些数据的难点不在于“能否被模型读取”,而在于能否被整理成同一个故障叙事。时间窗口不一致会让模型把旧事件当成当前原因;缺少拓扑关系会让它无法区分故障源和受影响服务;原始日志过多则会稀释真正有价值的异常。
因此,根因分析系统至少要解决三类上下文问题:
- 相关性:把同一请求、服务、主机、集群和时间窗口内的信号关联起来。
- 压缩性:从海量遥测中提炼异常摘要,而不是把所有日志直接塞进提示词。
- 可验证性:让每个结论都能回指到指标、日志、链路或变更证据。
上下文工程应该长什么样
可以把 AI 排障输入看成一个经过加工的证据包,而不是一段随手拼接的文本。一个实用的证据包通常包含:
- 故障时间窗口,以及相邻的基线窗口;
- 受影响服务、接口和区域;
- 关键指标的变化方向与幅度;
- 按服务聚合后的错误类型和代表性日志;
- 典型慢请求的链路片段;
- 故障前后的部署、配置和依赖变更;
- 已排除的假设,以及仍然缺少的证据。
这里的关键不是让提示词越来越长,而是让上下文拥有明确的结构。模型需要知道哪些内容是观测事实,哪些内容是系统计算出的相关性,哪些内容只是待验证的假设。
例如,可以采用下面这种输入约定:
角色:生产故障分析器
时间窗口:2025-01-15T10:00:00Z 至 2025-01-15T10:15:00Z
影响范围:checkout-api,us-east,HTTP 5xx 上升
观测事实:
- checkout-api 的 5xx 从 0.2% 上升到 18.4%
- p95 延迟从 240ms 上升到 3.8s
- 62% 的失败请求包含 payment-client timeout
关联证据:
- payment-service 在同一时间窗口出现连接池耗尽
- checkout-api 在故障前 4 分钟完成了 payment-client 配置变更
请输出:
1. 最可能的根因及置信度
2. 支持该判断的证据
3. 仍需验证的假设
4. 风险最低的下一步检查
约束:不要把相关服务自动视为根因;每个结论必须引用上面的证据。
这种格式可以降低模型把推测写成事实的概率,也便于后续把回答转换成事件记录、工单或自动化检查。
一个可运行的上下文构建示例
下面的 Python 示例不调用具体厂商的 LLM API,而是演示一个可以接入现有遥测查询系统的最小上下文构建器。它会把指标、日志、链路和变更统一成结构化证据包,再生成可发送给模型的请求正文。运行前只需替换示例数据,或把 load_* 函数接到 Prometheus、日志平台和链路系统。
import json
from datetime import datetime, timezone
def load_metrics():
return {
"checkout-api": {
"error_rate_before": 0.002,
"error_rate_now": 0.184,
"p95_ms_before": 240,
"p95_ms_now": 3800,
}
}
def load_logs():
return [
{
"service": "checkout-api",
"level": "ERROR",
"count": 428,
"pattern": "payment-client timeout",
},
{
"service": "payment-service",
"level": "ERROR",
"count": 391,
"pattern": "connection pool exhausted",
},
]
def load_traces():
return [
{
"trace_id": "trace-001",
"path": ["checkout-api", "payment-service"],
"duration_ms": 7400,
"error": "upstream timeout",
}
]
def load_changes():
return [
{
"service": "checkout-api",
"kind": "config",
"summary": "payment client connection timeout changed",
"minutes_before_incident": 4,
}
]
def build_context(start, end):
context = {
"incident_window": {"start": start, "end": end},
"metrics": load_metrics(),
"logs": load_logs(),
"traces": load_traces(),
"changes": load_changes(),
"instructions": [
"Separate observed facts from hypotheses.",
"Cite evidence for every root-cause claim.",
"List the next lowest-risk verification step.",
],
}
return json.dumps(context, ensure_ascii=False, indent=2)
if __name__ == "__main__":
now = datetime.now(timezone.utc)
start = "2025-01-15T10:00:00Z"
end = "2025-01-15T10:15:00Z"
print(build_context(start, end))
在真实系统中,build_context 前面通常还需要一层过滤和排序逻辑,例如限制每个服务只保留高频异常模式、为链路错误保留代表性样本、按照故障时间对变更排序,并设置总 token 或字节预算。上下文工程不是把更多数据交给模型,而是在预算内保留最能区分候选根因的证据。
从“给答案”转向“给证据链”
根因分析的输出也需要改变。一个只有结论的回答很难用于生产决策;更有价值的结果应该同时说明:
- 哪个组件最可能是故障源;
- 哪些观测支持这一判断;
- 哪些证据与该判断冲突;
- 当前结论的置信度;
- 如何用一次低风险查询或操作验证它;
- 如果假设成立,影响范围和修复方向是什么。
这要求排障平台把模型回答视为分析草稿,而不是最终事实。自动执行动作前,仍应保留权限控制、变更审批和人工确认,尤其是涉及回滚、扩容、流量切换或数据修复的场景。
同时,相关性算法本身也必须接受检验。时间上的相邻不等于因果关系,服务调用链上的下游异常也可能只是上游故障的受害者。系统可以让模型比较多个候选假设,但不应把模型生成的解释当作新的遥测事实写回数据源。
落地时的检查清单
可以从一个窄场景开始,例如只分析 HTTP 5xx 和延迟异常,再逐步加入链路、变更和依赖拓扑。上线前重点检查以下事项:
- 所有数据是否使用统一时区和明确的事件时间;
- 每条证据是否包含服务、时间和来源;
- 是否区分观测事实、计算结论与模型假设;
- 是否对日志、链路样本和 token 数量设置预算;
- 是否能从模型结论跳回原始遥测;
- 是否记录模型版本、提示词版本和上下文快照;
- 是否用历史故障回放评估准确性,而不只看回答是否流畅;
- 是否为高风险自动化动作保留审批边界。
Coroot 对 11 个模型的实验只是早期信号,不能单独证明所有模型都能可靠完成生产级排障。但它提示了一个值得采用的工程方向:模型选择仍然重要,真正决定结果质量的往往是遥测关联、上下文组织和证据验证。对团队而言,下一步不一定是立刻换更大的模型,而是先检查一次故障分析输入中是否包含了足够完整、准确且可追溯的事实。