# 主题深读:证据归因幻觉 + 现行质量门禁 **用途**:巩固知识 + 面试准备 + 举一反三 **不是**:逐字讲稿、旧 Issue 复述、过时五段流水线说明书 **材料日期**:2026-07-24 **叙事原则**:**以现行设计为主讲;早期 Issue 只说明「问题从哪来」** **主题定位**:质量辨识度主故事——「工具调了,结论为何仍不能直接给用户」 **现行依据(面试默认口径)** | 层级 | 路径 | |---|---| | 架构 | `mvp/architecture/current-mvp-architecture.md` | | 编排 | `mvp/architecture/agent-orchestration.md` | | 门禁 | `mvp/architecture/harness-quality-gates.md` | | Draft 契约 | `.../harness/contract/DiagnosisDraft.java`、`AnalysisKind.java` | | 物理验真 | `.../harness/guard/evidence/EvidenceGuard.java` | | 语义审查 | `.../harness/guard/semantic/SemanticGuard.java`、`semantic-guard-prompt.md` | | 发布 | `.../harness/release/DiagnosisReleaseUseCase.java`、`SafeFallbackFactory.java` | | Agent 规则 | `src/main/resources/prompts/diagnosis-agent-prompt.md` | **历史依据(只作起源,不代表 runtime)** - `mvp/issues/archived/executor-evidence-attribution-hallucination.md`(2026-07-07,旧 Executor 链路) - Phase2 Gatekeeper / `executor_evidence_v2` 归档文档 **配套** - 演进骨架 → [architecture-evolution-deep-dive.md](architecture-evolution-deep-dive.md) - 故事索引 → [issues-interview-stories.md](issues-interview-stories.md) §3.1 --- ## 0. 怎么用 / 怎么讲 | 目标 | 用法 | |---|---| | 巩固 | 先背现行三道门与 `DiagnosisDraft` 引用闭包,再看历史问题为何逼出这套设计 | | 面试 | **先讲现在怎么拦,再补一句早期怎么发现**;禁止把主链路讲成 Planner→Executor→Verifier | | 举一反三 | 用现行组件名迁移到客服/代码/合规场景 | **开场主线(先背这句,现行口径)** > 当前系统里,Diagnosis Agent 是唯一报告作者,它在 ReAct 里查只读工具并写出结构化 `DiagnosisDraft`。 > 但 Draft **默认不能出门**:必须先过 **EvidenceGuard(0 LLM,验 tool_call 归属与投影)**,再过 **SemanticGuard(隔离单轮,判是否越证)**,最后由 **Release Policy** 决定公开报告还是 SAFE_FALLBACK。 > 这套门禁要防的核心失败,是早期线上已经见过的 **证据归因幻觉**:有工具调用,却把 runbook/常识写成「本 Run 已证实事实」。 **禁止的过时口径** | 不要说 | 要说 | |---|---| | 我们主链路是 Planner→Executor→Gatekeeper→Verifier→Composer | 单 Diagnosis Agent + Harness 门禁 | | Gatekeeper 验 `source_invocation_id + raw_path + excerpt` | EvidenceGuard 验 `tool_call_id` + 当前 Run canonical/projection | | Verifier 输出 LOW_CONFID/PASS | SemanticGuard 输出 `SUPPORTED` / `UNSUPPORTED` | | 工具有 metrics/Prometheus | 现行诊断 Tool:`lookup_knowledge` / `query_logs` / `query_mysql` | | 引用主键是 DB `tool_invocation.id` | 框架 `tool_call_id`;完整调用在 Redis canonical | **四轴(现行)** 1. **谁写报告**:仅 Diagnosis Agent 2. **谁保证引用真**:EvidenceGuard + Redis canonical(Harness only) 3. **谁保证语义不越界**:SemanticGuard(无 Tool、无记忆) 4. **谁决定用户看见什么**:Release Policy(Draft ≠ 公开 SSE) **图目录** | 图 | 位置 | 白板优先级 | |---|---|---| | 现行主链(质量视角) | §1 | ★★★ | | DiagnosisDraft 引用闭包 | §2 | ★★★ | | EvidenceGuard 校验步骤 | §3 | ★★★ | | AnalysisKind × EvidenceStatus | §3.3 | ★★ | | SemanticGuard 输入冻结 | §4 | ★★★ | | Release / Repair / Fallback | §5 | ★★★ | | 数据三层如何服务验真 | §6 | ★★ | | 历史问题 → 现行映射(30 秒) | §8 | ★★ | | 白板速画 | §13 | ★★★ | --- ## 1. 现行设计:质量门禁长什么样 ### 1.1 在系统中的位置 ```text POST /api/chat (SSE) → ChatApplicationUseCase → Intent Router → DIAGNOSIS → Diagnosis ReAct Agent 只读 Tool × 3,观察的是 projection 产出 DiagnosisDraft → DiagnosisReleaseUseCase EvidenceGuard →(可选 EvidenceRepair 一次)→ SemanticGuard → Release → 公开 content | SAFE_FALLBACK | failure ``` ```mermaid flowchart TB Q["query + safe previous_turn"] --> A["Diagnosis Agent
唯一报告作者 · ReAct"] A <--> TB["ToolBoundary"] TB --> Redis["Redis canonical
Harness only"] TB --> Proj["bounded agent_result"] Proj --> A A --> Draft["DiagnosisDraft
内部制品"] Draft --> RU["DiagnosisReleaseUseCase"] RU --> EG["EvidenceGuard · 0 LLM"] EG -->|invalid| RP["EvidenceRepair 最多一次"] RP --> EG EG -->|valid snapshot| SG["SemanticGuard · 隔离单轮"] SG -->|SUPPORTED| Out["Release SUCCESS
公开 typed report"] SG -->|UNSUPPORTED / 不可用| FB["SAFE_FALLBACK"] EG -->|仍 invalid| FB ``` **面试一句话** > Agent 负责「尽量基于证据写对」;Harness 负责「写错了也不能当成功答案发出去」。 ### 1.2 职责切分(现行,必背) | 组件 | 做 | 不做 | |---|---|---| | **Diagnosis Agent** | 规划、调 Tool、写完整 Draft、证据不足时写 limitations | HTTP/SSE、预算、物理验真、语义终审、发布 | | **ToolBoundary** | schema/只读/预算、写 canonical、projector、只回有界观察 | 业务推理 | | **EvidenceGuard** | Draft 结构、analysis 闭包、`tool_call_id` 属本 Run、READY/可引用、kind↔status、投影可解析 | 调模型、改报告语义 | | **EvidenceRepair** | 在证据失败时尝试一次结构化修复 | 无限重试、绕过验真 | | **SemanticGuard** | 在 verified snapshot 上判断整份报告是否被支持 | Tool、记忆、Redis、改写报告、部分放行 | | **Release** | SUPPORTED 才公开 Draft;否则固定 fallback/failure | 把未验证 Draft 流式出去 | 对应代码入口:`DiagnosisReleaseUseCase.execute(run, query, draft)`。 ### 1.3 和「纯 ReAct Demo」的差(质量视角) ```text 纯 ReAct: Model ↔ Tools → 文本直接给用户 现行: Model ↔ ToolBoundary/projection → DiagnosisDraft → EvidenceGuard → SemanticGuard → Release → SSE + run 预算/取消 + Timeline(EVIDENCE/SEMANTIC/RELEASE 事件) ``` --- ## 2. 现行契约:`DiagnosisDraft` 如何逼归因诚实 ### 2.1 结构(代码事实) ```text DiagnosisDraft conclusion? text based_on_analysis_ids[] ← 必须指向本 Draft 内 analysis_id analysis[] analysis_id ← 唯一 kind: NORMAL | NEGATIVE_OBSERVATION text tool_call_ids[] ← 至少一个;必须是本 Run 真实 id action_plan[] action based_on_analysis_ids[] requires_human_confirmation recommendations[] text based_on_analysis_ids[] limitations ← 必填 scope(EvidenceGuard 校验) scope missing_info[] ``` ```mermaid flowchart TB C["conclusion / actions / recommendations"] -->|"based_on_analysis_ids"| A["analysis_id"] A -->|"tool_call_ids"| T["本 Run tool_call_id"] T --> Canon["Redis canonical
runId + toolCallId"] Canon --> Status["evidence_status
FOUND / NO_EVIDENCE"] A --> Kind["kind NORMAL / NEGATIVE"] Kind -.->|"must match"| Status ``` **设计意图(对着归因幻觉)** | 约束 | 防什么 | |---|---| | 每条 analysis 必须带 `tool_call_ids` | 「凭空结论」「常识当观测」 | | conclusion 不直接绑 tool,只绑 analysis | 结论必须落在已声明的分析链上,形成闭包 | | `limitations` 强制存在 | 证据不足时不能装成完整结案 | | `kind` 二分 | 负向观察与正向命中不能混用同一类证据状态 | ### 2.2 Agent Prompt 里的硬规则(现行) `diagnosis-agent-prompt.md` 关键口径(面试可直接引用思想,不必背原文): 1. **唯一报告作者**;内部规划,不对外输出 CoT。 2. **PreviousTurn 不是本 Run 证据**,不能引用其 tool_call_id。 3. 只有 `evidence_status=EVIDENCE_FOUND|NO_EVIDENCE` 且带真实 `tool_call_id` 的观察才能当证据。 4. **NORMAL** 只能引 FOUND;**NEGATIVE_OBSERVATION** 只能引 NO_EVIDENCE。 5. **NO_EVIDENCE ≠ 系统健康 / 已排除根因**。 6. Tool **ERROR 不是证据**,禁止引用。 7. 证据不足:`conclusion=null`,写 scope/missing_info,**禁止编根因**。 8. 不暴露 raw、凭据、内部错误、hidden reasoning。 **金句** > Prompt 负责教 Agent「怎么写才诚实」;EvidenceGuard 负责「写得不诚实就过不了」。两者缺一不可,但**安全边界在代码**。 ### 2.3 和早期「分区文案」的关系(30 秒) 早期 Issue 要求 Executor 输出「已证实 / 推测 / 缺口 / 动作」分区。 现行不是同一套 JSON 字段名,但**语义被结构吸收了**: | 早期分区意图 | 现行落点 | |---|---| | 已证实事实 | `analysis`(NORMAL + FOUND) | | 负向观察 | `analysis`(NEGATIVE_OBSERVATION + NO_EVIDENCE) | | 证据缺口 | `limitations.missing_info` + 可空 `conclusion` | | 建议动作 | `action_plan` / `recommendations`(必须 based_on analysis) | | 禁止常识当事实 | Guard 不认无 tool_call 的 analysis;Semantic 审越证 | --- ## 3. EvidenceGuard:现行物理验真(0 LLM) ### 3.1 它在防什么 不是「这句话好不好听」,而是: > 报告里声明依赖的每一个 tool_call,是否真的在本 Run 发生过、状态是否可引用、投影是否自洽、analysis kind 是否与 evidence_status 匹配,以及 conclusion/action 是否只引用存在的 analysis。 ### 3.2 校验流水(按代码路径讲) `EvidenceGuard.validate(RunContext, DiagnosisDraft)` 大致两段: **A. Draft 结构与报告闭包** - draft / analysis 非空 - `analysis_id` 存在且唯一 - `kind`、`text` 必填 - 每条 analysis 的 `tool_call_ids` 非空 - conclusion / action_plan / recommendations:text 非空,且 `based_on_analysis_ids` 非空、id 都认识 - `limitations.scope` 必填 **B. 逐 tool_call 验真** 对每个 `tool_call_id`: 1. 用 `runId + toolCallId` 生成 key,查 **Redis canonical** 2. 找不到 → `INVOCATION_MISSING`(典型:编造 id) 3. id 不一致 → `INVOCATION_ID_MISMATCH` 4. `!isReferencableBy(runId)` 或 agent_result 空 → `INVOCATION_NOT_REFERENCABLE` (跨 Run、未 READY、不可引用状态) 5. `analysis.kind.accepts(invocation.evidenceStatus)` - NORMAL ↔ EVIDENCE_FOUND - NEGATIVE_OBSERVATION ↔ NO_EVIDENCE 否则 `EVIDENCE_KIND_MISMATCH` 6. 按 tool 反序列化 **agent_result 投影**(不是让模型再读 raw 讲故事): - `lookup_knowledge` / `query_logs` / `query_mysql` 7. 投影内 `tool_call_id`、`evidence_status` 与 canonical 一致 8. FOUND 必须有可展示证据条目;NO_EVIDENCE 必须空列表且 count=0 9. 通过则写入 `VerifiedEvidence`,汇总为 `VerifiedEvidenceSnapshot` ```mermaid flowchart TB Draft["DiagnosisDraft"] --> S["结构 + analysis 闭包"] S --> Loop["foreach tool_call_id"] Loop --> Key["key = runId + toolCallId"] Key --> Redis["Canonical store"] Redis -->|missing| V1["INVOCATION_MISSING"] Redis -->|not referencable| V2["NOT_REFERENCABLE"] Redis -->|ok| K["kind vs evidence_status"] K -->|mismatch| V3["KIND_MISMATCH"] K --> P["Parse projection"] P -->|invalid| V4["PROJECTION_*"] P --> Snap["VerifiedEvidenceSnapshot"] ``` ### 3.3 `AnalysisKind` × `EvidenceStatus`(高频考点) ```text NORMAL → 只能绑 EVIDENCE_FOUND NEGATIVE_OBSERVATION → 只能绑 NO_EVIDENCE ERROR → 根本不是证据(Agent Prompt + 边界) ``` **面试例子** | Agent 想说 | 错误绑法 | Guard | |---|---|---| | 「CPU 告警 92%」 | 编造 tool_call_id | MISSING | | 「未查到池耗尽日志」 | kind=NORMAL 却绑 NO_EVIDENCE | KIND_MISMATCH | | 「已排除内存泄漏」 | 仅 NO_EVIDENCE 却写排除结论 | 物理可能过,**Semantic 应 UNSUPPORTED** | | 引用上一轮 previous_turn 的 id | 非本 Run canonical | MISSING / NOT_REFERENCABLE | ### 3.4 为什么验的是 projection 且 canonical 在 Redis | 设计 | 原因 | |---|---| | Agent 只见 projection | 有界 ACI;降低上下文里的 debug 噪声与胡拼素材 | | Guard 读 canonical 元数据 + 校验投影 | 确认「Agent 引用的 id」对应真实调用,且投影自洽 | | Agent 不能访问 Redis | 防止自己翻 raw 再编第二套故事 | | MySQL `tool_invocation` 只 metadata | 长期审计 ≠ 验真主存;完整 raw 短 TTL | **与早期 Gatekeeper 的差异(讲清楚就加分)** | 早期 Gatekeeper | 现行 EvidenceGuard | |---|---| | 多角色流水线中的一环 | Harness Release 路径上的确定性步骤 | | `source_invocation_id` + `raw_path` + `excerpt` 字符串闭合 | `tool_call_id` + Run ownership + 投影结构/状态闭合 | | 面向 `executor_evidence_v2` claims | 面向 `DiagnosisDraft` analysis 闭包 | | 工具集合含 metrics 等 | 现行三 Tool;投影类型 Rag/Logs/Mysql | 语义继承:**都是 0 LLM 的物理/契约验真**;协议与装载层已现代化。 ### 3.5 失败码怎么用于口述 挑几个最能讲故事的 `EvidenceViolationCode`: | Code | 一句话 | |---|---| | `TOOL_REFERENCE_MISSING` | analysis 根本没绑工具 | | `INVOCATION_MISSING` | 引用了不存在的 tool_call(归因造假) | | `INVOCATION_NOT_REFERENCABLE` | 调用存在但不可作为证据(错 Run/未就绪/空结果) | | `EVIDENCE_KIND_MISMATCH` | 负向/正向证据用错 kind | | `ANALYSIS_REFERENCE_UNKNOWN` | 结论引用了不存在的 analysis_id | | `PROJECTION_INVALID` | 投影与契约不一致,不能当干净证据 | --- ## 4. SemanticGuard:现行语义保险丝 ### 4.1 输入被故意冻死 `SemanticGuardInput.from(query, draft, verifiedSnapshot)`: ```text 只给: 原始 query + 完整 Draft 视图 + EvidenceGuard 产出的 verified snapshot 明确没有: Tool / 记忆 / Redis / 主 Agent 回调 / 诊断历史 ``` Prompt(`semantic-guard-prompt.md`)要求: - 查:evidence 是否支持各 analysis;analysis 是否支持 conclusion;action/recommendation 是否越界;limitations 是否如实 - **禁止**改写、纠正、摘要扩展、**部分批准** - 只返回 `{"verdict":"SUPPORTED|UNSUPPORTED","reason":"..."}` ### 4.2 它专门接住 EvidenceGuard 接不住的归因幻觉 EvidenceGuard 通过只说明: > 「你引用的调用是真的,投影也合法。」 仍可能: | 漏洞 | 例子 | 谁拦 | |---|---|---| | 真日志推不出该根因 | 只有超时日志 → 写「确定是死锁」 | SemanticGuard | | 负向观察说成排除 | NO_EVIDENCE → 「不可能是池耗尽」 | SemanticGuard + Agent 规则 | | 结论超出 analysis 集合语义 | analysis 只谈 A,conclusion 谈 B | SemanticGuard | | 建议动作无分析支撑 | 乱给变更建议 | SemanticGuard + 结构上 based_on | ```mermaid flowchart LR EG["EvidenceGuard
物理真"] --> SG["SemanticGuard
语义立"] SG -->|SUPPORTED| R["可发布"] SG -->|UNSUPPORTED| F["Fallback
保留 observed_facts"] ``` ### 4.3 为什么必须隔离、且无 Tool | 若 SemanticGuard 能再查库 | 后果 | |---|---| | 自建第二证据世界 | 与主 Agent / snapshot 不一致 | | 「审稿时补证」 | 绕过用户可见的排查过程 | | 又变成带 Tool 的第二 Executor | 归因问题换个角色重演 | **金句** > SemanticGuard 是保险丝,不是第二名侦探。 ### 4.4 技术失败策略(现行) - 输入/输出字节上限(`SemanticGuardLimits`) - 同输入有限重试;仍失败 → `semanticUnavailable` fallback(有 snapshot 时仍可带已验证事实) - 不把内部异常原文甩给用户 --- ## 5. Release:Draft 与公开通道切断 ### 5.1 `DiagnosisReleaseUseCase` 决策序 ```text 1) EvidenceGuard.validate 2) 若失败 → EvidenceRepair 一次 → 再 validate 3) 仍失败 → EVIDENCE_VALIDATION_FAILED fallback 4) SemanticGuard.review(query, draft, snapshot) 5) SUPPORTED → SUCCESS(公开 candidate Draft + snapshot 元数据路径) 6) UNSUPPORTED → SEMANTIC_UNSUPPORTED fallback(可带 observed_facts) 7) Semantic 技术不可用 → SEMANTIC_UNAVAILABLE fallback ``` Timeline 会记:`EVIDENCE_GUARD_INITIAL` / `RECHECK` / semantic / release 决策(普通 Trace 可回放阶段,不靠「感觉」)。 ### 5.2 SAFE_FALLBACK 在防什么 不是空白 500,而是**可信的不完整**: | Fallback 类型 | 用户侧含义(思想) | |---|---| | 证据校验失败 | 引用/结构没过,不能确认根因;可带 validation 问题方向 | | 语义不支持 | 已有可验证事实,但撑不起当前根因结论 | | 语义不可用 | 有事实,但审不过/审不了,暂不发根因 | `SafeFallbackFactory` 会从 snapshot 抽取有界 `observed_facts` / sources(有上限),并给出 `failure_stage`、`next_steps` 等——**在不泄 Prompt/raw/内部 Draft 细节的前提下**尽量可操作。 **金句** > 我们宁可发布「已经核实到什么、卡在哪」,也不发布「流畅但未过门禁的完整故事」。 ### 5.3 PreviousTurn 与门禁的衔接 - 仅 **同 Session、最近一次 DIAGNOSIS + SUCCESS + published_result** 可进下一轮 - Fallback/失败/raw **不进** PreviousTurn - Prompt 明确:previous_turn **不可当本 Run 证据** 防止「上一轮没过门禁的句子」在下一轮被当成已证实事实——这是归因幻觉的跨轮版本。 --- ## 6. Tool 边界:归因幻觉的上游防线 门禁是下游闸门;上游仍要减少「胡拼素材」。 ```text framework tool_call_id → exact Run / schema / read-only / budget → 执行 → Redis canonical(request/raw/agent_result/status) → projector → bounded agent_result → 只把 projection 给 Agent → MySQL 仅 metadata audit ``` 现行 Agent 可见 Tool 固定三个: - `lookup_knowledge` - `query_logs` - `query_mysql` **与归因的关系** | 机制 | 作用 | |---|---| | 投影有界 | 少把 rerank/debug 大字段留给模型拼案情 | | 统一 evidence_status | FOUND/NO_EVIDENCE/ERROR 语义稳定,供 kind 匹配 | | 每调必有 tool_call_id | Draft 绑定有稳定主键 | | 禁止 Agent 见 Redis | 不能「翻完整 raw 再假装引用」 | --- ## 7. Reasoning 明确不是证据(现行安全边界) 架构硬约束: - Provider reasoning 若存在,进独立 `agent_reasoning_audit` - **不进**普通 SSE、Trace 正文、Evidence Snapshot、业务判断 - **不能**绕过 EvidenceGuard / SemanticGuard - 未返回则记 unavailable,**禁止伪造** 面试若被问「你们保存思考过程吗」: > 审计与事实分离。思考不是 tool evidence,更不能当发布依据。 --- ## 8. 历史问题:只用来回答「为什么要这套现行设计」 ### 8.1 早期现象(30–45 秒够) 2026-07 旧 Chat 链路(Planner/Executor/Verifier…)上: - 多次 E2E:`lookup_* / logs / metrics` 有调用,仍大量 `LOW_CONFID` - 答案出现工具 raw 中不存在的「精确事故事实」(OOM 次数、慢 SQL 秒数等) - 根因命名:**证据归因幻觉**——把 runbook/常识/他服事实写成「本会话已证实」 - 曾伴随摘要失真、自证闭环(自己总结再自己绑引用) **正确用法** > 这段证明「只靠模型自觉 + 事后 LLM Verifier」不够,必须把物理引用做成确定性约束,并把发布权从生成模型手里拿走。 **错误用法** > 把整场面试讲成旧五段角色和 `executor_evidence_v2` 字段细节,却说不清现在的类名与 API。 ### 8.2 语义迁移表(历史 → 现行) | 历史概念 | 现行概念 | 说明 | |---|---|---| | Executor 综合答案 | Diagnosis Agent 写 Draft | 仍是模型生成,但是唯一作者 | | claim + excerpt binding | analysis + `tool_call_ids` | 主键协议变更 | | Gatekeeper | EvidenceGuard | 仍 0 LLM;装入 Release 用例 | | Verifier LOW_CONFID | SemanticGuard UNSUPPORTED | 隔离输入;二元 verdict | | Composer 控表达 | Draft 结构 + Release/Fallback | 表达权在 Agent,发布权在 Harness | | 调低阈值换 PASS | **明确不做** | 用 fallback 信息量换体验 | ```mermaid flowchart LR H1["早期:归因幻觉被发现"] --> H2["正确性模型:物理验真+语义审+控表达"] H2 --> H3["ISS-014:装载到单 Agent + Harness"] H3 --> Now["现行:Draft→EG→SG→Release"] ``` ### 8.3 一句话定位两阶段 > 早期 Issue 解决的是 **「要什么正确性」**; > 现行架构解决的是 **「正确性如何成为默认运行路径」**。 --- ## 9. 和业界主流的区别(用现行组件说) | 常见做法 | 缺口 | 本项目现行 | |---|---|---| | 纯 ReAct 直接吐最终答案 | 无发布闸 | Draft 默认内部,Release 才公开 | | 答案末尾 sources 角标 | 角标可假 | `tool_call_id` 必须在本 Run canonical 可解析 | | 单一 LLM-as-Judge | 真伪与语义混判、不可复现引用检查 | EG 代码 + SG 隔离模型 | | 质检 Agent 再带 Tool | 第二证据世界 | SG 无 Tool,冻结 snapshot | | 离线 RAGAS | 不挡单次错误出门 | 在线门禁 + Timeline + eval 夹具 | | 只靠更强模型 | 无工程边界 | 与模型代际正交的 Harness | **三个不一样(现行表述)** 1. **引用是 Run 级所有权问题**,不是文案装饰。 2. **物理与语义拆分**,失败阶段可进 Trace / fallback。 3. **公开通道与生成通道切断**,SUPPORTED 才是成功产品语义。 --- ## 10. 决策环(填的是现行答案) | # | 问题 | 现行答案 | |---|---|---| | 1 | 威胁? | 带 tool 外观的完整假案情进入 SSE | | 2 | 谁强制? | EG 强制引用;SG 强制语义;Release 强制发布 | | 3 | 数据面? | canonical / projection / metadata audit;reasoning 另表 | | 4 | 失败用户看到? | SAFE_FALLBACK(阶段、有界事实、下一步),非假成功 | | 5 | 如何证明? | 单元/契约测 violation;E2E release_outcome;Timeline 事件 | | 6 | 演进? | 误杀先查投影与 schema/Repair;不给 SG 加 Tool;停止策略见 ISS-015 | --- ## 11. 面试题库(默认用现行答) ### 11.1 90 秒主叙述(推荐背这个版本) > 故障诊断里最危险的不是完全不查工具,而是查了一点真实信号就补成完整事故——我们早期在旧链路上把它定义为证据归因幻觉。 > **现在**的做法是:唯一 Diagnosis Agent 用 ReAct 查三个只读工具,只看投影,输出结构化 DiagnosisDraft;每条分析必须绑定本 Run 的 tool_call_id。 > Draft 先过 EvidenceGuard:纯代码检查结构闭包、调用是否存在于当前 Run 的 Redis canonical、证据状态是否与 NORMAL/负向观察匹配、投影是否自洽。 > 通过后生成 verified snapshot,再交给无工具的 SemanticGuard 做整份语义是否越证的审查。 > 只有 SUPPORTED 才经 Release 进入 SSE;否则走 SAFE_FALLBACK,宁可告诉用户已核实事实和卡住的阶段,也不发未验证根因。 > 所以质量辨识度是三句话:**可调用 ≠ 可归因;可归因 ≠ 语义成立;语义成立才可发布。** ### 11.2 为什么题 | 问题 | 现行得分点 | |---|---| | 怎么防幻觉? | Draft 绑定 tool_call_id → EG → SG → Release | | 为什么 EG 不用模型? | 归属与状态是确定性的;要可回归 | | 为什么还要 SG? | 真调用推不出假根因;负向≠排除 | | 为什么 SG 不能有 Tool? | 冻结证据集,防第二世界 | | 引用主键为什么是 tool_call_id? | 框架协议 id;与当次调用一致;不靠「最新 DB 行」 | | Agent 能看 raw 吗? | 不能;只看 projection;canonical Harness only | | 证据不足怎么办? | conclusion 可空 + limitations;或 fallback;不编根因 | | 和旧 Gatekeeper 啥关系? | 语义祖先;现装在 Harness,协议已换 | ### 11.3 对抗题 **Q:这不就是多 Agent 质检吗?** A:不是。业务侧只有一个带 Tool 的 Diagnosis Agent。SG 是无 Tool 的隔离单轮审查,属于 Harness 控制面,不是协作同事。 **Q:你们重构掉多角色后质量是不是弱了?** A:弱的是重复的 LLM 角色编排;强的是默认路径上的确定性 EG + 发布切断。正确性模型保留,装载点从流水线角色变成 Release 用例。 **Q:投影校验不看 raw 原文子串,会不会漏?** A:现行 EG 强调 **调用所有权 + 状态 + 投影结构自洽 + kind 匹配**,再交给 SG 做语义。上游靠 projector 把可引用证据做成稳定结构。若追问 excerpt 级闭合,可承认协议从早期 raw_path/excerpt 演进到投影契约,并强调 **不能引用 ERROR/跨 Run/不可引用调用** 仍是硬的。 **Q:用户体验会不会总是 fallback?** A:体验做在「信息化 fallback + 成功路径的 limitations」,不是放宽 Guard。ISS-015 继续收敛停止策略与 fallback 信息量。 ### 11.4 现场设计题 1. 给「工单退款 Agent」设计等价于 `tool_call_ids` + EG 的字段。 2. 若增加第四个 Tool,EG 要补哪些分支?kind/status 如何扩展? 3. Knowledge Query 路径(非完整 DIAGNOSIS)如何复用「引用必须真实」而不照搬整份 SemanticGuard? 4. 如何用 Timeline 事件向面试官演示一次 UNSUPPORTED 的失败阶段? --- ## 12. 原则清单(现行) 1. **唯一报告作者,多个确定性关卡** 2. **Draft 是 staging,SSE 是 production** 3. **引用主键 = 本 Run 的 framework tool_call_id** 4. **NORMAL / NEGATIVE 与 FOUND / NO_EVIDENCE 强匹配** 5. **NO_EVIDENCE 不是健康证明** 6. **ERROR 与跨 Run id 绝不能当证据** 7. **PreviousTurn 不是证据** 8. **SemanticGuard 冻结 snapshot,无 Tool** 9. **Reasoning 不是证据** 10. **历史 Issue 论证问题,现行代码定义答案** --- ## 13. 白板默画(只画现行) ### 13.1 图 A · 60 秒主链 ```text Diagnosis Agent → DiagnosisDraft ↓ EvidenceGuard (0 LLM, tool_call_id) ↓ verified snapshot SemanticGuard (no tools) ↓ SUPPORTED? Release → SSE or SAFE_FALLBACK ``` ### 13.2 图 B · 45 秒闭包 ```text conclusion.based_on → analysis_id → tool_call_ids ↓ Redis canonical (this runId) ↓ FOUND / NO_EVIDENCE ↓ kind must match ``` ### 13.3 图 C · 30 秒历史锚点(可选) ```text 早期发现:有 tool 仍假案情 → 要物理验真 + 语义审 + 发布权 → 现装在 EG / SG / Release ``` ### 13.4 红线 - 画出 Planner/Executor/Composer 当主路径 - 说 Verifier 输出 LOW_CONFID 当现行 API - SG 带检索箭头 - Draft 直连用户 - 说 metrics Tool 仍是诊断三件套之一(现行是 knowledge/logs/mysql) --- ## 14. 复习路径(偏现行) | 步骤 | 动作 | |---|---| | 1 | 读 `diagnosis-agent-prompt.md` + `DiagnosisDraft` / `AnalysisKind` | | 2 | 通读 `EvidenceGuard.validate` 与 `EvidenceViolationCode` | | 3 | 读 `DiagnosisReleaseUseCase` + `semantic-guard-prompt.md` | | 4 | 对照 `harness-quality-gates.md` 默画 §13 图 A/B | | 5 | 用 §11.1 录音;再花 20 秒提早期归因幻觉作动机 | | 6 | 扫一眼归档 Issue 标题与现象表即可,不背旧字段 | --- ## 15. 源文件索引 ### 现行(主) | 内容 | 路径 | |---|---| | 架构总览 | `mvp/architecture/current-mvp-architecture.md` | | 执行序列 | `mvp/architecture/agent-orchestration.md` | | 门禁 | `mvp/architecture/harness-quality-gates.md` | | Draft | `src/main/java/.../harness/contract/DiagnosisDraft.java` | | Kind/Status | `AnalysisKind.java` / `EvidenceStatus.java` | | EG | `.../guard/evidence/EvidenceGuard.java` | | SG | `.../guard/semantic/SemanticGuard.java` | | Release | `.../release/DiagnosisReleaseUseCase.java` | | Fallback | `.../release/SafeFallbackFactory.java` | | Agent Prompt | `src/main/resources/prompts/diagnosis-agent-prompt.md` | | SG Prompt | `src/main/resources/prompts/semantic-guard-prompt.md` | ### 历史(辅) | 内容 | 路径 | |---|---| | 归因幻觉发现 | `mvp/issues/archived/executor-evidence-attribution-hallucination.md` | | 自证闭环笔记 | `mvp/issues/design-notes/executor-self-evidence-loop-design-note.md` | | 旧证据契约 | `mvp/architecture/archive/2026-07-22-legacy/executor-evidence-pipeline-refactor.md` | | 重构承接 | `mvp/issues/archived/ISS-014-...` | | 运行质量后续 | `mvp/issues/active/ISS-015-...` | --- ## 16. 自测(必须能用现行组件名回答) 1. 画出 Draft 从产生到 SSE 的完整门禁序,并标出哪步 0 LLM。 2. `NORMAL` 与 `NEGATIVE_OBSERVATION` 分别能绑哪种 `evidence_status`? 3. EvidenceGuard 如何发现「编造 tool_call_id」? 4. 为什么 conclusion 要 `based_on_analysis_ids` 而不是直接绑 tool? 5. SemanticGuard 的输入有哪三样?为什么不能有 Tool? 6. Evidence 失败时 Repair 最多几次?仍失败用户看到什么产品语义? 7. PreviousTurn 为什么不能提供可引用的 tool_call_id? 8. Reasoning 能否帮助 Draft 过 EG/SG? 9. 用 20 秒说明早期归因幻觉与现行三道门的关系(动机 vs 实现)。 10. 举一个「EG 通过但 SG 应 UNSUPPORTED」的例子。 --- ## 17. 和旧版材料的关系 若你曾按「五段流水线 + excerpt 外键」准备: - **保留**:归因幻觉定义、物理/语义拆分、发布切断思想 - **替换**:所有主路径类名、Tool 列表、verdict 枚举、引用主键、API - **降级**:Executor/Gatekeeper/Composer 仅出现在「历史动机」小节 **面试默认叠词顺序** ```text 1. 现行:Agent → Draft → EG → SG → Release 2. 机制:tool_call_id、kind/status、snapshot、fallback 3. 动机:早期归因幻觉(可选一句) 4. 演进:正确性模型保留,装载进 Harness(若追问重构) ```