docs(interview): refresh materials for single-agent harness narrative

Archive pre-refactor interview notes and add current deep-dives on
architecture evolution, issue-derived stories, and evidence gates.
This commit is contained in:
zhuyongxin
2026-07-24 18:14:49 +08:00
parent e47f2dead0
commit de5a5b09d9
18 changed files with 2622 additions and 57 deletions
@@ -0,0 +1,739 @@
# 主题深读:证据归因幻觉 + 现行质量门禁
**用途**:巩固知识 + 面试准备 + 举一反三
**不是**:逐字讲稿、旧 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<br/>唯一报告作者 · ReAct"]
A <--> TB["ToolBoundary"]
TB --> Redis["Redis canonical<br/>Harness only"]
TB --> Proj["bounded agent_result"]
Proj --> A
A --> Draft["DiagnosisDraft<br/>内部制品"]
Draft --> RU["DiagnosisReleaseUseCase"]
RU --> EG["EvidenceGuard · 0 LLM"]
EG -->|invalid| RP["EvidenceRepair 最多一次"]
RP --> EG
EG -->|valid snapshot| SG["SemanticGuard · 隔离单轮"]
SG -->|SUPPORTED| Out["Release SUCCESS<br/>公开 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<br/>runId + toolCallId"]
Canon --> Status["evidence_status<br/>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<br/>物理真"] --> SG["SemanticGuard<br/>语义立"]
SG -->|SUPPORTED| R["可发布"]
SG -->|UNSUPPORTED| F["Fallback<br/>保留 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(若追问重构)
```