AI 根因分析的瓶颈,正在从模型推理转向上下文工程

2026-07-25 21 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:12 分钟

大模型用于根因分析(RCA)时,一个常见误区是把效果不佳归因于“模型不会推理”。越来越多工程师提出了另一种判断:现代 LLM 已经具备分析故障因果链的基本能力,真正困难的是在调用模型之前,把指标、日志、调用链、部署事件和服务依赖整理成一份准确、紧凑、带时间关系的上下文。

Coroot 针对 11 个模型开展的实验,为这一判断提供了早期证据。它并不意味着 RCA 已经变成一个简单的提示词问题,而是提示团队重新分配工程投入:与其反复寻找“更聪明”的模型,不如先检查遥测关联管道是否向模型交付了足够可靠的事实。

模型看到的不是生产系统,而是一段输入

值班工程师分析事故时,通常会同时查看多类信息:

  • 哪些服务在什么时间出现错误率或延迟突增;
  • 异常请求经过了哪些上游和下游服务;
  • 故障窗口内是否发生部署、配置修改或扩缩容;
  • 日志中的异常是否与指标拐点处于同一时间段;
  • 数据库、消息队列或第三方 API 是否出现资源饱和。

LLM 不会天然获得这些信息。若输入只有一句“支付接口变慢,请分析根因”,模型只能依赖通用经验补齐空白,最终给出数据库慢查询、网络抖动、线程池耗尽等看似合理却无法验证的猜测。

因此,RCA 上下文至少需要保留四种关系:

  1. 时间关系:异常开始、变更发生和恢复发生的时间顺序。
  2. 拓扑关系:服务之间真实的调用方向,而不是一份静态服务清单。
  3. 信号关系:指标峰值、日志错误和异常 trace 是否指向同一组件。
  4. 基线关系:当前值相对正常窗口究竟偏离了多少。

这也是“上下文工程”与简单拼接日志的区别。前者在构建可供推理的证据结构,后者只是在消耗 token。

遥测关联管道才是新的核心组件

一个面向 LLM 的 RCA 管道,可以拆成以下步骤:

Metrics / Logs / Traces / Deploy Events
                  |
                  v
        时间归一化与实体解析
                  |
                  v
         服务拓扑和请求链关联
                  |
                  v
       异常检测、去重与证据排序
                  |
                  v
          RCA Context Package
                  |
                  v
                 LLM

这里最容易出问题的通常不是最后一次模型调用,而是前面的数据处理:服务名在日志中写成 checkout,在指标标签中写成 checkout-api;部署事件使用本地时间,trace 使用 UTC;采样策略恰好丢掉了错误请求;一次连锁故障产生数千条重复异常,挤掉了真正关键的配置变更。

高质量上下文包不必包含全部遥测数据,但应该让每一项重要结论都能追溯到证据。一个实用的数据结构可以包含:

  • 事故窗口与正常基线窗口;
  • 服务依赖图及受影响路径;
  • 按时间排序的关键事件;
  • 每项异常的当前值、基线值和变化幅度;
  • 代表性日志与 trace ID;
  • 已知缺失数据和采样限制。

最后一项尤其重要。告诉模型“该服务没有 trace”与“trace 显示该服务正常”是完全不同的事实。

可以这样实践:生成一个最小 RCA 上下文包

下面是一个可直接运行的 Python 示例。它不依赖真实可观测性平台,而是演示如何把部署事件、指标异常和日志按时间整理成模型可消费的 Markdown。假设各数据源已经统一为 UTC,并且服务名称已经完成映射;接入生产环境时,需要把示例列表替换为 Prometheus、日志平台、追踪系统和部署系统的查询结果。

将代码保存为 build_rca_context.py,然后运行 python build_rca_context.py

from datetime import datetime, timezone

INCIDENT = {
    "start": "2025-03-08T10:00:00Z",
    "end": "2025-03-08T10:20:00Z",
    "symptom": "checkout API p95 latency exceeded 2 seconds",
}

