面试知识库

V4 Layer: 分层自适应调查引擎(L3)#

目录: app/investigation/, app/runbooks/, app/skills/

上游: L2 预处理(IncidentContext) → 下游: L4 决策层 / L5 执行层

参考: Robusta playbooks (Tier 1);Microsoft RCACopilot (Tier 2);HolmesGPT + ReAct (Tier 3);Anthropic Orchestrator-Workers

3.1 核心设计:确定性三级递进,不是三个模式#

V3 最大的设计错误是让调用方手选 fast / deep 模式——让调用方替系统决定花多少算力,真实 OnCall 里这是错的。V4 的回答是:

调用方只提交告警,调查深度是系统的输出结果,不是输入参数。

告警(调用方只做这一步)


L2 预处理 → IncidentContext


Tier 1: 已知故障 Runbook 匹配 ──命中──► 确定性处置(零 LLM)
   │ 未命中(继承证据)

Tier 2: 相似故障向量检索 ──命中──► 复用历史 RCA
   │ 未命中(继承证据)

Tier 3: 假设驱动 ReAct Loop ──► 输出根因 + 证据链
plaintext

三点强调:

  • 同一条链路上的三个确定性递进阶段,不是三个模式,没有入口分流开关
  • 命中即止:Tier 1 命中则 Tier 2/3 一次不调——“省算力”是自然结果,不需要任何人预先选择
  • 证据继承:未命中时已有证据传递给下一级,不重复取证

3.2 Tier 1:已知故障 Runbook 匹配#

零 LLM。告警指纹正则匹配 + 前置条件校验 + 冷却期检查。

class Runbook(BaseModel):
    match_conditions: list[RunbookMatchCondition]  # 正则 + 标签精确匹配
    precondition_checks: list[RunbookStep]         # 环境校验(可选)
    action_steps: list[RunbookStep]                # 资源域结构化动作
    verify_steps: list[RunbookVerifyStep]           # 三级验证门禁
    rollback_steps: list[RunbookStep]               # 回滚步骤
    cooldown_sec: int = 300                         # 冷却期
    status: str = "active"                          # active / disabled / pending_review
    source: str = "seed"                            # seed / auto / manual
python

Runbook vs Skill 的关系

Skill (保留)Runbook (新增)
给 LLM 看的”菜单卡”,自由 Markdown playbook确定性处置单元,结构化动作序列
Tier 3 假设生成时注入排障经验Tier 1 确定性匹配 + 处置
triggers 启发式匹配match_conditions 正则 + 标签精确匹配
并存,不替换

种子 Runbook 冷启动#

data/runbooks/ 下 5 个种子 Runbook:container 重启、service 重启、process kill、deployment 回滚、imagestore prune(磁盘清理)。

前置条件校验的保守设计#

探测不到资源当前状态时判不通过,宁可递进 Tier 2 也不瞎跑处置。这意味着没有 MCP 工具连接的环境里 Tier 1 命中率恒为 0——这是刻意的。

候选逐个试#

一条”磁盘满”告警可能同时匹配三份 Runbook(悬空镜像/停止容器/构建缓存)。前置条件不过就试下一个,不是第一个失败就放弃。

3.3 Tier 2:相似故障向量检索 + 限定 3 轮验证#

检索 Top-3 相似历史 Incident,每条用便宜档小模型(Qwen-turbo)做适用性复核。

检索实现#

  • 独立语料表 incident_vectors:RESOLVED 态的 Incident 向量化落库,与知识库 kb_chunks 分表——混表会让知识库检索被历史故障报告污染
  • pgvector + pg_search(BM25) + RRF 融合:复用知识库的 rrf_fuse() 纯函数
  • RRF 只管排序,不当相似度用:门槛用向量 cosine;BM25-only 命中回落字面相似度

限定 3 轮验证#

class Tier2VerificationRound(BaseModel):
    round_number: int                   # 1/2/3
    retrieved_incident_id: str
    similarity_score: float
    applicability_verdict: str          # applicable / not_applicable / uncertain
    reasoning: str                      # 小模型的推理过程
python

任一轮 applicable 且相似度过阈值 → 命中。3 轮全部 not_applicable / uncertain → 递进 Tier 3,误命中记入负样本。

负样本闭环#

