面试知识库

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.FAST
python

问题:

  • 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 模式
plaintext

3. 结构信号:四个维度#

核心问题:一个诊断结论可靠不可靠,不看 LLM 说什么,看它的调查过程是不是完整的。

维度检测信号对应的诊断失败模式
调查完整性EXECUTOR_MAX_STEPSREPLANNER_MAX_STEPS_FORCE 出现在 transition_history相当于警察还在查案就被叫回去写报告,调查被截断
方向稳定性REPLANNER_REROUTE 出现在 transition_history初始选的 Skill 方向错了,切换过一次说明单域视角不充分
证据充分性成功工具调用 ≤1 次,或工具失败 ≥2 次只看了一个数据源就下结论,或关键数据源挂了看不到
结论可溯源报告中没有引用具体指标值/容器名/日志片段”可能是 X 导致”这种空话,没有落到实际数据

判断逻辑:硬触发 + 软累积#

为什么是”硬 + 软”两层而不是加权打分:

  • 硬触发有明确的因果关系(调查被截断 = 结论必然不完整),不需要权衡
  • 软信号单独出现可能正常(有些简单故障确实只需要 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 可能存在性能问题,建议检查其资源使用情况"
plaintext

Phase 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": "调查未完成(打满迭代上限被强制终止)"}
plaintext

Phase 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_countstream_sink 发出的 {"type": "tool_call"} 事件计数
tool_errorstool_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: "escalation" 事件渲染一条分界提示:“fast 模式结论不充分,正在切换到 deep 模式深度排查…”。

6. 配置项#

环境变量默认值说明
DEEP_DIAGNOSIS_ENABLEDfalsedeep 图总开关,关闭时永远不会升级
FAST_FIRST_ESCALATION_ENABLEDtrue升级判断开关。关闭时 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"}}]}'
bash

8. 后续优化方向#

  1. 将 fast 的部分证据注入 deep:目前 deep 是从零开始调查。可以把 fast 的 past_steps 作为初始 Evidence 注入 deep 的 CorrelationContext,避免重复取证。
  2. 自适应阈值:基于历史数据统计”升级后 deep 是否真的比 fast 给出更好结论”,调整软信号累积阈值。
  3. PACE-LM 式评估(灰区):对于结构信号处于边界的 case,用 RAG 检索历史相似事件 + LLM 评估 grounded-ness,作为第二层判断。参考微软 PACE-LM (arXiv:2309.05833)。