Files
SuperBizAgent-java/openspec/changes/archive/2026-07-03-chat-verifier-agent/design.md
T

350 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
Chat 多 Agent 链路当前由 ChatService 驱动 Planner → Executor,答案输出前无质量门禁。Verifier Agent 作为 Executor 后置质量门禁,在 Executor 输出后做事实核查。
前序 change `executor-action-memory-relevance` 已在 Executor 侧构建了行动记忆和检索质量归一化,Verifier 不需要重复验证检索质量。
## Goals / Non-Goals
**Goals:**
- Verifier 作为无工具 ReactAgent,由 ChatService 显式调用
- Verifier 输出 verdict (PASS/LOW_CONFID/REJECT) + groundedness_score + facts_checked
- ChatService 负责单轮显式编排:Planner → Executor → Verifier
- ChatService 外层根据 Verifier 判决做轮次路由:PASS→输出,LOW_CONFID≥0.5→带声明输出,LOW_CONFID<0.5→补充一轮,REJECT→降级
- Verifier 判决写入 diagnosis_session.self_evaluation JSON 容器中的 `verifier_evaluation` 槽位做可观测
**Non-Goals:**
- Verifier 不调用工具
- 不改动单 Agent 链路
- 不修改 Executor 的输出内容
- 不涉及数据库表结构变更
- Verifier 不继承 Executor 的中间推理过程(通过 MessagesModelHook 过滤)
## Decisions
| 决策 | 选择 | 放弃方案 | 原因 |
|------|------|---------|------|
| Verifier 是否有工具 | 无工具 ReactAgent | 有工具的 Agent | 职责单一,只核查不检索 |
| 判决分类 | PASS / LOW_CONFID / REJECT | PASS / FAIL 二分类 | LOW_CONFID 提供了弹性输出路径 |
| 回调机制 | ChatService 外层控制最多两轮 | 全交给 Supervisor / 不回调 | 轮次上限需要硬控制,不能只靠 prompt 记忆 |
| 可观测方案 | 写入 self_evaluation JSON 容器 | agent_step / tool_invocation / 新表 | 不改表结构,同时避免与 evidence_score 覆盖冲突 |
| 输入隔离 | 显式状态输入 + MessagesModelHook 裁剪噪音 | 仅靠原始消息过滤 / 数据库注入 | Verifier 需要稳定读取 query、工具摘要、最终答案,不能依赖消息格式猜测 |
| 阈值配置 | yml 配置化 | 硬编码 | 方便运维调整,不需改代码 |
## Verifier 输入契约
Verifier 的业务输入由 `ChatService` 显式组装,不依赖原始 conversation messages 的隐式结构。
### 必选输入
- `original_query`:用户原始问题
- `executor_final_answer`:本轮 Executor 最终答案
- `tool_trace_summary`:由工具调用事实整理出的半结构化摘要
### 条件输入
- `retry_context`:仅第二轮注入,描述上一轮 verifier 发现的证据缺口和补充约束
### tool_trace_summary 最小结构
```json
[
{
"tool_name": "lookup_knowledge",
"success": true,
"input_summary": "查询 ERR_TIMEOUT",
"output_summary": "命中 payment/errors.md,返回错误码定义",
"evidence_level": "direct"
},
{
"tool_name": "query_logs",
"success": false,
"input_summary": "按 traceId 查询日志",
"output_summary": "日志服务超时",
"evidence_level": "none"
}
]
```
约束:
- `tool_trace_summary` 只纳入证据型工具调用,不纳入纯辅助或无业务事实意义的工具
- `tool_trace_summary` 来源于工具调用事实,不直接透传原始日志全文
- Verifier 基于摘要做事实核查,不直接读取数据库
- 若某工具调用失败,仍需记录在摘要中,供 Verifier 判断证据缺口
### 证据型工具边界
默认纳入 `tool_trace_summary` 的工具:
- `lookup_knowledge`
- `query_logs`
- `query_metrics`
- `query_order` 或其他业务事实查询类工具
- 其他只读、能提供客观事实的工具
默认不纳入:
- `getCurrentDateTime`
- 纯格式化、转换、控制类工具
- 与事实核查无关的辅助工具
### 摘要压缩规则
- 每次调用只保留“最小证据摘要”,不透传原始返回全文
- `output_summary` 控制为 1-3 句,重点描述“这次调用证明了什么 / 没能证明什么”
- 失败调用必须保留,但统一标记:
- `success=false`
- `evidence_level=none`
- 同一工具、同一主题域、同一轮次的重复调用可以折叠为一条合并摘要
- 合并摘要至少保留:
- 首次有效命中结果
- 额外重复次数 / 未命中次数 / 失败次数
### 截断优先级
若 `tool_trace_summary` 过长,优先保留:
1. 被 `executor_final_answer` 直接引用的证据
2. 支撑根因结论的证据
3. 支撑修复结论的证据
4. 与上一轮 `retry_context` 缺口直接相关的证据
低优先级、与最终答案无关的辅助性工具摘要可被截断。
### retry_context 最小结构
```json
{
"round": 1,
"missing_evidence_facts": [
"“根因是连接池耗尽”缺少直接证据",
"“错误码 ERR_TIMEOUT 来自支付网关”只有间接支持"
],
"instruction": "仅补充以上断言相关证据,不要重复已完成检索"
}
```
### MessagesModelHook 职责边界
- 可以:移除 Planner/Executor 中间推理、无关闲聊和冗余 message
- 不可以:作为 Verifier 核心业务输入的唯一来源
- 目标:降噪,而非拼装业务事实
## Verifier 判决矩阵
Verifier 先提取并校验 `facts_checked`,再依据矩阵生成 verdict,避免只靠模型主观判断。
### facts_checked 分类
每条事实仅允许以下四类之一:
- `direct_evidence`:工具结果中有明确直接证据
- `indirect_support`:可由工具结果合理推导,但不是直接陈述
- `no_evidence`:工具结果中没有足够信息支撑
- `contradicted`:工具结果与该事实冲突,或该事实编造了不存在的关键实体/错误码/结论
### 关键事实范围
Verifier 优先校验关键事实,至少包括:
- 根因结论(root cause)
- 错误码 / 接口 / 组件归属
- 证据来源陈述(如“日志显示”“文档说明”)
- 明确修复结论
一般性建议、风险提示、非事实性表述默认不纳入关键事实,除非答案明确声称“已被证据证明”。
### verdict 规则
- `REJECT`
- 任意关键事实为 `contradicted`
- 或答案编造了工具/日志/文档中不存在的关键实体、错误码、结论
- `PASS`
- 所有关键事实均为 `direct_evidence` 或 `indirect_support`
- 且至少一条关键事实为 `direct_evidence`
- 且不存在 `contradicted`
- `LOW_CONFID`
- 不存在 `contradicted`
- 但存在关键事实为 `no_evidence`
- 或所有关键事实都只有 `indirect_support`,缺少直接锚点
一句话归纳:
- `REJECT` = 有冲突
- `LOW_CONFID` = 无冲突但缺关键证据
- `PASS` = 无冲突且关键事实均有支撑
### groundedness_score 计算
`groundedness_score` 不由模型自由打分,而由关键事实分类映射得到:
```text
direct_evidence = 1.0
indirect_support = 0.6
no_evidence = 0.0
contradicted = 0.0
```
规则:
- 仅对关键事实计分
- 取平均值后截断到 `[0.0, 1.0]`
- 若存在任意关键事实为 `contradicted`,直接 verdict=`REJECT`,且 `groundedness_score=0.0`
### 第二轮补证据范围
第二轮 `retry_context` 仅回灌以下关键缺口:
- 关键事实为 `no_evidence`
- 关键事实为 `indirect_support`,但仍缺直接证据锚点
`REJECT` 不进入第二轮补证据,直接降级输出。
## 用户侧输出协议
Verifier 的内部判决与用户侧最终输出类型分离:
- `PASS` → `NORMAL`
- `LOW_CONFID` → `LOW_CONFID_WITH_DISCLAIMER`
- `REJECT` → `DEGRADED`
### LOW_CONFID_WITH_DISCLAIMER
适用场景:
- 第一轮 `LOW_CONFID` 且 `groundedness_score >= threshold`
- 第二轮后仍为 `LOW_CONFID`
输出规则:
- 使用固定免责声明前缀
- 免责声明后拼接 `executor_final_answer`
- 可选附加“当前证据缺口”列表,但来源必须是 verifier 的关键缺口,不得自由扩写
建议模板:
```text
以下结论基于当前已获取证据,仍存在部分证据缺口,请谨慎参考。
{executor_final_answer}
当前缺口:
- ...
- ...
```
### DEGRADED
适用场景:
- 任意一轮 `REJECT`
- 系统无法基于现有证据形成可靠结论
输出规则:
- 不透传原始 `executor_final_answer`
- 使用固定降级模板
- 仅允许包含:
- 已确认信息
- 证据缺口
- 下一步建议
建议模板:
```text
当前无法基于已获取证据生成可靠结论,建议人工介入。
已确认信息:
- ...
证据缺口:
- ...
建议下一步:
- ...
```
### 输出边界
- `LOW_CONFID_WITH_DISCLAIMER` 可以带出原始答案,但必须加固定免责声明
- `DEGRADED` 不得透传未经验证的原始答案
- 用户侧输出模板由代码层拼装,不依赖 Verifier 自由生成
## self_evaluation 存储约定
`diagnosis_session.self_evaluation` 统一定义为 JSON 容器对象,而不是单一评估结果:
```json
{
"rule_evaluation": {
"evidence_score": 65,
"source": "rule",
"factors": []
},
"verifier_evaluation": {
"verdict": "LOW_CONFID",
"groundedness_score": 0.42,
"facts_checked": [],
"rationale": "...",
"round": 1
}
}
```
写入约束:
- `EvaluationService` 只负责写 `rule_evaluation`
- `ChatService` 只负责写 `verifier_evaluation`
- 两侧都必须使用 read-modify-write,保留另一侧已有内容
- 禁止整段覆盖 `self_evaluation`,除非初始化为空对象
## Risks / Trade-offs
- [Risk] Verifier 误判导致好答案被降级 → Mitigation: REJECT 仅用于明显编造场景,LOW_CONFID 为主要输出路径
- [Risk] callback Planner 后新答案质量不一定提升 → Mitigation: 仅回调一次,Token 成本可控
- [Risk] 第二轮仍可能产出 REJECT → Mitigation: 第二轮 REJECT 仍降级,不透传
- [Risk] `self_evaluation` 被异步 evidence_score 覆盖 → Mitigation: 定义 JSON 容器槽位,统一 read-modify-write
- [Risk] Verifier 增加 Token 消耗 → Mitigation: 单次轻量 LLM 调用,估算 <500 token
- [Risk] 消息过滤可能导致输入契约漂移 → Mitigation: 主输入由显式状态输入提供,Hook 仅用于剔除中间推理和无关噪音
- [Risk] 判决边界主观化,导致不同模型输出不稳定 → Mitigation: 用 facts_checked 分类 + verdict 矩阵 + 映射分数约束输出
- [Risk] 最终用户文案随模型漂移,导致产品行为不稳定 → Mitigation: LOW_CONFID/DEGRADED 使用固定输出协议和模板
## Implementation Plan
1. 创建 `chat-verifier-prompt.md`
2. 新建 `VerifierInputHook.java`(MessagesModelHook 实现,BEFORE_MODEL 时裁剪 messages,只保留必要上下文)
3. `ChatService.java` 新增 `buildChatVerifierAgent()` 方法(ReactAgent,无工具,带 hook)
4. 添加 `verifier.low-confidence-threshold: 0.5` 到 application.yml
5. 在 `ChatService.executeChatComplex()` 中显式调用 `Planner → Executor → Verifier`
6. 保留 `SupervisorAgent` 构造作为 legacy residue,不再依赖 prompt-only supervisor sequencing 保证 Verifier 执行
7. 在 `ChatService.executeChatComplex()` 外层实现最多两轮调用控制
8. 组装 Verifier 显式状态输入:`original_query` / `executor_final_answer` / `tool_trace_summary` / `retry_context`
9. 将 `self_evaluation` 升级为 JSON 容器读写:`rule_evaluation` / `verifier_evaluation`
10. 读取 Verifier 判决写入 `verifier_evaluation`
## Implementation Notes
### Explicit orchestration
The final implementation uses `ChatService` to call `planner -> executor -> verifier` directly in each outer round. This replaces the earlier prompt-only dependency on `SupervisorAgent` for verifier execution. The supervisor construction remains in the code as legacy residue, but runtime correctness is driven by explicit `callAgent(...)` ordering.
### Traceability model
The implemented verifier input and persisted evaluation include an evidence index:
- `tool_trace_summary[*].trace_ref`
- `tool_trace_summary[*].source_invocation_ids`
- `tool_trace_summary[*].query_samples`
- `tool_trace_summary[*].retrieval_layers`
- `tool_trace_summary[*].relevance_levels`
- `tool_trace_summary[*].source_documents`
Each verifier fact may carry `facts_checked[*].evidence_refs`, which points back to `trace_ref` and the underlying `tool_invocation` ids. This closes the audit gap where verifier could list many checked facts but the reviewer could not tell which facts related to which tool calls.
### Observability adjustment
`agent_step.thought` is now intentionally concise for verifier steps. Full verifier judgment belongs in `diagnosis_session.self_evaluation.verifier_evaluation`, with `model_output` retaining the model output snapshot.