feat: add chat verifier agent
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
## 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.
|
||||
Reference in New Issue
Block a user