面试知识库

M2 · 假设驱动式调查机制#

简历 Bullet Point: Tier 3 采用两阶段推理:LLM 先生成 3-5 条根因假设并按可能性排序,再在 ReAct Loop 中逐条调用工具定向取证,每轮输出结构化假设状态与置信度,达标即终止,避免非必要推理。


开场钩子#

场景#

V3 的 deep 模式用 4 个专家 Agent(Log/Metric/Infra/Runbook)并行取证,最后由一个 RCA Judge LLM 一次性裁决根因。听起来很工业化——但上线后碰到一个典型问题:某次 web-api 502 告警,MetricAgent 查到 CPU 100%,LogAgent 查到 OOM 日志,InfraAgent 查到容器反复重启——三条高度相关的线索分散在三个 Agent 各自的证据列表里,RCA Judge 面对一堆无结构化的证据文本做了一次”创意写作”式的裁决,输出了一段看起来很合理但根因完全对不上的报告。

拆解后发现两个结构性问题:第一,取证是无方向的——4 个 Agent 各自按”我在我的工具域里尽量多查”运行,没人告诉它们”要验证什么假设”,结果是广撒网但深度不够。第二,裁决是一锤定音的——RCA Judge 拿到一堆松散证据做一次性推理,没有”哪条假设被证实、哪条被证伪”的结构化中间状态,可审计性为零——一条根因结论说对了靠运气,说错了无法追溯哪步推理出了问题。

V4 把这两个问题拆成两个阶段:先收敛(LLM 生成 3-5 条根因假设并排序),再定向展开(按排序逐条派 subagent 取证,每轮输出结构化假设状态 + 置信度,达标即终止)。取证不再是无目标漫游,而是围绕特定假设的定向验证。

面试官切入#

“你提到了假设驱动——这跟普通的 ReAct 循环有什么区别?“


一、模块运作流程#

1.1 一句话定位#

Tier 3 是三级递进调查引擎的兜底层——Tier 1 Runbook 无匹配、Tier 2 历史案例不适用时,用假设驱动的两阶段推理做深度调查。与普通 ReAct(reason→act 无目标循环)的关键区别:先生成有限假设并排序(收敛),再逐条定向取证(展开),每轮产出结构化状态,达标即终止——省 token、可解释、可审计。

1.2 全景流程图(文字版)#

1.3 分步详解#

Step 1: 继承上下文组装#

  • 做什么: 把 Tier 1/2 未命中时留下的证据和排除方向汇总,交给阶段一假设生成
  • 怎么做: run_tier3 接收两个参数:excluded(前序 Tier 明确排除的方向列表,如 “Tier 2: 历史案例 inc-xxx 不适用——组件不匹配”)和 inherited_evidence(前序 Tier 产出的 Evidence dict 列表,如 Tier 1 前置条件校验时探到的资源实况”容器当前态 running”)。两者分别注入假设生成 prompt 的不同位置:排除方向放 ## 前序 Tier 已排除的方向,继承证据放 ## 前序 Tier 已取得的证据 (可信)
  • 为什么这么做: 三级递进”未命中自动递进并继承已有证据”的落点就在这里。没有排除方向,LLM 会重复提出 Tier 1/2 已经否定的假设白烧 token;没有继承证据,Tier 3 会派 subagent 重新探一遍 Tier 1 已经探过的资源状态

Step 2: Skill 剧本注入#

  • 做什么: 从 Skill Router 选一本排障剧本注入假设生成的 prompt
  • 怎么做: _select_playbook 调用 skill_router_node 做域路由,选中后从 SkillRegistry 取该 Skill 的 playbook(Markdown 格式的标准操作流程),截断到 1200 字符注入 knowledge_block
  • 为什么这么做: Runbook 是”能被结构化为确定性步骤”的知识(Tier 1 用),Skill 的 playbook 是”人怎么排查这类故障”的经验——恰好是假设生成需要的先验知识。两者并存不替换。剧本选择失败不致命——Tier 3 没有剧本也能靠 LLM 自身知识生成假设

Step 3: 假设生成(阶段一)#

  • 做什么: LLM 一次调用生成 3-5 条结构化根因假设
  • 怎么做:
    1. 组装 prompt:可信上下文(告警信息 + 继承证据 + 拓扑关系)放顶部;不可信内容(知识库召回 + 排障剧本)用 <<<UNTRUSTED_DATA_BEGIN>>> 定界符包裹——间接提示注入隔离
    2. ainvoke_structured 调 Planner 模型(temperature=0.2,允许微量随机性避免假设趋同),强制结构化输出 HypothesisSet(不从自由文本解析动作)
    3. drafts_to_hypotheses 做后处理:按 prior_rank 排序 + 同 (fault_domain, target_component) 去重(一个故障域只留排序最靠前的一条)+ 截断到 tier3_max_hypotheses(默认 5)
    4. LLM 不可用时退化为单条兜底假设:从 L2 的 root_cause_candidates 取第一个组件,构造 "{component} 异常导致 {alertname}"
  • 为什么这么做: 结构化输出是关键设计。假设必须声明 required_evidence_types(metric/log/config/runbook),供后续定向取证决定派哪类 subagent——如果让 LLM 自由文本描述假设再人工解析动作类型,多一层脆弱的解析就多一层出错。互斥去重防止 LLM 堆叠同域近义假设(“MySQL 连接超时""MySQL 网络不通""MySQL 端口不可达”其实是一个意思)