Tier2Result.negative_samples 回写 incident_vectors.negative_count,检索时按 sim / (1 + w·n) 乘性降权。误命中过的候选既排得更后也更难过门槛。

三档降级#

  • embedding 挂 → 只走 BM25
  • 没装 pg_search → 退纯向量
  • 两路都空 → 回落字面相似 + Wiki

3.4 Tier 3:假设驱动两阶段推理#

Tier 3 不做无目标的 ReAct 循环,采用两阶段推理把发散收敛分离。

阶段一:假设生成#

LLM 结合 IncidentContext + 前两级继承的证据 + RAG 检索 + Skill 排障剧本,生成 3-5 条根因假设,按可能性排序。

class Hypothesis(BaseModel):
    description: str                     # "Redis 内存溢出导致服务不可用"
    target_component: str                # 涉及组件
    fault_domain: str                    # host/container/network/datastore/...
    prior_rank: int                      # 可能性排序(1=最可能)
    required_evidence_types: list[str]   # log/metric/trace/config/...
    status: HypothesisStatus             # PENDING → INVESTIGATING → CONFIRMED/REFUTED
    confidence: float                    # [0, 1]
    evidence_chain: list[str]            # evidence IDs
python

假设间应具有互斥性或至少不同故障域覆盖,避免同类假设堆叠。

阶段二:定向取证 ReAct Loop#

按可能性排序逐条假设调用工具定向取证(orchestrator-workers 模式)。

  • 每轮输出结构化假设状态INVESTIGATING / CONFIRMED / REFUTED / INSUFFICIENT_EVIDENCE
  • 某假设达到 CONFIRMED + 置信度达标 → 立即终止
  • 所有假设遍历完无 CONFIRMED → 取最高置信度者标记 BEST_EFFORT,Incident 进入 ESCALATED

置信度判定与 Impact 调门槛#

effective_threshold = tier3_confirm_threshold + impact_score.score * 0.15
# 默认: 0.8 + 0~0.15 = [0.8, 0.95]
python

影响面越大,置信度门槛越高——不敢便宜地停。

证据继承的实际落地#

Tier 1 产 resource_state_probe 证据(前置条件校验的副产品),Tier 2 产 similar_incident 证据(含不适用的——“不是它”也是有用的信息)。Tier 3 拿到前两级证据当既定事实,不重复取证,不提与之矛盾的假设。

3.5 Runbook 自增长飞轮#

Tier 3 调查完成 + root_cause.confidence ≥ 0.8

人工确认 RCA 采纳 (POST /incidents/{id}/accept-rca)
   ├── 三个条件全中 → 触发 Runbook 生成
三级验证门禁通过 (RESOLVED 状态)



LLM 提炼 → Runbook 草稿 (source=auto, status=pending_review)


审核队列 → 通过后 status=active, 入 Runbook Registry


下次同类告警 → Tier 1 命中(调查深度从 Tier 3 前移至 Tier 1)
plaintext

防污染刹车

  • 草稿一律 pending_review,不参与 Tier 1 匹配
  • validate_draft 拿资源域 REGISTRY 拒收 LLM 发明的动作
  • 门禁失败的 Runbook 自动打回 pending_review

两个触发点的到达顺序不确定(人可能在门禁前就点了采纳),靠 investigations.generated_runbook_id 做幂等——谁最后到,谁提炼。

3.6 优雅降级#

这是把规则层放在 LLM 前面的直接收益:

LLM 状态Tier 1Tier 2Tier 3系统行为
正常全功能
RPM 限流✅(检索侧)确定性自愈 + 相似案例推荐
完全不可用部分(BM25)纯规则自愈

模拟面试问答#

🔥 热点拷问#

面试官:你说砍掉 fast/deep 换三级递进。但 fast/deep 是你自己设计的,现在又说它错了——你怎么确定三级递进不是下一个”被砍掉的设计”?

fast/deep 的问题不在于”只有两个模式”,而在于存在需要调用方选择的模式。我在中间确实考虑过”把 fast/deep 换成 Tier 1/2/3 但仍让调用方选”,这个方案被否掉了——复杂度不降反升。三级递进的关键改进是消除了选择:调用方只提交告警,系统内部一条确定性链路,走多深是链路的输出不是输入。如果要说三级递进可能被砍掉的场景,那就是 Tier 2 的价值被证伪(历史案例复用率太低 → 不如直接跳 Tier 3)——这需要实测数据。

