📝 术语别名
  • RCA: Root Cause Analysis, 根因分析, 找到事故的真正起因;
  • OCE: On-Call Engineer, 轮值工程师, 收到告警的那个人;
  • Handler: 一段事先写好的诊断流程, 由 OCE 编写, 系统按 alert 类型匹配执行;
  • Diagnostic Information: handler 收集到的所有运行时证据 (logs / metrics / traces / configs 等);
  • CoT: Chain-of-Thought, 让 LLM 先一步步推理再下结论的 prompt 技巧;
  • In-context Learning / Few-shot: 在 prompt 里给 LLM 几条相似历史例子作为参考;

前置: cloud incident RCA 是个什么问题

云服务一旦出事 (latency 飙高、error rate 飙升、磁盘炸了、配置错了), 监控系统会在毫秒到秒级吐出一条 incident 警报, 然后接手:

  1. OCE 起夜接到 page;
  2. 翻 dashboard, 看 metrics 异常时间段;
  3. 拉日志 (kusto query / KQL / Splunk / grep), 找 error pattern;
  4. 看 trace, 定位到具体哪个 service 哪段调用挂了;
  5. 形成假设, 在系统里验证;
  6. 找到根因, 写报告, 决定 mitigation;

这套流程慢、费眼睛、依赖经验; OCE 资深与否直接决定恢复时间; 而每个团队、每种 alert 又都有自己的诊断套路, 这种隐性知识很难规模化复用 — RCACopilot 想解决的就是这个;

前置: 把 RCA 自动化为什么不容易

直觉上你会想: 把日志和 metrics 都扔给 GPT, 让它推根因不就好了; 实际上不行, 主要三个原因:

  1. LLM 不知道你的 telemetry 系统怎么查: 不同公司用 Kusto/Splunk/Prom/Loki/自研一堆, LLM 没法生成对的查询;
  2. 数据量太大: 一个事故关联的日志可以是 GB 级的, 直接灌 LLM token 预算炸穿;
  3. 诊断顺序也是知识: “先看 CPU 还是先看 GC, 看完不对再切到哪个 region” 这种 SOP 不是从日志能推出来的, 是 OCE 脑里的经验;

所以纯 prompt-engineering 解决不了这事, 必须有一层确定性的诊断流程脚手架, LLM 才能在已经收齐证据的基础上做它擅长的事 — 基于证据归因 + 自然语言解释;

论文定位

RCACopilot 是一套部署在 Microsoft 内部的 on-call 系统, 把 OCE 的诊断 SOP 编码成可执行的 handler, handler 自动跑完后用 LLM 做最后一步根因分类与解释;

设计上的关键判断: 将"取证据"和"做归因"彻底分离, 取证据由人写的 handler 做 (确定性), 做归因由 LLM 做 (概率性); 这是它跟一刀切的端到端 LLM 方案最大的不同;

论文 claim 的最强结果: 在 Microsoft 真实一年的 incident 数据上达到 0.766 根因分类准确率, 而且诊断收集模块 (Stage 1) 在 MS 已经生产部署 4+ 年;

论文解决的核心问题

具体回答的是这几个问题:

  1. 怎么把 OCE 的诊断 SOP 用一种可扩展、可让团队自己写的方式编码下来?
  2. handler 跑完拿到一堆 raw 数据, 怎么压缩成 LLM 能吃的形式?
  3. 怎么让 LLM 借鉴历史相似 incident 的根因经验, 而不是从零推断?
  4. 整套系统怎么部署到 Microsoft 真实生产并跑出工业可接受的准确率?

整体架构: 两阶段四存储

整个系统横向分两阶段, 各有自己的输入输出, 中间用结构化 diagnostic info 过渡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
             Collection Stage                       Prediction Stage
┌───────────────────────────────────┐ ┌───────────────────────────────────┐
│ │ │ │
│ Incident Parsing │ │ Incident Summarization (LLM) │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ Handler Matching ◄─ Load handler │ │ FastText Embedding │
│ │ ┌────────┐ │ │ │ │
│ ▼ │ DB │ │ │ ▼ │
│ Info. Collection ─────► (handler│ │ │ Neighbor Search ◄─ ┌──────────┐ │
│ (handler 三种 action) │ + diag│ │ │ (top-K KNN) │ Embedding│ │
│ │ info) │ │ │ │ │ vector DB│ │
│ └────────┘ │ │ ▼ └──────────┘ │
│ │ │ CoT Prompt → LLM → 根因 + 解释 │
└─────────────────┬─────────────────┘ └─────────────────┬─────────────────┘
│ │
└───────► Diagnostic Information ─────────┘


