Step 15: Fast-First 诊断 + 结构信号自动升级 Deep#
1. 背景与动机#
原有问题#
旧架构在 webhook 入口按告警严重度做死的 fast/deep 二选一:
# 旧逻辑 (app/api/v1/webhook.py)
if severity in {"critical", "page", "p0", "p1"}:
return DiagnosisMode.DEEP
if len(payload.alerts) >= 10:
return DiagnosisMode.DEEP
return DiagnosisMode.FASTpython问题:
- severity 是告警源定义的,不反映故障复杂度。一个 critical 可能只是单容器重启(fast 30s 就搞定),一个 warning 可能是级联故障(该用 deep)
- 入口做决策时没有任何调查数据,是盲猜
- 一旦选了 fast 就没有回头路,复杂故障会得到不可靠的结论
设计原则#
不在入口预判复杂度,让调查结果说话。
永远先跑 fast(30s,低成本),跑完看调查过程的客观行为轨迹判断结论是否可靠。不靠 LLM 自评”我有多确定”——学术上已证明 LLM intrinsic self-correction 校准差(ICLR 2024, Huang et al.)。
2. 架构总览#
告警进入
├── severity=critical/page/p0 或 ≥10 条告警
│ → 直达 deep(节省 30s fast 尝试)
│
└── 其余(约 80% 的告警)
→ Phase 1: 跑 fast 模式
→ Phase 2: 从执行轨迹提取结构信号,评估置信度
├── 信号达标 → 结束,产出 fast 报告
└── 信号不达标 → Phase 3: 自动续跑 deep 模式plaintext3. 结构信号:四个维度#
核心问题:一个诊断结论可靠不可靠,不看 LLM 说什么,看它的调查过程是不是完整的。
| 维度 | 检测信号 | 对应的诊断失败模式 |
|---|---|---|
| 调查完整性 | EXECUTOR_MAX_STEPS 或 REPLANNER_MAX_STEPS_FORCE 出现在 transition_history | 相当于警察还在查案就被叫回去写报告,调查被截断 |
| 方向稳定性 | REPLANNER_REROUTE 出现在 transition_history | 初始选的 Skill 方向错了,切换过一次说明单域视角不充分 |
| 证据充分性 | 成功工具调用 ≤1 次,或工具失败 ≥2 次 | 只看了一个数据源就下结论,或关键数据源挂了看不到 |
| 结论可溯源 | 报告中没有引用具体指标值/容器名/日志片段 | ”可能是 X 导致”这种空话,没有落到实际数据 |
判断逻辑:硬触发 + 软累积#
# 硬触发:任一命中直接升级
if signals.hit_max_iters:
return escalate("调查未完成")
if signals.rerouted:
return escalate("方向不稳定")
# 软信号:累积 ≥ 2 个则升级
soft = []
if signals.successful_tool_calls <= 1:
soft.append("证据不足")
if signals.tool_errors >= 2:
soft.append("数据缺失")
if not signals.response_has_concrete_evidence:
soft.append("结论空泛")
if len(soft) >= 2:
return escalate(f"多项软信号: {soft}")python为什么是”硬 + 软”两层而不是加权打分:
- 硬触发有明确的因果关系(调查被截断 = 结论必然不完整),不需要权衡
- 软信号单独出现可能正常(有些简单故障确实只需要 1 次工具调用),需要累积才有意义
- 二层逻辑比加权打分更容易向面试官和同事解释
4. 完整示例#
场景:一个 warning 级别的告警,实际是跨域级联故障#
告警内容:service payment-api 响应延迟升高,P99 > 5s
Phase 1: Fast 模式执行
Skill Router → 选中 network_diagnosis (看到"延迟"关键词)
Planner → 计划: [1. 查 payment-api 网络连通性, 2. 查上游依赖延迟, 3. 查 DNS 解析]
Executor Step 1 → 调用 ping_target(payment-api) → 正常
Executor Step 2 → 调用 query_prometheus(http_request_duration) → 发现上游 catalog-svc 也慢
Replanner → 判断: 不是网络问题,是上游服务问题 → 触发 reroute 切换到 container_diagnosis
Planner (reroute) → 新计划: [1. docker_ps 看 catalog-svc, 2. 查容器日志]
Executor Step 3 → 调用 docker_ps → catalog-svc 状态 Up
Executor Step 4 → 调用 search_logs(catalog-svc) → 工具超时失败
Replanner → max_steps 到达,强制收尾
Report → "catalog-svc 可能存在性能问题,建议检查其资源使用情况"plaintextPhase 2: 结构信号评估
FastRunSignals(
transitions=[..., "replanner_reroute", ..., "executor_tool_error",
"replanner_max_steps_force"],
tool_calls_count=4,
tool_errors=1,
response="catalog-svc 可能存在性能问题,建议检查其资源使用情况"
)python评估结果:
- ✅ 硬触发 1:
hit_max_iters= True → 调查被截断 - ✅ 硬触发 2:
rerouted= True → 方向切换过
判定:直接升级。 任一硬触发命中就升级,不需要看软信号。
→ 产出 SSE 事件: {"type": "escalation", "reason": "调查未完成(打满迭代上限被强制终止)"}plaintextPhase 3: Deep 模式并行取证
IncidentManager → 加载告警上下文
CorrelationContext → Wiki 召回: 历史上 catalog-svc 出过 DB 连接池耗尽
EvidencePlan → 派出 4 个专家: metric_agent + log_agent + infra_agent + runbook_agent
metric_agent → 发现 catalog-svc 的 DB 连接池使用率 100%,payment-api 在等它
log_agent → 发现 catalog-svc 日志 "connection pool exhausted, wait timeout"
infra_agent → docker inspect 显示容器内存正常,没有 OOM
runbook_agent → 匹配到 SOP: "DB 连接池耗尽 → 重启服务或扩连接池"
EvidenceReducer → 归并: 4 条证据指向同一根因
RCAJudge → "根因: catalog-svc 的 DB 连接池耗尽,导致上游 payment-api 等待超时"
置信度 0.92 (4 条独立证据收敛)
RemediationPlanner → docker restart catalog-svc
Report → 完整报告 (引用所有证据)plaintext对比:如果没有 fast-first 升级机制#
- 旧逻辑:warning 级别 → 永远走 fast → 得到”可能存在性能问题”的空泛结论
- 新逻辑:warning 级别 → fast 尝试 → 信号不达标 → 自动升级 deep → 定位到 DB 连接池耗尽
5. 实现细节#
文件改动#
| 文件 | 改动 |
|---|---|
app/runtime/escalation.py | 新建。FastRunSignals 数据类 + should_escalate_to_deep() 判断函数 |
app/orchestration/diagnosis_runner.py | 重构。抽出 _run_single_graph() 积累 meta 统计;run_diagnosis_graph() 改为 fast-first 三阶段流程 |
app/api/v1/webhook.py | 微调。直达 deep 的条件从 p0/p1/critical/page 收紧为 p0/critical/page,p1 改走 fast-first |
FastRunSignals 数据来源#
信号不是从 graph state 直接取的(state 里没有 tool-level 计数),而是从 _run_single_graph() 流式消费事件时积累:
| 信号 | 来源 |
|---|---|
transitions | 每个 node_output 里的 transition_history[].reason,逐条 append |
tool_calls_count | stream_sink 发出的 {"type": "tool_call"} 事件计数 |
tool_errors | tool_call 事件中 status == "failed" 的计数 + executor_tool_error transition 计数 |
response | 最后一个 type=report 事件的 data.report 文本 |
结论可溯源检测 (_has_concrete_evidence)#
用正则检测报告文本是否包含 ≥2 处具体数据引用:
_CONCRETE_EVIDENCE_PATTERNS = re.compile(
r"(\d+(\.\d+)?%)" # 百分比: CPU 98%
r"|(\d+(\.\d+)?\s*(MB|GB|ms|s|...))" # 带单位数值: 3.2GB, 120ms
r"|(容器|container|pod|service)\s*\w+" # 实体名: 容器 nginx-proxy
r"|`[^`]{4,}`" # 反引号引用: `connection refused`
r"|(ERROR|WARN|Exception|OOM|...)" # 日志信号词
r"|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # IP 地址
r"|(\d{2}:\d{2}:\d{2})", # 时间戳
re.IGNORECASE,
)python阈值为 2:至少出现 2 处具体数据引用才算”有据”。单纯出现一个”可能”+“ERROR”不算。
SSE 事件流示例#
fast-first 升级到 deep 时,前端会收到以下事件序列:
{"type": "start", "stage": "diagnosis_init", ...}
{"type": "mode_selected", "effective_mode": "fast", ...}
{"type": "skill_selected", "skill": "network_diagnosis", ...}
{"type": "plan", "plan": ["查网络连通性", "查上游延迟", ...], ...}
{"type": "step_complete", "iteration": 1, ...}
{"type": "step_complete", "iteration": 2, ...}
{"type": "transition", "reason": "replanner_reroute", ...}
{"type": "step_complete", "iteration": 3, ...}
{"type": "transition", "reason": "replanner_max_steps_force", ...}
{"type": "report", "report": "catalog-svc 可能存在性能问题...", ...}
// ← 升级分界线
{"type": "escalation", "reason": "调查未完成(打满迭代上限被强制终止)",
"signals": {"hit_max_iters": true, "rerouted": true, ...}}
{"type": "evidence_plan", "agents": ["metric_agent", "log_agent", ...], ...}
{"type": "evidence", "agent": "metric_agent", ...}
{"type": "evidence", "agent": "log_agent", ...}
{"type": "rca", "rca": {"root_cause": "DB 连接池耗尽", "confidence": 0.92}, ...}
{"type": "report", "report": "# 深度诊断报告...", "deep": true}
{"type": "complete", ...}json前端可以用 type: "escalation" 事件渲染一条分界提示:“fast 模式结论不充分,正在切换到 deep 模式深度排查…”。
6. 配置项#
| 环境变量 | 默认值 | 说明 |
|---|---|---|
DEEP_DIAGNOSIS_ENABLED | false | deep 图总开关,关闭时永远不会升级 |
FAST_FIRST_ESCALATION_ENABLED | true | 升级判断开关。关闭时 fast 跑完即结束,不评估升级 |
两个开关独立:
DEEP_DIAGNOSIS_ENABLED=false→ fast only,无论信号如何都不升级DEEP_DIAGNOSIS_ENABLED=true, FAST_FIRST_ESCALATION_ENABLED=false→ 旧行为(入口二选一)- 两者都 true → fast-first + 自动升级(推荐)
7. 测试验证#
# 单元测试: 5 个 case 覆盖硬触发/软信号/正常通过
.venv/bin/python -m pytest tests/test_escalation.py -v
# 集成验证: 发一个 warning 告警,观察 SSE 流是否出现 escalation 事件
curl -X POST http://localhost:9900/api/v1/webhook/alertmanager \
-H "Content-Type: application/json" \
-d '{"alerts": [{"labels": {"alertname": "HighLatency", "severity": "warning"}, "annotations": {"summary": "payment-api P99 > 5s"}}]}'bash8. 后续优化方向#
- 将 fast 的部分证据注入 deep:目前 deep 是从零开始调查。可以把 fast 的
past_steps作为初始 Evidence 注入 deep 的CorrelationContext,避免重复取证。 - 自适应阈值:基于历史数据统计”升级后 deep 是否真的比 fast 给出更好结论”,调整软信号累积阈值。
- PACE-LM 式评估(灰区):对于结构信号处于边界的 case,用 RAG 检索历史相似事件 + LLM 评估 grounded-ness,作为第二层判断。参考微软 PACE-LM (arXiv:2309.05833)。