追问:Tier 1 在没有 MCP 工具的环境命中率恒为 0——这不是说你的系统在 demo 环境下最核心的功能根本不工作吗?

是的,这是刻意的保守设计,面试时不会回避。Tier 1 的前置条件校验需要探测目标资源的当前状态——“容器是不是真的在 crash?磁盘是不是真的满了?“。探测不到就不处置,因为在无法确认现场的情况下执行处置动作是危险的。这不是 demo 环境的问题,而是任何没有可观测性基础设施的环境都不应该自动处置。Tier 1 的价值体现在”有完整 MCP 工具链”的生产环境,这正是目标场景。demo 环境里告警会一路走到 Tier 3,由 LLM 给出诊断和建议——这本身也是可用的降级路径。


面试官:Tier 3 说是”假设驱动”,但假设是 LLM 生成的——LLM 生成的假设靠谱吗?会不会所有假设都指向同一个方向?

这是假设驱动的核心风险。两个缓解措施:一是 prompt 里要求假设间”至少覆盖不同故障域”——如果告警是 API 超时,不能 5 条假设都说”网络问题”,必须覆盖 host/container/network/datastore/application 多个域。二是 Skill 排障剧本的注入——tier3._select_playbook 会把当前故障域的人工排查经验灌给 LLM,引导它沿着”人类运维工程师会检查什么”的方向生成假设。但确实有可能所有假设都指向错误的方向——这时候 Tier 3 会走到兜底:所有假设遍历完无 CONFIRMED → BEST_EFFORT + ESCALATED,不硬编结论。系统承认”我不知道”比编一个错误答案更有价值。

追问:假设生成 3-5 条,取证是逐条的——如果第一条假设就花了 5 分钟取证然后被证伪,剩下 4 条还来得及吗?总预算是多少?

每条假设的取证不是无限的,受 AgentHarness 的步骤上限(默认 15 步)和 token 上限约束。正常情况一条假设取证 2-4 轮工具调用就能 CONFIRMED 或 REFUTED。5 分钟是极端情况——取证的大部分时间花在 MCP 工具调用的 I/O 上(等 Docker inspect、等 metric query),不是 LLM 推理。总预算由调查引擎的迭代上限控制,所有假设共享同一个上限。更重要的是达标即终止:如果第一条假设就 CONFIRMED 了,后面 4 条根本不会开始。假设按可能性排序的意义在于:最可能的假设先验证,大概率一两条就命中了。


面试官:你的证据继承——Tier 1 的资源探测传给 Tier 2,Tier 2 的历史案例传给 Tier 3。但实现报告说这个之前是”空管道”,你怎么保证现在真的工作了?

之前确实是空管道——graph.py 里 tier2/tier3 节点硬编码 inherited_evidence=[],做了但没接通。修法分两步:第一步让 Tier 1/2 实际产出证据(前置条件校验的资源探测结果、相似案例检索结果),第二步在图节点间通过 state 传递这些证据。tests/test_evidence_inheritance.py 同时覆盖 LangGraph 节点和纯阶梯两条路径,验证前一级的证据确实出现在后一级的输入里。

深度追问链#

面试官:你说 Tier 2 用 pgvector + BM25 + RRF 混合检索历史 Incident——同一套检索逻辑你在知识库 RAG 里也用了。但历史 Incident 的文本结构和知识库文档完全不同,同一个检索管道真的适合两种语料?

检索管道复用的是 RRF 融合函数(rrf_fuse),不是整条管道。embedding 模型相同但语料表分开(incident_vectors vs kb_chunks),检索入口独立(HybridSimilarIncidentRetriever vs HybridRetriever)。分表是刻意的——历史 Incident 的摘要是结构化的(根因 + 故障域 + 组件),知识库文档是自由文本(SOP、架构说明),混在一起会让 BM25 的词频分布失真。RRF 本身是排序函数,与语料无关。真正需要调的是相似度门槛——知识库检索用 0.3 就够了(宁可多召回),但 Tier 2 的门槛要高得多(tier2_similarity_threshold = 0.75),因为误命中的代价不一样:知识库召回多一条无关文档只是多给 LLM 看几行字,Tier 2 误命中意味着直接复用一个错误的 RCA。