OCE

注意系统里有两个独立的存储:

  • 左边 Collection Stage 的 DB: 存所有团队写好的 handler 定义, 还存历次跑出来的诊断结果;
  • 右边 Prediction Stage 的 Embedding vector DB: 存历史 incident 的 FastText 向量, 用来做 KNN 检索;

paper 没明说这两个 DB 的具体技术栈, 考虑到部署在 Microsoft, 大概率是 Cosmos DB / Kusto 这种自家服务; vector DB 因为 FastText 300 维 + incident 量级在 10310410^3 \sim 10^4, 实现上可以非常轻, 哪怕是个 in-memory FAISS index 都够;

Stage 1: Diagnostic Information Collection

这一阶段的目的是: 拿到 incident alert 后, 系统化地把所有相关诊断证据按 SOP 收齐;

1.1 Incident Parsing

incident 进系统的第一步是解析关键字段:

  • ID
  • Title
  • OwningTenant / OwningTeam (谁的服务)
  • Severity (优先级)
  • Active 状态

这一步纯字符串处理, 没什么 magic;

1.2 Handler Matching

按 OwningTeam + alert type 在 handler DB 里查到对应的 handler; 一个团队会预先写一组 handler, 每个 handler 对应一种典型 alert (例如 “Latency spike on service X”, “Disk full on storage tier Y”);

📝 关键设计决策

handler 是 OCE 手写, 不是 AI 自动生成; 这是 paper 一个很务实的判断 — 团队自己最清楚要看什么, 不要把这个负担转嫁给 LLM; 而且这样系统永远是可解释、可审计的;

1.3 Information Collection: 三种 action 原语

handler 是一段控制流, 由三类 action 拼接而成:

Action 干嘛 例子
Scope Switching 动态调整调查范围 单 instance 失败 → 看整个 cluster, 单 region 异常 → 看 partner region 对照
Query 调外部系统拉数据, 输出 KV 对 KQL 查日志, Prometheus 查 metrics, 查配置库, 查 trace 系统
Mitigation 直接采取行动 重启服务, 切流量, 升级二线 oncall, 触发 failover
📝 关键设计直觉

Query action 输出的是结构化 KV 对, 不是原始日志倾倒; 这意味着OCE 写 query 的时候已经做了一次粗压缩 — KQL 里的 group by, summarize, take top N 这些操作把原始数据压成了 LLM 友好的形式; paper 没显式讲"日志怎么 summarize", 因为它把这个责任交给了写 handler 的 OCE;

1.4 三种 telemetry 信号在 handler 里的不同处理

这是 paper 没明说但能从设计推出的:

信号 handler 端通常怎么处理 进入 Stage 2 时的形态
Logs KQL/Splunk 加 filter + group by + take top N 半结构化 KV, 但仍可能含原始 lines
Metrics Prom/Geneva 查询返回数字 (P99, count, rate) 几乎是纯数字 KV
Traces 取关键 span 或 critical path 带结构的 latency 拆解

Logs 是最难处理的, 这就是为什么 Stage 2 第一步要再做一次 LLM-based summarization;

Stage 2: Root Cause Prediction

这一阶段的目的是: 拿到诊断信息后, 借助历史经验推根因, 输出可读解释;

2.1 Incident Summarization (LLM 第一次出场)

handler 跑出来的 diagnostic info 经常 > 2000 tokens, 不仅 token 预算吃紧, 而且人读起来都费劲; 所以这里有一道显式的 LLM-based summarization:

  • 输入: handler 输出的全量诊断信息;
  • 输出: 约 120-140 词的 readable note;
  • 模型: 同一个 LLM (跟 prediction 阶段共用);

这个 summary 后面会被两次使用:

  1. 拿去做 embedding, 用于 KNN 检索;
  2. 拼进 prediction prompt, 让 LLM 看到"这次的简明病情";

[!此处的小 tradeoff]

同一个 LLM 既做 summarize 又做 predict, 第一次的偏差会传染给第二次; paper 没特别 mitigate 这一点, 是后续工作的口子;