EVENTS = [
    {
        "time": "2025-03-08T09:57:00Z",
        "kind": "deployment",
        "service": "pricing",
        "detail": "version changed from 2.4.1 to 2.5.0",
    },
    {
        "time": "2025-03-08T10:02:00Z",
        "kind": "metric",
        "service": "pricing",
        "detail": "database pool wait p95 rose from 12 ms to 840 ms",
    },
    {
        "time": "2025-03-08T10:03:00Z",
        "kind": "trace",
        "service": "checkout -> pricing",
        "detail": "pricing span accounted for 91% of request latency; trace_id=abc123",
    },
    {
        "time": "2025-03-08T10:04:00Z",
        "kind": "log",
        "service": "pricing",
        "detail": "connection acquisition timeout, 137 occurrences in 5 minutes",
    },
]

DEPENDENCIES = [
    "web -> checkout",
    "checkout -> pricing",
    "pricing -> postgres",
]

LIMITATIONS = [
    "Only 10% of successful requests are traced.",
    "PostgreSQL statement-level telemetry is unavailable.",
]


def parse_time(value: str) -> datetime:
    return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(timezone.utc)


def build_context() -> str:
    ordered = sorted(EVENTS, key=lambda event: parse_time(event["time"]))
    lines = [
        "# Incident context",
        "",
        f"- Window: {INCIDENT['start']} to {INCIDENT['end']}",
        f"- User-visible symptom: {INCIDENT['symptom']}",
        "",
        "## Service dependencies",
        *[f"- {item}" for item in DEPENDENCIES],
        "",
        "## Correlated timeline",
    ]

    for event in ordered:
        lines.append(
            f"- {event['time']} | {event['kind']} | "
            f"{event['service']} | {event['detail']}"
        )

    lines.extend(["", "## Data limitations"])
    lines.extend(f"- {item}" for item in LIMITATIONS)
    return "\n".join(lines)


if __name__ == "__main__":
    print(build_context())

生成上下文后,可以把它与下面的提示词一起提交给所选模型:

你是一名生产事故分析工程师。仅根据提供的事故上下文完成分析。

请输出:
1. 最可能的根因,并引用对应的时间、服务和遥测证据;
2. 从根因到用户症状的因果链;
3. 两个仍然合理的替代解释;
4. 为确认或排除每个解释需要执行的查询;
5. 当前证据的缺口。

规则:
- 不要把时间相关性直接表述为因果关系。
- 没有证据时明确写“未知”。
- 将结论分为“已观察事实”“推断”和“待验证假设”。

这个例子刻意要求模型输出替代解释和验证查询。RCA 的目标不应只是生成一个听起来确定的答案,而是压缩调查范围,并给值班人员提供下一步可执行动作。

评估时别只看答案是否命中

Coroot 的多模型实验可以视为早期信号,但团队在自己的系统中仍需要建立评测集。生产故障类型、遥测完整度和服务拓扑差异很大,一个模型在公开或预设案例中表现良好,不代表它能处理本地环境中的命名混乱与数据缺失。

建议用历史事故复盘构造测试样本,并分别衡量:

  • 根因候选是否覆盖复盘确认的原因;
  • 引用的证据是否真实存在,时间是否一致;
  • 模型有没有把相关性夸大为因果性;
  • 缺少关键遥测时,是否明确表达不确定性;
  • 建议的验证命令是否可执行、是否可能扩大事故;
  • 更换模型后,结果改善来自推理能力还是上下文长度变化。

还应保留人工审批边界。模型可以建议回滚、重启或调整连接池,但不应仅凭概率性判断直接执行高风险操作。上下文中也可能包含密钥、用户数据和内部拓扑,进入外部模型前必须完成脱敏和访问控制。

采用前的工程检查表

把 LLM 接入 RCA 流程之前,可以检查以下问题:

  • 所有遥测源是否使用统一时间和稳定的服务标识;
  • 是否能将指标、日志和 trace 关联到同一事故窗口;
  • 部署与配置变更是否进入时间线;
  • 上下文是否包含正常基线,而不只是异常值;
  • 是否标记采样率、数据缺口和查询失败;
  • 模型输出能否回链到原始证据;
  • 自动化动作是否设置权限、审批和回滚机制;
  • 是否用历史事故持续回归测试上下文管道与模型。

AI 根因分析的竞争重点,可能不再只是模型排行榜上的推理分数。决定系统是否可信的,往往是模型调用之前那条不显眼的管道:它能否把分散的遥测转换成时间一致、拓扑清楚、证据可追溯的事故叙述。模型负责提出解释,而上下文工程决定这些解释有没有事实基础。


相关推荐