7.7 KiB
04 验证与发布:让未经证明的结论无法越过出口
这一篇只回答一个问题:Agent 写完诊断草稿以后,为什么还不能直接返回给用户?
1. 先看一个具体问题
Agent 在报告中写道:
10:03 出现数据库连接池耗尽,因此支付接口大量超时。
它同时引用了一次日志 Tool Call。系统即使确认这个调用真实存在、属于当前 Run,而且日志中确实出现“connection timeout”,仍然不能直接发布上述结论。
因为这里至少有三个不同问题:
- Agent 引用的 Tool Call 是不是真的?
- 真实日志是否足以证明“数据库连接池耗尽导致支付超时”?
- 验证失败后,系统最终应该向用户发布什么?
它们分别属于 EvidenceGuard、SemanticGuard 和 Release。
2. 草稿怎样通过发布链
flowchart LR
D["DiagnosisDraft<br/>Agent 写出的草稿"] --> E["EvidenceGuard<br/>机械验证引用和归属"]
E -->|"结构或引用可修复"| R["EvidenceRepair<br/>只修引用,不改语义"]
R --> E
E -->|"得到已验真证据"| S["SemanticGuard<br/>判断证据是否支持报告"]
S -->|"SUPPORTED"| P["Release SUCCESS<br/>发布原始安全 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 不是第二个报告作者。它必须满足:
修复前用户可见语义 == 修复后用户可见语义
它只能修结构和引用,不能新增事实、改变结论或补写建议;修复后还必须重新经过 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,但自己不写新根因。
flowchart TB
V{"可以安全发布正常报告吗?"}
V -->|"引用真实且语义受支持"| OK["ReleaseOutcome.SUCCESS<br/>发布原 Draft"]
V -->|"证据不足或验证不通过"| FB["ReleaseOutcome.FALLBACK<br/>发布 SafeFallback"]
V -->|"无法形成安全业务结果"| ER["ReleaseOutcome.FAILED"]
这里必须区分:
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. 先记住这些
- Draft 是候选结果,不是已发布报告。
- EvidenceGuard 用代码证明引用真实,SemanticGuard 判断证据是否支持语义。
- Repair 只能修引用,不能改变用户可见语义,而且只尝试一次。
- Release 是唯一出口,只发布原 Draft 或确定性 SafeFallback。
FALLBACK可以对应一次正常完成的 Run。
四篇组件导读到这里结束。需要回看整体路径时返回组件学习地图。