这是整个 RAG (retrieval-augmented generation) 流水的核心:

  • Embedding model: FastText, 输出约 300 维向量;
  • Distance: 欧几里得距离;
  • 存储: Embedding vector DB (paper 没说具体产品);
  • 检索: 找当前 incident 的 top-K 最近邻历史 incident;

为什么用 FastText 而不是 GPT embedding 或 sentence-transformers:

维度 FastText GPT embedding
速度 极快, 离线训完直接跑 每次要调 API 或推理
成本 几乎为 0 按 token 付费
维度 ~300d, 内存友好 ~1500d 起
准确率 一般

paper 选 FastText 是个工程优先级判断: 这是一个频繁触发的内嵌系统, 不能每来一次 incident 就调一次 OpenAI; 准确率的损失通过后续 LLM 弥补;

[!为什么不上重型 vector DB]

Microsoft 一年的 incident 量虽然不小, 但单团队 / 单类 alert 下的历史 incident 通常在 10310410^3 \sim 10^4; 加上 FastText 300 维, 一次 KNN 暴力算就 ms 级; 论文用了 vector DB 抽象, 但实现上完全不需要 Pinecone 这种托管服务, 一个 in-memory FAISS index 就够了;

2.3 Chain-of-Thought Prediction (LLM 第二次出场)

最后一步把所有线索捏成 prompt 喂给 LLM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Prompt 结构 (示意):

### 当前 incident
Title: ...
OwningTeam: ...
Diagnostic Info Summary: <120-140 词的 summary>

### 历史相似 incident 1 (距离 0.12)
Title: ...
Diagnostic Info: ...
Root Cause Category: <label>
OCE Notes: <历史根因解释>

### 历史相似 incident 2 (距离 0.18)
...

### 历史相似 incident K (距离 0.31)
...

### 任务
请按照上面例子的思路, 一步步推理当前 incident 的根因类别;
如果以上历史 case 都不像, 请返回 "Unseen Incident";
最终输出根因类别 + 解释;

这是标准的 few-shot in-context learning + Chain-of-Thought, 要点:

  1. few-shot: top-K 历史相似 incident 当例子, 教 LLM “这种症状对应这种根因”;
  2. CoT: 提示词里写 “step by step”, 让 LLM 先推导再下结论, 减少瞎猜;
  3. Unseen 退路: 如果检索回来的 K 个都不相似, 显式允许 LLM 说 “我没见过这种, 兜底交给 OCE”;

LLM 输出两块内容:

  • Root Cause Category: 一个离散标签, 从已有根因类别里选;
  • Explanatory Narrative: 一段自然语言解释, 给 OCE 看为什么是这个原因, 含 CoT 中间过程;

关键设计决策的 tradeoff

把这套架构里几个值得拎出来的判断列一下:

决策 取舍 paper 的回答
handler 谁写 OCE 手写 vs LLM 自动生成 OCE 手写, 牺牲 generality 换可解释性和正确性
数据怎么压缩 规则 vs LLM-based summarization handler-level 隐式压缩 + LLM-level 显式总结, 两层夹击
Embedding model FastText vs GPT embedding FastText, 牺牲一点准确率换速度和成本
Vector DB 自研 vs 现成 paper 没说, 多半是 MS 内部存储
Prediction model 微调 vs 提示工程 纯 in-context learning, 不微调, 换模型零成本
处理 Unseen 强行猜 vs 显式 reject 显式 reject 选项, 放给 OCE 兜底

这一套组合拳的共同主题是: 能用确定性手段做的就不交给 LLM, LLM 只在最后一步做归因; 这是 LLM-for-Ops 这一类系统设计的一个示范模板;

评测亮点

paper 的评测部分主要打三件事:

  1. 准确率: 在一年真实 MS incident 上达到 0.766 根因分类准确率, 比纯 LLM baseline 高出 ~26 个百分点;
  2. 消融: 去掉 in-context learning (即不检索历史) 准确率明显下降, 验证 RAG 的必要性;
  3. 生产部署: Stage 1 (诊断收集) 已在 MS 跑了 4+ 年, 处理过大量真实事故 — 这是工业系统 paper 最硬的背书;

一些值得讨论的局限

检索匹配的是 summary, 不是原始诊断信息

这是 paper 一个隐藏但关键的设计选择, 也是它最大的实验空白; 完整 retrieval 链路其实是:

1
2
3
4
5
6
7
8
9
10
raw logs/metrics/traces ──► handler query ──► KV 诊断信息 (>2000 tokens)


LLM Incident Summarization (~120-140 词)


FastText embed ──► ~300d 向量


vector DB KNN ◄── 历史 incident 的 summary 向量

注意:

  1. embedding 作用对象是 summary, 不是 KV 诊断信息, 也不是原始 log;
  2. 历史 incident 在 vector DB 里也是以 summary 形态存的, 这是 summary-vs-summary 的相似度匹配;
  3. raw log/metrics 在 Stage 1 末尾就被丢弃了, 后面 retrieval 完全看不到它们;

paper 没认真讨论这个压缩带来的损失

这是这套设计最值得追问的地方:

  • 没有 ablation 研究 “summary 长度 vs 检索准确率”: 60 词 / 240 词 / 500 词分别效果如何, paper 没做;
  • 没有对比 “summary embedding vs 直接拿 KV 诊断信息 embedding”: 跳过 summarization 这一步反而是好是坏, 没数据;
  • 没有讨论 LLM context window 对系统设计的影响: 选 120-140 词应该是经验值, 没解释为什么不是 200 或 80; 也没说模型升到 100k context 后这个设计还能不能成立;
  • 没有限制 log/metrics 量级的实验: paper 没正面回应 “多少 token 的诊断信息是上限” 这种工程问题;

这种 summary-based retrieval 至少有三类失真

失真类型 描述
高阶相似但根因不同 两个 incident summary 都说 “P99 latency 飙到 1s”, 但一个是 GC stall 一个是 DB 锁竞争; raw log 里本有差异 token (GC pause 980ms vs wait for lock), 被 LLM summarize 时抹平了
被 summary 风格盖掉 LLM 总结有自己的语言模板, 容易把不同的原始证据用相似句式表达, 让本来不像的两次事故变得相似
数值信号被语言化 原始 metrics 里精确数字 (P99 = 982ms, error rate = 0.34%) 在 summary 里常变成 “high latency”, magnitude 信息丢失; FastText 是 word-level embedding, 对数字也不敏感

但这个损失大概率被工程兜底吸收了

retrieval 的 summary 只是检索 query key, 不是终极证据;最终 prediction 的 prompt 里, top-K 历史 incident 是带完整 diagnostic info 喂给 LLM 的; 所以即使 retrieval 阶段稍有偏差, K=5/10 个候选里通常仍有真正相似的那条; 也就是说 paper 默认假设了 retrieval 的 recall 比 precision 重要; 但这个假设没被实证, 是后续工作可以挖的口子;

其他局限

  • Handler 质量上限决定整体上限: OCE 写的 KQL 没把关键信息查到, 后续 LLM 也救不回来; 系统自动化的是执行, 不是诊断思路本身;
  • 历史依赖: 前所未见的全新事故只能 fall back 到 OCE; 这是 RAG 系统通病;
  • 同一 LLM 做 summarize + predict: 第一次的偏差会传染给第二次, paper 没显式处理;
  • 跨服务事件: 多服务联动的复杂事故单 handler 不够覆盖, paper 没讨论;
  • 可复现性: 不公开 handler 模板、不公开后端、不公开 vector DB 产品, 外部要完整复刻只能照思路重写;
📝 论文主流观点总结

把 RCACopilot 的设计思路浓缩成几句, 它的主流观点可以总结为:

  1. 把云事故 RCA 拆成两个性质不同的阶段: 取证据 (确定性) + 做归因 (概率性), 不要让 LLM 同时干这两件事;
  2. 取证据这一步交给 OCE 手写 handler, 用 Scope Switching / Query / Mitigation 三类 action 拼出诊断 SOP, 不让 LLM 自动生成查询;
  3. handler 输出的诊断信息再用 LLM 自己做一道 summarization 压到 ~120 词, 解决 token 预算问题;
  4. FastText embedding + vector DB 检索 top-K 相似历史 incident, 走 RAG 把"过往经验"喂给 LLM;
  5. few-shot + Chain-of-Thought prompt 让 LLM 输出根因类别 + 解释, 同时保留 “Unseen” 退路给 OCE 兜底;
  6. 整个系统选型上该轻就轻 (FastText 而不是 GPT embed, 简单 KNN 而不是高级 ANN), 工程上不是炫技导向, 这是它能在 MS 跑 4+ 年的关键;