Step 4: 定向取证(阶段二——前沿并行)#

  • 做什么: 排名最靠前的 PARALLEL_FRONTIER(默认 2)条假设同时取证
  • 怎么做: asyncio.gather 并行调 _investigate_one。每条假设内部:agents_for(hypothesis) 根据 required_evidence_types 映射到专业 subagent(metric→metric_agent、log→log_agent、config/infra→infra_agent、runbook→runbook_agent),同一假设的多类 subagent 同样并行。每个 subagent 的输入里注入了当前假设描述:"[定向取证任务] 请围绕以下根因假设收集证据: {hypothesis.description}"——这正是”定向”的落点
  • 为什么这么做: 排名前 2 的假设最可能是根因,并行不等待彼此。不把全部假设一次性并行的原因:CONFIRMED 会提前终止,前 2 个够了就不需要后面的,全并行只是白烧 token

Step 5: 结构化验证#

  • 做什么: 每条假设取证完后,用 LLM 判定假设状态(confirmed / refuted / insufficient_evidence)
  • 怎么做: _verify 用 Executor 模型(temperature=0.0,确定性判定),输入是假设描述 + 本轮取到的证据 + 继承证据。强制结构化输出 HypothesisVerdict,包含 statusconfidence(0~1)、reasoning(必须引用证据)、refutation_reason(证伪时指出否定证据)。LLM 不可用时返回 insufficient_evidence(绝不硬判 confirmed)
  • 为什么这么做: 验证 prompt 有两条硬约束:(1)confirmed 必须有证据直接支持,没有直接证据一律 insufficient_evidence,不靠”合理推测”凑;(2)refuted 必须指出哪条证据否定了它。这两条保证了每个假设状态背后都有可追溯的证据链——V3 RCA Judge 的”创意写作”问题正是因为缺了这个

Step 6: 定向取证(阶段二——其余串行 + 提前终止)#

  • 做什么: 前沿并行完后如果没有 CONFIRMED,剩余假设按排序逐条取证
  • 怎么做: for 循环遍历 rest = hypotheses[PARALLEL_FRONTIER:],每条走 _investigate_one_verify,每轮检查 _confirmed(hypothesis, threshold)——置信度 ≥ 生效门槛即终止。迭代计数器 iterations 累计达到 tier3_max_iterations(默认 8)时也停止
  • 为什么这么做: 串行的原因是提前终止的价值远大于并行的时间节省——第 3 条假设如果 CONFIRMED 了,第 4、5 条根本不需要取证。迭代预算同时是资源耗尽护栏:提示注入没法把循环拖到无限

Step 7: 终止与兜底#

  • 做什么: 根据假设验证结果产出最终 Investigation
  • 怎么做:
    1. 达标终止: 某假设 status=CONFIRMEDconfidence ≥ effective_thresholdinvestigation.hit=True,根因填入 RootCause(含 hypothesis_id 追溯),进 remediation_planner
    2. 全不达标: 取 confidence 最高且非 REFUTED 的假设标记 BEST_EFFORTinvestigation.hit=Falseescalation_reason 写明门槛和实际最高值,Incident 迁 ESCALATED 交人工
    3. 生效门槛:effective = tier3_confirm_threshold(0.8) + impact_score × weight(0.15),硬顶 0.95。影响面越大越不敢便宜地停
  • 为什么这么做: “置信不足时不硬编结论”是系统底线。BEST_EFFORT 不进处置链路——宁可人工接管也不在不确定时执行自动修复。这区别于很多 AIOps 系统”总是给一个答案”的做法

1.4 数据流 trace(一条告警走一遍 Tier 3)#

MySQL replica lag > 10s on db-slave-02 为例,假设 Tier 1 Runbook 无匹配、Tier 2 检索到一条历史案例但被判不适用,现在递进到 Tier 3。

① 继承上下文组装

  • 输入: excluded=["Tier 1: 无匹配 Runbook", "Tier 2: 历史案例 inc-20260601 不适用——组件版本不同"]inherited_evidence=[{source: "tier1", type: "resource_probe", summary: "service://mysql-slave 当前态 active, lag=12s"}, {source: "tier2", type: "similar_incident", summary: "inc-20260601 相似度 0.78, 判定 not_applicable"}]
  • 处理: 填入 Investigation.inherited_evidence,计算生效门槛 0.8 + 0.39×0.15 = 0.859
  • 输出: threshold=0.859, inherited=2 条

② Skill 剧本注入

  • 输入: ctx.query = "MySQL replica lag > 10s on db-slave-02"
  • 处理: Skill Router 选中 mysql_ops(置信度 0.88),从 registry 取 playbook:"### MySQL 主从延迟排查\n1. 检查 SHOW SLAVE STATUS\n2. 查看主库 binlog 写入速率\n3. 检查从库 IO/SQL 线程状态\n4. 排查大事务或 DDL\n5. 验证网络延迟"
  • 输出: playbook[:1200] 注入 knowledge_block

