Skip to content

RCA(根因分析)

告警触发时,Ongrid 派出一个 LLM worker,在 incident-investigator persona 上驱动 graph-kernel ReAct agent,调用工具收集证据,把结构化报告写回 investigation_reports

报告渲染在 SPA 的 /alerts/incidents/:id 页上,紧挨着触发的 series —— 人工 SRE 永远不用"从一张空白 prompt 开始"。

HLD-013

当前管线落地了 HLD-013 的 Phase 1+2 因果模型。"总结触发了什么"的朴素做法 (PR-2)被替换掉了,因为很明确运维想看的是0 号病人 —— 那个引发级联的 具体进程 / 容器 / 行 —— 而不是告警文本的复述。

生命周期

text
incident.fire (isNew=true)
    └─ alert.Usecase.RecordFiring
        └─ Investigator.InvestigateAsync(incident)
            └─ Enqueue → Gate 1-3 (severity, inflight, semaphore)
                └─ repo.Create(pending row)   ← UI shows "investigating…"
                └─ go run(reportID, incident, dedupKey, locale)
                    ├─ spawner.SpawnWorker(incident-investigator persona)
                    ├─ worker drives the ReAct loop with tools
                    ├─ Pass-2 structured extraction (cheap model)
                    └─ repo.MarkReady(report fields)  ← UI shows ready

InvestigateAsync 是 alert.Usecase 调用的公开缝 —— 见 usecase.go:301

Gate

三道门在 worker 派出去之前过滤。每次拒绝都会持久化为一行 status=skipped, 这样 SPA 给运维一个理由,而不是"永远没启动"。

默认未命中行为
严重级地板Config.MinSeveritywarning静默跳过,不写行
进程内 inflight(按 incident_id永远开静默合并
并发上限Config.MaxConcurrent5写一行 skipped: concurrency limit reached (N workers in flight)

并发上限保护 LLM provider 限流,并在 100 条 incident 同时触发时约束 RAM。 超上限的调用方会拿到一行明确的 skipped,不是排队。

worker

运行时用 chatruntime.SpawnRequest 派 worker:

go
// internal/manager/biz/alert/investigator/usecase.go:571
worker, err := uc.spawner.SpawnWorker(ctx, chatruntime.SpawnRequest{
    AgentName:   uc.cfg.AgentName, // "incident-investigator"
    Prompt:      prompt,
    Background:  false,
    SessionKind: "investigation",
})

prompt 由 renderAlertPrompt 渲染,包括:

  • Incident 元数据(rule、severity、device_id、value、threshold、summary)。
  • 一条明确的起始指令:Start with correlate_incident to pull metrics + logs + traces + topology around the fire window.
  • 一个硬预算:最多 10 次 tool 调用,第 7 次前必须开始写报告。这个预算 住在 user 消息里,因为非前沿模型(GLM、DeepSeek)跟 user 消息里的约束 比 system 消息里的约束更可靠 —— 不带它的话 eino MaxStep 上限每跑一次就 撞一次。
  • 一条locale 指令,用运维的 UI locale 覆盖 persona 的隐式语言(locale 怎么传播见 模型 / 路由)。

tool 预算 + 抢救

eino ReAct graph 限制总步数。worker 在没写最终答案前用完上限时,investigator 会抢救部分 trail:

  1. MessageReader.ListMessages(sessionID, limit=100) 把每一轮拉出来。
  2. assistant + tool 消息拼成一份合成的"我们找到了什么"markdown。
  3. 把抢救件喂给同一个 Pass-2 抽取器。
  4. 报告被标 ready,开头加一条低置信度注: 工作器超出最大步数预算(exceeds max steps);以下为根据已收集工具结果的局部分析,置信度偏低。

不抢救的话运维看到的是 status=failed,没有任何有用数据 —— worker 通常已经 调了 10+ 次工具拿到答案,就是没写出最后的综合 turn。

结构化抽取(Pass 2)

worker 的最终 assistant 消息是 markdown —— SPA 需要结构化字段。第二次便宜的 LLM 调用抽取:

  • root_cause —— 一段 TL;DR。
  • affected_window —— 症状区间什么时候开始 / 结束。
  • pinpointed_target —— 发生变化的具体进程 / 容器 / 文件。
  • related_alerts —— 共触发的 incident(通过 RelatedAlertQuerier)。
  • evidence —— 带项目符号的源引用(PromQL 输出、日志行等)。
  • suggested_actions —— 运维可执行的下一步。
  • confidence + confidence_factors
  • tool_call_count —— 从 chat_messages 读回来,UI 显示 worker 实际调了 多少次工具(不是硬写的 0)。

模型 + provider 可通过 Config.SummarizerProvider / Config.SummarizerModel 配置。默认 30s 超时(短 prompt、短 reply、无 tool 循环)。

抽取器没接线或出错时,回退用 firstParagraphOneLine 在 worker markdown 上 跑,填 root_cause,并把整份 markdown 原样作为 findings_md 发出去。

加粗 header 陷阱

firstParagraphOneLineusecase.go:846) 跳过纯 markdown 脚手架(heading、divider、整行加粗的章节标题),让 root_cause 读起来像一句话,而不是 **现象**。之前有个 bug 只剥掉了开头 的 **,留了尾巴上一对 —— 在同一个函数里修了。

手动重触发

http
POST /v1/alerts/incidents/{id}/investigation
Accept-Language: en

ForceEnqueue 跑手动路径:

  1. 停掉这条 incident 当前在跑的 worker(best-effort —— worker_id 在重启 后过期就 warn 并继续)。
  2. 硬删之前的 investigation_reports 行(覆盖之前 force-enqueue 留下的软 删行,让唯一 incident_id 索引不拒下次 Create)。
  3. 释放 inflight 守卫。
  4. 用从 Accept-Language 解出的 locale 调 EnqueueWith,让重生成的报告用 运维 UI 语言回来。

严重级地板照样适用 —— 对 info 级 incident 手动触发会返回 severity below floor 错误。

启动期回填

新装会撞蛋鸡问题:结构化 RCA 链路只在至少配置了一个 LLM provider 时才接 上,但 incident 可以在运维加 provider 之前先触发。BackfillUnstartedIncidents 在启动时跑,走 ListIncidentsWithoutReport(since, limit),重新入队窗口内 触发过的。常规 gate 仍然适用,所以已解决的 incident 和超地板的会被跳过。

接线见 cmd/ongrid/main.go —— 启动时用 24 小时窗口跑。

另见

  • 告警 —— 触发什么。
  • 拓扑 —— expand_topology 暴露什么给 investigator 走爆炸半径。
  • 技能 —— correlate_incidentget_incident_detailquery_promqlsearch_logs:worker 调的工具。
  • 模型 / 路由 —— 每次调查的 locale + provider 怎么解。
  • 模型 / 预算 —— 约束调查成本的全局每日 token 上限。