# 04 验证与发布:让未经证明的结论无法越过出口 这一篇只回答一个问题:**Agent 写完诊断草稿以后,为什么还不能直接返回给用户?** ## 1. 先看一个具体问题 Agent 在报告中写道: > 10:03 出现数据库连接池耗尽,因此支付接口大量超时。 它同时引用了一次日志 Tool Call。系统即使确认这个调用真实存在、属于当前 Run,而且日志中确实出现“connection timeout”,仍然不能直接发布上述结论。 因为这里至少有三个不同问题: 1. Agent 引用的 Tool Call 是不是真的? 2. 真实日志是否足以证明“数据库连接池耗尽导致支付超时”? 3. 验证失败后,系统最终应该向用户发布什么? 它们分别属于 EvidenceGuard、SemanticGuard 和 Release。 ## 2. 草稿怎样通过发布链 ```mermaid flowchart LR D["DiagnosisDraft
Agent 写出的草稿"] --> E["EvidenceGuard
机械验证引用和归属"] E -->|"结构或引用可修复"| R["EvidenceRepair
只修引用,不改语义"] R --> E E -->|"得到已验真证据"| S["SemanticGuard
判断证据是否支持报告"] S -->|"SUPPORTED"| P["Release SUCCESS
发布原始安全 Draft"] E -->|"无法验真"| F["SafeFallback"] S -->|"UNSUPPORTED 或不可用"| F F --> O["Release FALLBACK"] ``` 第一次阅读只需记住: - **EvidenceGuard 验真**:证明引用确实来自当前 Run 的已完成 Tool 调用。 - **SemanticGuard 验义**:判断这些真实证据是否支持报告中的结论。 - **Release 决定出口**:只发布验证通过的原 Draft,或者发布确定性的 SafeFallback。 ## 3. EvidenceGuard:代码能证明的事交给代码 EvidenceGuard 会检查: - Draft 结构和 analysis ID 是否合法; - 所有结论分析是否形成完整引用闭包; - `tool_call_id` 是否属于当前 Run; - canonical invocation 是否为 `READY`; - `EvidenceStatus` 与引用类型是否一致; - 引用的 evidence 是否确实存在于标准化 Tool 结果中。 这些都是确定性问题,不需要模型判断。让模型验证自己的引用,会让同一个非确定性来源同时当作者和裁判。 验证通过后,EvidenceGuard 生成 `VerifiedEvidenceSnapshot`。它只包含 SemanticGuard 所需的已验真证据、来源和 scope,不包含 Tool raw response。 主要代码入口:`EvidenceGuard`、`EvidenceGuardResult`、`VerifiedEvidenceSnapshot`、`EvidenceViolation`。 ## 4. EvidenceRepair:为什么允许修,又为什么只能修一次 有些 Draft 的业务语义可能没有问题,只是引用结构出错,例如漏写一个 analysis ID。直接丢弃会浪费一次昂贵诊断,因此系统允许一次 EvidenceRepair。 但 Repair 不是第二个报告作者。它必须满足: ```text 修复前用户可见语义 == 修复后用户可见语义 ``` 它只能修结构和引用,不能新增事实、改变结论或补写建议;修复后还必须重新经过 EvidenceGuard。若语义视图发生变化,Repair 立即失败。 只允许一次,是为了避免形成“修复失败再修复”的隐式 Agent loop,也让成本和执行路径保持可解释。 ## 5. SemanticGuard:引用真实不等于推论成立 一条真实的 `connection timeout` 日志,可能来自下游网络问题,也可能只是故障结果,不能自动证明数据库连接池耗尽。 这类支持关系无法完全用规则判断,因此 SemanticGuard 使用一次隔离的模型调用,只接收: - 用户原始问题; - Draft 的用户可见语义; - EvidenceGuard 产生的 verified snapshot。 它没有 Tool、没有记忆、没有 ReAct loop、不访问 Redis,也不能改写报告。输出只有 `SUPPORTED / UNSUPPORTED` 和审计原因。 这个设计没有追求一个看似精确的置信度分数。首版真正需要的是发布门禁,而未经校准的 0.73 并不能形成比二元判定更可靠的协议。 主要代码入口:`SemanticGuard`、`SemanticGuardInput`、`SemanticGuardDecision`、`GuardModelCall`。 ## 6. Release:为什么必须只有一个出口 如果 Agent、Guard、Application 都能各自构造最终结果,会出现: - 同一验证失败被不同层解释成不同文案; - 某个分支忘记经过 SemanticGuard; - 迟到的 Draft 绕过已经确定的取消或失败; - Fallback 混入未经验证的 Agent 内容。 `DiagnosisReleaseUseCase` 因此拥有唯一发布决策。它协调 EvidenceGuard、可选 Repair、SemanticGuard 和 SafeFallbackFactory,但自己不写新根因。 ```mermaid flowchart TB V{"可以安全发布正常报告吗?"} V -->|"引用真实且语义受支持"| OK["ReleaseOutcome.SUCCESS
发布原 Draft"] V -->|"证据不足或验证不通过"| FB["ReleaseOutcome.FALLBACK
发布 SafeFallback"] V -->|"无法形成安全业务结果"| ER["ReleaseOutcome.FAILED"] ``` 这里必须区分: ```text RunState.SUCCESS + ReleaseOutcome.FALLBACK ``` 这表示系统完整、安全地处理了请求,但证据不足以发布正常诊断结论。Fallback 是安全产品结果,不等于执行失败。 ## 7. SafeFallback:不是让另一个模型重新回答 验证失败后再次让模型“写得保守一点”,仍然可能产生新事实。SafeFallback 因此由代码确定性构造,只允许使用: - 已验真的来源和检查范围; - 可以安全表达的观察事实; - 当前证据的限制; - 面向补充数据的下一步建议; - 稳定、可公开的问题类型。 它不会包含 Agent 原 Draft 的未验证结论、SemanticGuard 内部原因、Provider 错误或异常栈。 主要代码入口:`DiagnosisReleaseUseCase`、`EvidenceRepair`、`SafeFallbackFactory`、`DiagnosisReleaseResult`。 ## 8. Contract:为什么状态和结果必须类型化 发布链横跨模型 JSON、Java、Redis、数据库和 SSE。如果各层都使用自由字符串 `SUCCESS`,很快就无法区分: - Tool 调用完成; - Tool 找到候选证据; - 语义审查通过; - Run 执行成功; - 最终发布正常报告。 Contract 包用不同类型保持这些问题正交,例如 `InvocationStatus`、`EvidenceStatus`、`SemanticVerdict`、`RunState` 和 `ReleaseOutcome`。类型多不是为了复杂,而是为了阻止一个含糊的 `status` 穿过整个系统。 ## 9. 为什么没有采用其他方案 | 方案 | 没有采用的原因 | |---|---| | 只检查 Tool Call ID 存在 | 只能证明引用存在,不能证明归属、状态和内容一致 | | 一个 Verifier Agent 同时验引用和语义 | 混合确定性与非确定性判断,失败后难以定位责任 | | Guard 自动改写报告 | Guard 会变成第二个作者,并可能引入未验证事实 | | SemanticGuard 使用 Tool 再查证 | 会形成第二条诊断链,预算和证据归属变复杂 | | 验证失败直接抛技术错误 | 证据不足是正常业务结果,用户仍需要安全说明 | | 用置信度阈值发布 | 未校准分数不能充当可靠安全协议 | ## 10. 先记住这些 1. Draft 是候选结果,不是已发布报告。 2. EvidenceGuard 用代码证明引用真实,SemanticGuard 判断证据是否支持语义。 3. Repair 只能修引用,不能改变用户可见语义,而且只尝试一次。 4. Release 是唯一出口,只发布原 Draft 或确定性 SafeFallback。 5. `FALLBACK` 可以对应一次正常完成的 Run。 四篇组件导读到这里结束。需要回看整体路径时返回[组件学习地图](README.md)。 需要深入验证规则时,阅读[证据安全链专题](../Harness证据安全链-从引用真实到结论可发布.md);需要查所有 contract 类型时,阅读[组件全景](../Harness组件全景-职责-设计原因与边界.md)。