③ 假设生成

  • 输入: 告警上下文 + 继承证据(标记 可信)+ 知识库召回(标记 UNTRUSTED)+ 排除方向 + 剧本
  • 处理: Planner 模型(temperature=0.2)结构化输出 HypothesisSet
  • 输出: 4 条假设:
    1. #1 "主库大事务阻塞 binlog 传输导致从库延迟" — fault_domain=datastore, required=[metric, log]
    2. #2 "从库 SQL 线程被长查询阻塞" — fault_domain=datastore, target=db-slave-02, required=[metric, log]
    3. #3 "主从之间网络延迟/带宽饱和" — fault_domain=network, required=[metric, infra]
    4. #4 "从库磁盘 IO 饱和导致 relay log 应用缓慢" — fault_domain=host, target=db-slave-02, required=[metric, infra]
    • 注意:去重后没有出现第二个 datastore×db-slave-02 假设(#2 留下因为 target 不同于 #1)

④ 前沿并行取证(#1 和 #2)

  • 假设 #1 的 subagent 分派:required=[metric, log] → metric_agent + log_agent 并行
    • metric_agent: 注入 "[定向取证任务] 请围绕以下根因假设收集证据: 主库大事务阻塞 binlog 传输",调 mysql_status 查主库 binlog 位点变化率
    • log_agent: 调 mysql_processlist 查是否有长事务
    • 取证结果:[{source: metric_agent, summary: "主库 binlog 写入 3.2MB/s,无明显阻塞"}, {source: log_agent, summary: "processlist 无超过 10s 的事务"}]
  • 假设 #2 的 subagent 分派:required=[metric, log] → metric_agent + log_agent 并行
    • 取证结果:[{source: metric_agent, summary: "从库 Seconds_Behind_Master=12, SQL 线程 state=Waiting"}, {source: log_agent, summary: "从库慢查询日志: SELECT ... JOIN 四表关联, 执行 8s, 阻塞 SQL 线程"}]

⑤ 结构化验证

  • 假设 #1 验证:证据不支持(主库无大事务)→ HypothesisVerdict(status=refuted, confidence=0.1, refutation_reason="processlist 无长事务, binlog 写入正常")
  • 假设 #2 验证:慢查询直接阻塞 SQL 线程 → HypothesisVerdict(status=confirmed, confidence=0.88)

⑥ 终止判定

  • confidence=0.88 ≥ threshold=0.859CONFIRMED,立即终止,不验证 #3 和 #4
  • 产出 RootCause(component=db-slave-02, failure_mode=sql_thread_blocked, description="从库 SQL 线程被慢查询阻塞导致主从延迟", confidence=0.88, hypothesis_id=hyp-xxx)
  • 报告渲染:假设树里 #1 标 ❌ REFUTED、#2 标 ✅ CONFIRMED、#3/#4 标 ⏸ PENDING

1.5 技术选型决策表#

组件选了什么备选方案为什么选它什么情况下换
假设生成结构化输出Pydantic + ainvoke_structured自由文本 + 正则解析结构化输出在 API 层面保证格式正确,不需要后处理解析;重试逻辑在 ainvoke_structured 内部闭环模型不支持 function calling / JSON mode 时需要回退到解析
假设验证模型Executor 模型(deepseek-v4, temperature=0.0)用便宜小模型(qwen-turbo)验证需要对证据做推理判断——小模型在判定”证据是否直接支持假设”时准确率不够,推理链经常跳步验证只是二分判断不需要推理时可以降级(目前不是——需要分析证据是否构成直接支持)
Subagent 分派策略required_evidence_types 静态映射LLM 动态选择 subagent映射表一张(EVIDENCE_TYPE_TO_AGENT),确定性、零延迟、不消耗 token。LLM 选择会引入又一层不确定性证据类型远多于当前几类、或 subagent 种类扩展到需要语义匹配时
并行策略前 2 并行 + 其余串行全并行 / 全串行全并行在”第 1 条就 CONFIRMED”时白烧 3-4 条假设的取证成本;全串行在第 1 条 REFUTED 时浪费等待时间。前 2 并行是经验折中如果 LLM 假设排序质量很高(第 1 条命中率 >80%),可以改为 PARALLEL_FRONTIER=1

1.6 口述脚本#

开场(20s): “Tier 1 和 Tier 2 都没命中时进入 Tier 3——假设驱动的两阶段推理。核心思路是:不让 Agent 无目标地漫游,而是先让 LLM 生成一组根因假设排好序,然后逐条派专业 subagent 定向取证。”

展开(60s): “阶段一,LLM 一次调用生成 3-5 条假设,每条声明’验证这个假设需要什么类型的证据’——比如一条假设说’主库大事务阻塞 binlog’,声明需要 metric 和 log 证据。阶段二,排名前两条并行取证,每条假设的证据类型映射到专业 subagent——metric 类证据派 metric_agent,log 类派 log_agent。取证回来后,另一个 LLM 调用做结构化验证,输出三选一:confirmed、refuted、insufficient_evidence,附带置信度。置信度达标立即终止,不验证后续假设。”

[留钩子]: “这里有一个关键设计:假设验证的 LLM prompt 明确要求’confirmed 必须有证据直接支持,没有直接证据一律 insufficient_evidence’——这是从 V3 的一个痛点学来的。“(引导面试官追问 V3 RCA Judge 的问题)

收尾(10s): “所有假设都没达标怎么办?取置信度最高的标 BEST_EFFORT,Incident 迁 ESCALATED 交人工——系统不在不确定时硬编结论。“


二、踩坑实录#

坑 1:V3 RCA Judge 的”创意写作”——无结构化裁决的系统性风险#

  • 现象: V3 deep 模式 4 个专家 Agent 并行取证完成后,RCA Judge 一次性拿到全部证据做根因裁决。多次出现”报告读起来很合理,但根因对不上”的情况——Judge 会把不相关的证据串成一个自洽但错误的叙事
  • 根因: RCA Judge 的输入是一大段无结构化的证据文本(几千 token),输出也是自由文本。LLM 在这种设定下天然倾向于”编一个说得通的故事”而不是”严格基于证据做判断”。没有中间结构(假设状态、置信度、证伪依据)约束,就没有审计抓手——说对了不知道为什么对,说错了不知道哪步错
  • 修法: V4 拆成假设生成 + 逐条验证两阶段。验证的结构化输出 HypothesisVerdict 强制 LLM 在三选一(confirmed/refuted/insufficient_evidence)里做判断,并且 confirmed 必须引用证据、refuted 必须指出否定证据。每条假设的状态变迁全记录在 Investigation.hypotheses
  • 教训: LLM 做开放式推理的可靠性远低于做约束性判断。把开放问题(“根因是什么”)分解成一系列约束判断(“这条假设是否被这组证据支持”),每步都有结构化输出,整体可审计性质变

坑 2:假设间同域堆叠——LLM 的”安全策略”副产品#

  • 现象: 早期不做互斥去重时,LLM 面对 MySQL 主从延迟告警经常生成:“#1 主库大事务""#2 主库长查询""#3 主库 DDL 操作”——三条假设本质是同一个故障域(datastore×主库)的近义表述,且全部需要 log+metric 证据,取证内容高度重叠
  • 根因: LLM 有”多样性焦虑”——被要求生成多条假设时倾向于把一个方向拆成多个小变体以凑数,而不是真正跨故障域思考。这是 LLM 的安全策略(避免遗漏)的副产品,但在假设驱动调查里恰好有害
  • 修法: drafts_to_hypotheses 里加了同 (fault_domain, target_component) 去重——同一个域+组件只留 prior_rank 最靠前的一条。同时在生成 prompt 里显式要求”假设之间尽量互斥,至少覆盖不同故障域”
  • 教训: 给 LLM 约束不能只靠 prompt 里写要求——prompt 约束是 soft 的,结构化后处理才是 hard 的。两者结合:prompt 引导 LLM 往互斥方向想,后处理兜住它没做到的情况

坑 3:证据链断裂——subagent 产出的 Evidence 缺 id#

  • 现象: 假设状态显示 CONFIRMED 但 hypothesis.evidence_chaininvestigation.evidence_ids 恒为空列表。报告里”证据链”段全是 (未取到证据),跟实际取到了丰富证据的事实矛盾
  • 根因: subagent(metric_agent/log_agent 等)原本是为 V3 deep 图设计的,它们产出的 Evidence dict 里没有 id 字段——在 deep 图里 Evidence 落库时由 Postgres 分配 id,但 Tier 3 的验证在落库之前就需要 id 来构建证据链引用
  • 修法: _investigate_one 里在取证完成后立即给每条 Evidence 补 id:evidence.setdefault("id", new_id("ev"))。这个 id 是本地引用 id(ev- 前缀),在落库时会被替换为持久 id,但在 Tier 3 内部的证据链追踪已经够用
  • 教训: 跨模块复用函数时,输出契约的隐式假设(“id 会由下游补”)是最容易踩的坑。显式补全好过指望下游补——setdefault 不覆盖已有 id,安全

坑 4:继承证据丢失导致 Tier 3 重复探测#

  • 现象: Tier 1 前置条件校验已经探到”容器当前态 running,lag=12s”,但 Tier 3 假设生成后的取证阶段又派 infra_agent 去探了一遍容器状态——浪费了一轮 subagent 调用和 token
  • 根因: 早期版本 run_tier3 没有 inherited_evidence 参数,继承证据只通过 excluded_directions 传了排除方向(“不是什么”),但没传具体事实(“已知是什么”)。假设生成 LLM 不知道”容器是 running”这个事实,就无法在假设里声明”不需要再探容器状态”
  • 修法: 增加 inherited_evidence 参数,在假设生成 prompt 里单列 ## 前序 Tier 已取得的证据 (可信) 段,并标注 > 上述事实已被证实, 不要提出与之矛盾的假设, 也不要重复取证。验证阶段同样把继承证据一起呈给判定 LLM
  • 教训: “继承”不只是传一个标志——排除方向说”不是什么”(收敛搜索空间),继承证据说”已知是什么”(避免重复工作)。两者缺一不可,少了后者等于让 Tier 3 在 Tier 1 已经探过的路上重走一遍

三、量化评估#

3.1 评估数据集构建#

  • 数据来源: Tier 3 的测试覆盖来自 tests/test_investigation_ladder.py 中 Tier 3 相关路径(假设生成 → 取证 → 验证 → CONFIRMED/BEST_EFFORT/ESCALATED)+ tests/test_tier3_hypothesis.py(假设去重、兜底退化、证据链完整性)
  • 规模与分布: 覆盖 5 类场景——(1) 正常假设生成+首条 CONFIRMED 提前终止 (2) 全部 REFUTED → BEST_EFFORT+ESCALATED (3) LLM 不可用 → 单条兜底假设 (4) 继承证据注入验证 (5) 迭代预算耗尽
  • 标注方式: 每条测试用例的 mock LLM 输出和预期 Investigation 结果人工标注

3.2 评估方法#

  • 怎么跑: pytest tests/test_investigation_ladder.py tests/test_tier3_hypothesis.py,LLM 调用全 mock(ainvoke_structured 返回预设的 HypothesisSet/HypothesisVerdict),subagent 返回预设 Evidence
  • 对照组: (1) 有继承证据 vs 无继承证据:验证继承证据注入后验证 LLM 能利用前序事实做判定 (2) 有互斥去重 vs 无去重:验证去重后假设覆盖更多故障域
  • 可复现性: 全部单测用 mock,不依赖外部 LLM 或数据库,一键可跑

3.3 结果与解读#

指标数值来源说明
Tier 3 平均假设数3-5 条配置 tier3_max_hypotheses=5去重后通常 3-4 条(同域假设被合并)
前沿并行宽度2 条PARALLEL_FRONTIER=2前 2 条并行取证,其余串行
迭代预算8 轮tier3_max_iterations=8兼作资源耗尽护栏
单次 Tier 3 耗时30s-3min端到端计时取决于假设数和 subagent 取证深度
单次 Tier 3 成本~¥0.20token 台账假设生成 1 次 + 取证 2-5 次 + 验证 2-5 次
基础置信门槛0.80tier3_confirm_thresholdimpact 加码后实际 0.80-0.95
ESCALATED 兜底全假设未达标时触发设计保证不硬编结论,交人工

局限性: 当前量化数据主要来自单测(mock LLM),不是真实 LLM 的假设生成质量评估。假设质量(假设排序是否与真实根因一致)和验证准确率(confirmed/refuted 判断是否正确)没有用标注数据集做过独立评测。实际效果高度依赖 LLM 的推理能力——换一个推理能力弱的模型,假设质量和验证准确率都会下降。


四、面试问答#

基础题(必问级)#

Q1: 假设驱动跟普通 ReAct 到底有什么区别?#

答: 两者都是 reason-then-act 循环,但结构不同。普通 ReAct 是无目标循环——每一轮 LLM 看上一轮结果决定下一步做什么,没有终止条件的结构化保证,容易发散(LLM 反复查同一类数据、或者跳到无关方向)。假设驱动是先收敛再展开——阶段一生成有限假设排序(把开放问题收敛成 3-5 个可证伪的封闭问题),阶段二逐条定向取证(每步有明确目标),每轮产出结构化状态(confirmed/refuted/insufficient),达标即终止。

三个可量化的差异:(1)token 消耗:ReAct 平均每轮的”reason”环节需要回顾所有历史 context,假设驱动的验证只看当前假设 + 它的证据,context 窗口小得多。(2)可审计性:ReAct 的推理链是自由文本,假设驱动每条假设有明确的 status + confidence + evidence_chain,可以追溯到”哪条证据导致了 confirmed/refuted”。(3)终止确定性:ReAct 的终止依赖 LLM 自判”我觉得够了”(calibration gap),假设驱动的终止是结构化的——置信度 ≥ 门槛就停,遍历完没达标就 ESCALATED。

追问 1: 但假设生成本身不也依赖 LLM 吗?如果假设排序不对,第一条不是根因,岂不是浪费了? 答: 对,假设排序的质量直接影响效率——排序越准,达标越快。但系统不依赖排序完美。前 2 条并行取证(PARALLEL_FRONTIER=2),即使第 1 条不对,第 2 条如果是根因也同时在跑不浪费时间。更重要的是,即使假设排序全错(5 条全 REFUTED),系统的行为是确定的——走到 BEST_EFFORT+ESCALATED 交人工,而不是 ReAct 那样在错误方向上漫游消耗预算。

追问 2: 假设生成时 temperature=0.2,验证时 temperature=0.0,为什么不同? 答: 生成需要微量随机性避免假设趋同——temperature=0 时 LLM 倾向于只给”最安全”的一个方向的变体,0.2 鼓励它覆盖不同故障域。验证是确定性判断——同一组证据对同一条假设的判定应该是一致的,temperature=0 保证可复现性。两个阶段的任务性质不同,采样策略也不同。

Q2: subagent 的分派是怎么做的?为什么不让 LLM 动态选?#

答: 分派靠一张静态映射表 EVIDENCE_TYPE_TO_AGENT——假设声明的 required_evidence_types 里每个类型(metric/log/config/infra/runbook)对应一个 subagent。比如假设说”需要 metric 和 log 证据”,就派 metric_agent + log_agent,两者并行。空/未知类型退化到 metric_agent(默认)。

不让 LLM 动态选的原因:(1)确定性——映射表是确定的,LLM 选择引入不确定性,可能选错或选漏。(2)延迟——动态选需要额外一次 LLM 调用,但假设本身已经声明了证据类型,映射是零延迟操作。(3)安全——如果 LLM 的假设描述里被注入了恶意内容(间接提示注入),动态选可能导致调用不应该调的 subagent。静态映射不受输入内容影响。

subagent 内部是怎么做到”定向”的?关键在注入 prompt:"[定向取证任务] 请围绕以下根因假设收集证据: {hypothesis.description}"。subagent 原本为 V3 deep 图设计(通用取证),加了假设注入后变成围绕特定假设的定向取证——改动最小化,复用了全部 subagent 代码。

追问: 4 个 subagent 够吗?如果需要一种新类型的证据怎么扩展? 答: 扩展点有两个:(1)EVIDENCE_TYPE_TO_AGENT 表加一行映射 + 对应的 subagent 函数(如 trace_agent)。(2)假设生成 prompt 里提到新的证据类型名(让 LLM 知道可以声明它)。约束是 subagent 必须遵循相同的接口契约——输入 stateinput/task_id/incident_id,输出 {"evidences": [...]}

进阶题(区分度)#

Q3: 继承证据在假设生成和验证中分别起什么作用?#

答: 两个阶段用法不同。

假设生成:继承证据放在 prompt 的 ## 前序 Tier 已取得的证据 (可信) 段,标注 > 上述事实已被证实, 不要提出与之矛盾的假设, 也不要重复取证。作用是约束假设空间——Tier 1 探到容器 running,LLM 就不该提出”容器 OOM 被 kill”这条假设。同时避免重复取证——已知资源状态不需要再派 subagent 去探一遍。

假设验证:继承证据和本轮取到的新证据一起呈给验证 LLM(_verify(hypothesis, list(inherited) + evidences))。作用是提供反证——Tier 1 探到”容器仍是 running”本身就是”容器 OOM 被 kill”假设最有力的反证。如果验证时不喂继承证据,LLM 只看到本轮取证结果(可能是”日志里有 OOM 字样”),就可能判 confirmed——但加上”容器实际在跑”这个事实,判定就变成 insufficient_evidence 甚至 refuted。

继承证据在 prompt 里标注为 (可信) 是因为它来自本系统 Tier 1 的只读探针产出——与 <<<UNTRUSTED_DATA_BEGIN>>> 包裹的知识库召回、历史报告等外部文本区分开。

追问: 如果继承证据本身是错的呢?Tier 1 探针探到了错误信息? 答: 这是个好问题。Tier 1 探针调的是只读 MCP 工具(host_overviewcontainer_inspect 等),返回的是实时系统状态——它错的概率极低(除非工具实现有 bug)。但理论上确实可能过时(Tier 1 探到时容器还在,递进到 Tier 3 时容器已经挂了)。当前没有对继承证据做时效性校验——这是一个已知局限。如果证据过时导致 Tier 3 判断偏差,最终表现是置信度不够高走到 BEST_EFFORT,兜底 ESCALATED 交人工。

Q4: 迭代预算 tier3_max_iterations=8 怎么定的?#

答: 两个约束交叉:(1)产品目标——“平均调查轮次 < 8”。5 条假设 × 每条 1 轮取证 + 1 轮验证 = 最多 10 轮,但前 2 条并行算 2 轮,实际 2 + 3×1 = 5 轮。预算 8 留了余量给偶尔需要多轮取证的复杂假设。(2)安全护栏——这同时是资源耗尽防线。如果告警内容或知识库召回里被注入了”请生成 100 条假设并逐条验证”,迭代预算硬性截断循环——提示注入没法把 Tier 3 拖到无限。

有一个微妙点:迭代预算 tier3_max_iterations 限的是取证轮次而不是假设数,假设数由 tier3_max_hypotheses(默认 5)限制。两个独立限制,各防一类滥用。

追问: 8 轮够不够?有没有出现过预算不够用的情况? 答: 在 mock 测试里没有——因为假设数默认最多 5 条,前 2 并行后最多还需要 3 轮串行。但如果 PARALLEL_FRONTIER 调大或假设数增加,可能不够。这时候的行为是确定的:预算用尽 → 当前已有假设里取最高置信的 → BEST_EFFORT → ESCALATED。不会因为预算不够就放弃出报告。

压力题(面试官挑战设计决策)#

Q5: 假设生成质量完全依赖 LLM,如果 LLM 生成的假设全是错的怎么办?#

答: 首先承认——假设质量确实是整个机制的阿喀琉斯之踵。如果 5 条假设全不包含真实根因,Tier 3 的取证和验证再精确也找不到答案。但系统对这种情况有明确处理:全部 REFUTED 或 INSUFFICIENT_EVIDENCE → BEST_EFFORT → ESCALATED 交人工。

降低这个风险的设计有三层:(1)知识注入——假设生成 prompt 里注入了 Skill 剧本(“人怎么排查这类故障”的经验)+ 知识库召回(历史类似问题的文档),LLM 不是从零开始想。(2)互斥约束——要求覆盖不同故障域,防止 5 条假设都集中在一个方向。(3)继承证据——前序 Tier 的事实约束了假设空间,排除已知不是的方向。

更诚实的回答:这是假设驱动 vs 无目标 ReAct 的 tradeoff。假设驱动的失败模式是”假设空间没覆盖到真实根因”,ReAct 的失败模式是”无目标漫游烧完 token 没结论”。前者的失败是结构化的(ESCALATED 有明确原因),后者的失败是模糊的(“ReAct 跑了 20 轮也没说清楚”)。

追问: 有没有考虑过让 Tier 3 在第一轮全 REFUTED 后重新生成一批假设? 答: 考虑过但没做。原因:(1)重新生成需要显式把第一轮的 REFUTED 原因喂回去做”负样本排除”,否则 LLM 可能生成一模一样的假设——这增加了 prompt 复杂度和 token 成本。(2)如果第一批 5 条互斥假设全错,说明当前上下文信息不足以做出有效推理——再来一批大概率还是猜不到,不如 ESCALATED 让人工带着更多背景信息来。(3)迭代预算是安全约束,增加”重生成”循环会让预算管理变复杂。这是一个可以做但选择不做的 tradeoff。

Q6: 你说假设验证的置信度是 LLM 自评的,但 LLM 自评不靠谱——V3 的经验不就证明了这一点吗?#

答: 好问题。V3 的教训是:让 LLM 自评开放式结论的可靠性不靠谱(“你觉得你的根因分析对不对?1-10 分”,经常给 8-9 分但结论错)。但 Tier 3 的设定不同——LLM 评的不是”你的推理对不对”,而是”这组证据是否直接支持这个假设”。任务从自评推理质量变成了判断证据-假设关系,后者的 calibration 好得多。

具体的 guardrail:(1)prompt 要求 confirmed 必须有直接证据支持——“没有直接证据一律 insufficient_evidence,不要靠合理推测凑”。这把置信度锚定在证据质量上,不是 LLM 的主观感觉。(2)temperature=0.0 保证同一组输入同一个判定。(3)即使 LLM 置信度虚高(说 0.95 但实际证据不足),后续处置链路还有三级验证门禁(命令成功→服务健康→指标恢复)兜底——置信度虚高最多让系统尝试自动处置,处置失败会触发补偿回滚。

追问: 有没有想过用多个 LLM 交叉验证同一条假设? 答: 想过。方案是对同一条假设用两个不同模型(或不同 temperature)各验证一次,两者都 confirmed 才算数。好处是降低单模型的 calibration 偏差,代价是验证成本翻倍。当前没做因为百炼 TPM 配额有限——Tier 3 已经是 token 消耗最大的层,再翻倍会更容易触发限流。这是一个”有明确方案但因资源约束暂不实施”的权衡。

Q7: 为什么用 Orchestrator-Workers 而不是自己写一个 ReAct Agent?#

答: Tier 3 的”orchestrator”(run_tier3)是自己写的 Python 循环,不是 LangGraph 节点——它比 LangGraph 的 create_react_agent 更简单也更可控。不用框架的 ReAct Agent 有三个原因:

(1)终止条件是自定义的。框架 ReAct 的终止靠 LLM 说”我不需要再调工具了”或打满 recursion_limit。Tier 3 的终止是置信度门槛——一个数值比较,不是 LLM 判断。

(2)循环结构不是标准 ReAct。标准 ReAct 是 reason→act→observe→reason→…,Tier 3 是 generate→(verify→act→judge)×N——假设生成只做一次,后面是逐条取证+判定的循环。硬套 ReAct Agent 需要把”阶段一生成”和”阶段二取证”塞进同一个循环,不自然。

(3)并行策略不标准。前 2 并行 + 其余串行 + CONFIRMED 提前终止——这个调度逻辑在框架 Agent 里很难表达,自己写 asyncio.gather + for 循环反而清晰。

代价:不经 LangGraph 就没有 checkpoint——Tier 3 中途 Worker 挂了需要从头跑。这是可接受的 tradeoff:Tier 3 耗时 30s-3min,从头跑的成本不大;而加 checkpoint 需要序列化假设状态和 subagent 上下文,复杂度显著增加。


五、前沿概念与延伸#

5.1 ReAct / Reflexion / Tree-of-Thought#

是什么: 三种 LLM 推理范式。ReAct(Reason+Act)是 LLM 交替推理和调用工具的循环——每轮先 reason(“我应该查什么”)再 act(调工具),结果喂回下一轮。Reflexion 在 ReAct 基础上加了自我反思——每轮结束后 LLM 评估”我的推理是否有效”,如果无效则调整策略重来。Tree-of-Thought (ToT) 把推理过程建模为树——每步生成多个候选思路,评估后选最优的展开,可回溯。

为什么要这么做: 单次 LLM 调用的推理深度有限,复杂问题需要多步推理+工具交互。ReAct 是最基本的多步框架;Reflexion 解决 ReAct “犯了错不纠正”的问题;ToT 解决 ReAct “只有一条路径不回溯”的问题。

这么做的理由: 本项目的 Tier 3 借鉴了 ReAct 的 reason→act 循环和 ToT 的”多候选”思路,但做了定制:(1)假设生成对应 ToT 的”生成多候选”,但不做树形展开(展开一层就够,因为假设是互斥的不需要组合)。(2)逐条取证验证对应 ReAct 的 reason→act→observe,但每轮的目标是固定的(验证特定假设),不是 LLM 自由决定。(3)BEST_EFFORT→ESCALATED 对应 Reflexion 的”反思后放弃”——但不是 LLM 自己决定放弃,而是置信度不够时结构化放弃。

举例说明: HolmesGPT(开源 AIOps 工具)用类似 ReAct 的 investigation 模式——Agent 自由决定调什么工具、查什么方向。Anthropic 的《Building Effective Agents》推荐 Orchestrator-Workers 模式——orchestrator 分解任务给 worker,每个 worker 专注一个子任务。本项目的 Tier 3 更接近后者:run_tier3 是 orchestrator,metric_agent/log_agent 等是 workers。

延伸问答:

面试官:“ReAct 和假设驱动的适用场景分别是什么?” 答:ReAct 适合探索性任务——不知道该查什么,需要 LLM 自己发现方向。典型场景:客服聊天机器人、通用问答。假设驱动适合诊断性任务——问题空间有限(故障域就那么几个),需要系统性排除。典型场景:故障排查、医疗诊断、代码 debug。两者的核心区别:ReAct 的搜索空间是开放的(下一步做什么由 LLM 决定),假设驱动的搜索空间是封闭的(假设在阶段一就确定了,阶段二只做验证)。

5.2 Hypothesis-Driven Debugging(假设驱动调试)#

是什么: 一种系统化的故障排查方法论——先基于现象生成可能的根因假设,按可能性排序,然后逐条设计验证实验收集证据,证实或证伪。来源于科学方法论(假说-演绎法),在 SRE 领域由 Google SRE Handbook 推广。

为什么要这么做: 运维排障的常见反模式是”想到什么查什么”——经验丰富的 SRE 会本能地先查最可能的原因,但经验不足的或者 LLM Agent 容易在不相关的方向上浪费时间。假设驱动把排障过程结构化:(1)限定了搜索范围(只验证已生成的假设)。(2)每步有明确目标(验证特定假设)。(3)终止条件清晰(证实或排除所有假设)。

这么做的理由: 与无目标 ReAct 的核心差异:ReAct 是”我看到 CPU 高,那我去查什么呢”——下一步取决于 LLM 当前的判断。假设驱动是”我有 4 条假设,#1 说 CPU 高因为大事务,#2 说因为连接风暴——我先验证 #1,需要查慢查询日志”——下一步取决于假设声明的证据需求,不取决于 LLM 的临时判断。前者的路径是动态的(不可预测),后者是半静态的(假设生成后路径基本确定)。

举例说明: Google SRE Handbook 的 “Troubleshooting” 章节描述了类似流程:triage → examine → diagnose → test/treat。Microsoft RCACopilot 用检索增强的方式辅助假设生成(从历史案例检索候选根因)。本项目的 Tier 3 融合了两者:检索增强(继承证据 + 知识库注入)+ 结构化假设(Pydantic HypothesisDraft)+ 定向取证(subagent 分派)。

延伸问答:

面试官:“假设驱动和 Chain-of-Thought (CoT) 有什么关系?” 答:CoT 是 LLM 在单次推理中展开中间步骤(“让我们一步步想”),整个推理在一次 LLM 调用内完成。假设驱动是多轮交互——假设生成是一次调用,每条假设的取证和验证又各是独立调用,中间穿插了工具调用和实际数据采集。CoT 的中间步骤是 LLM 自己推导的(可能幻觉),假设驱动的中间结果是工具返回的实测数据。两者可以组合:假设生成时用 CoT 让 LLM 推导”为什么这个假设可能性最高”,验证时用工具实测而不是 CoT 推导。

5.3 Orchestrator-Workers 模式(Anthropic 推荐)#

是什么: Anthropic《Building Effective Agents》提出的多 Agent 编排模式——一个 orchestrator 负责分解任务和汇总结果,多个 worker 各自专注执行子任务。Worker 之间不直接通信,只通过 orchestrator 协调。

为什么要这么做: 单个 Agent 处理复杂任务时面临两个问题:(1)工具集过大——20+ 个工具让 LLM 选择困难。(2)上下文膨胀——所有取证结果堆在一个 context 里,LLM 难以聚焦。Orchestrator-Workers 通过分治解决:每个 worker 只看少量工具和与自己相关的上下文。

这么做的理由: 本项目 Tier 3 的实现就是 Orchestrator-Workers:run_tier3 是 orchestrator(生成假设、调度取证、汇总结论),metric_agent/log_agent/infra_agent/runbook_agent 是 workers(各自有专门的工具白名单和 prompt)。与 Anthropic 推荐的差异:(1)Anthropic 的 orchestrator 通常也是 LLM,本项目的 orchestrator 是 Python 代码——因为调度逻辑是确定性的(按假设排序取证),不需要 LLM 决策。(2)Worker 之间通过共享 evidence_sink 间接通信——一条假设的取证结果可能成为另一条假设的反证。

举例说明: Claude Code 的 Agent tool 是典型的 Orchestrator-Workers——主 Agent 分派子任务给 subagent,subagent 独立执行后返回结果。LangGraph 的 send API 也支持类似的 fan-out 模式。

延伸问答:

面试官:“Orchestrator-Workers 和 Map-Reduce 有什么区别?” 答:Map-Reduce 的 reduce 阶段是聚合——所有 map 结果汇总后做一次性处理。Orchestrator-Workers 的 orchestrator 可以逐步决策——一个 worker 返回后就可以决定”够了,不需要派其余 worker”。本项目的”CONFIRMED 提前终止”就是这种逐步决策——第 2 个假设验证通过后立即停止,不等剩余假设的 worker 结果。Map-Reduce 做不到这个——它必须等所有 map 完成后才能 reduce。


六、诚实边界#

  1. 假设质量依赖 LLM 推理能力: 假设生成是整个机制的上限——如果 LLM 的 5 条假设没覆盖到真实根因,后续取证和验证再精确也无济于事。当前通过知识注入(Skill 剧本 + RAG 召回 + 继承证据)降低这个风险,但没有从结构上消除它。一个可能的改进方向是”假设重生成”——第一轮全 REFUTED 后基于 REFUTED 原因生成新一批假设。当前没做,原因见 Q5 追问。

  2. 验证准确率未独立评测: LLM 做假设验证(“这组证据是否直接支持这条假设”)的准确率没有用标注数据集做过系统评测。只有 mock 单测验证了结构正确性(结构化输出能解析、状态流转正确),没有评过”判定结论是否与人工标注一致”。这意味着 confirmed 可能虚高(证据不够充分但 LLM 判了 confirmed)或虚低(证据充分但 LLM 判了 insufficient)。

  3. Tier 3 没有 checkpoint,中途失败从头跑: run_tier3 是纯 Python 循环,不经 LangGraph checkpointer。Worker 在 Tier 3 执行中途崩溃时,整个 Tier 3 需要从假设生成开始重跑。对于耗时 30s-3min 的 Tier 3 来说尚可接受,但如果未来假设数或取证深度增加,需要考虑增加中间 checkpoint。

  4. subagent 复用 V3 代码,取证深度有限: metric_agent/log_agent 等 subagent 是为 V3 deep 模式设计的通用取证 Agent,每个内部只有 3 轮 LLM↔工具交互上限(max_iters=3)。对于需要多轮工具调用才能定位的复杂假设(如”网络间歇性丢包需要连续 ping 5 次”),3 轮可能不够。当前靠假设拆分缓解——一个复杂根因可能被拆成多条假设,每条的取证需求更简单。

  5. 并行取证的 token 成本不可控: 前 2 条假设并行取证,如果第 1 条很快 CONFIRMED,第 2 条的取证已经在跑了,其 token 消耗无法追回。PARALLEL_FRONTIER=2 是经验值,没有基于成本优化做过系统调参。在百炼 TPM 配额紧张的环境下,可以考虑降到 1(纯串行)换取可预测的 token 消耗。