继续追问:那 incident_vectors 表一开始是空的——冷启动时 Tier 2 不是永远不会命中吗?

是的。冷启动时 Tier 2 的语料表为空,检索返回空,直接递进 Tier 3——这是预期行为。随着 Incident 被 RESOLVED 后向量化落库,Tier 2 逐渐有语料可搜。这和 Tier 1 的冷启动一样:种子 Runbook 只有 5 个,覆盖率很低,大部分告警一路落到 Tier 3。系统的设计预期是随着运行积累,Tier 1/2 的覆盖率逐渐上升,Tier 3 的调用率逐渐下降——这就是”越用越准”的飞轮。冷启动阶段的 Tier 3 调用量高是正常的,对百炼 RPM/TPM 的压力也是最大的——但这阶段 Incident 数量也少,整体可控。

再追问:Runbook 自增长闭环——三个条件(置信度 ≥ 0.8 + 人工确认 + 门禁通过)同时满足才触发。现实中人工确认的响应时间可能是几小时甚至几天,门禁可能在几分钟内完成——到达顺序完全不确定。你怎么保证不丢?

两个触发点各调一次 maybe_generate_runbook():API POST /incidents/{id}/accept-rcaremediation_worker 推 VERIFY_PASSED 之后。每次调用都检查三条件是否已全中,靠 investigations.generated_runbook_id 做幂等。谁先到,谁发现条件不全,什么也不做。谁后到,发现条件齐了,触发提炼。即使两者同时到达(理论上可能),幂等字段确保只生成一份草稿。GenerationRefused(LLM 拒绝或资源域校验失败)是正常路径不是异常——飞轮转不动不应该影响处置执行或 HTTP 响应。

常规问题#

面试官:为什么 Tier 2 限定 3 轮而不是 5 轮或 1 轮?

3 是成本和召回率的平衡。1 轮太少——检索排序第一的不一定适用,但第二三名可能适用。5 轮太多——适用性复核每轮消耗一次 LLM 调用(虽然是便宜档小模型),而且 Top-5 的相似度通常已经很低了,复核的价值递减。3 轮是经验值,如果实测 Top-3 的适用率都很低可以降到 1(直接跳 Tier 3),如果适用率高可以适当放宽。

面试官:BEST_EFFORT 的结论会写进诊断报告吗?用户怎么知道这不是一个确定的根因?

会写进报告,但会明确标注”BEST_EFFORT:以下结论基于有限证据,置信度未达标(X.XX / 阈值 X.XX),建议人工复核”。Incident 同时进入 ESCALATED 状态,表示自动化链路在这个问题上不收敛。系统不会假装它知道答案——这是和”即使不确定也给出结论”的系统最大的区别。

反思与改进#

面试官:分层自适应调查引擎是整个 V4 最核心的改动。如果重来你会改什么?

两个改进。第一,Tier 2 的适用性复核不应该用通用的 prompt,应该按故障域定制 prompt。磁盘满和网络超时的”适用性”判断标准完全不同——前者看”组件相同 + 告警指标相同”就够了,后者需要看”网络拓扑是否类似 + 链路节点是否匹配”。用同一个 prompt 做所有故障类型的适用性判断,准确率一定打折。第二,Tier 3 的假设数量 3-5 是固定的,更好的做法是根据 IncidentContext 的复杂度动态调整——单服务单指标的告警 2 条假设够了,跨域多服务的告警可能需要 7-8 条。但动态调整本身又引入了一个 LLM 决策,增加了不确定性。这是个 tradeoff,当前选择了简单的固定值。

面试官:从 fast/deep 到 Tier 1/2/3,你学到的最大教训是什么?

不要让用户做系统应该做的决定。fast/deep 的本质错误是”调查深度由调用方选择”——这等于把系统设计的责任转嫁给用户。OnCall 工程师凌晨三点被叫醒,不应该还要判断”这个告警该用 fast 还是 deep”。三级递进消除了这个选择,但更深层的教训是:如果你的系统需要一个模式开关,说明你的自适应逻辑没做好。 模式开关是工程师偷懒的产物——不知道什么情况用什么策略,就让用户选。正确的做法是系统内部根据信号自动决定。