Files
SuperBizAgent-java/mvp/issues/design-notes/executor-structured-output-v2.md

1924 lines
64 KiB
Markdown
Raw Permalink 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.
# Executor Structured Output V2 可执行设计与实施 Issue
**状态**:阶段四待启动,前三阶段已归档并提交
**严重程度**:高
**创建日期**:2026-07-07
**最后更新**:2026-07-08
**文档类型**:可执行设计 issue / 分阶段实施说明
**范围**:Chat Executor 输出结构、Gatekeeper、Verifier、Composer 数据契约调整
**关联问题**:
- `executor-evidence-attribution-hallucination`
- `executor-self-evidence-loop-design-note`
- `chat-verifier-agent`
- `executor-evidence-output-contract`
---
## 0. 执行摘要
这份文档是 `Executor Structured Output V2` 的可执行设计 issue,用来指导后续 agent 按阶段实施,不是单纯的数据结构草案。
后续 agent 接手时,应先读完本节,确认“为什么做、做到哪、下一步做什么、如何验收”,再进入后面的字段契约和 prompt 细节。
### 0.0 这次改造要解决什么
当前 Chat 复杂诊断链路中,Executor 已经会调用 `lookup_knowledge`、`query_metrics`、`query_logs` 等证据工具,但它在最终输出时容易把三类内容混在一起:
1. 本轮工具真实返回的事实。
2. runbook、skill、知识库里的通用模式。
3. 模型基于经验补全的推断。
这会导致 Executor 把“可能方向”写成“确认结论”,甚至在 `user_facing_answer` 中夹带未被 Verifier 校验过的根因、错误码、指标值或修复建议。Verifier 虽然会拦截一部分,但它被迫同时处理“事实抽取”和“事实校验”,职责边界不稳定。
本 issue 的核心目标是切断这条风险链路:
```text
Executor 只负责收集证据并输出结构化材料;
Gatekeeper 先用代码拦截物理级幻觉;
Verifier 只判断 claim 是否能由证据合理推出;
Composer 只根据 Verifier 允许的材料生成最终用户答案。
```
### 0.1 当前实现与目标链路
当前复杂 Chat 链路:
```text
chat_planner -> chat_executor -> chat_verifier -> final answer
```
目标链路:
```text
chat_planner
-> chat_executor
-> VerifierInputHook 内 Gatekeeper
-> chat_verifier
-> chat_composer
-> final answer
```
本次不改 Planner,不新增 Controller,不改现有 retry 机制。改造重点只放在 Executor 输出契约、Gatekeeper 确定性校验、Verifier claim 可推导性校验、Composer 最终表达。
### 0.2 当前接手快照
截至 2026-07-08,仓库状态应以 `git status --short`、`openspec/changes` 和最近提交为准。当前已知阶段状态如下:
| 阶段 | 状态 | 记录 |
|---|---|---|
| 阶段一:Executor V2 输出契约 | 已完成、已归档、已提交 | `050cbc8 feat(agent): add executor evidence v2 contract` |
| 阶段二:Gatekeeper 接入 VerifierInputHook | 已完成、已归档、已提交 | `c5e496e feat(agent): add executor gatekeeper hook` |
| 阶段三:Verifier V2 可推导性校验 | 已完成、已归档、已提交 | `1b31e78 feat(agent): add verifier claim checks` |
| 阶段四:Composer 输出最终答案 | 下一阶段,待启动 | 建议 change id:`executor-composer-final-answer` |
| 阶段五:回归评测与审计闭环 | 未开始 | 等 Composer 链路稳定后补齐 |
接手时的第一动作:
1. 运行 `git status --short`,确认工作区是否干净。
2. 运行 `Get-ChildItem openspec\changes`,确认是否存在未完成 active change。
3. 如果没有 active change,下一步应创建阶段四 OpenSpec change:`executor-composer-final-answer`。
4. 阶段四完成后,必须先验证、归档 OpenSpec、回填 devflow、单独 commit,再进入阶段五。
### 0.3 推荐阅读顺序
1. 先读 `0A - 0I`:理解背景、问题、目标、范围、阶段和执行纪律。
2. 再读 `0.5 当前下一阶段执行卡片`、`19. Composer 设计`、`20. 当前代码影响面`、`21. 实施阶段`:理解当前下一阶段如何落地。
3. 最后按需查阅 `2 - 18`:这些是已经讨论过的数据契约、Gatekeeper 规则和 Verifier 输入输出。
### 0.4 阶段实施纪律
后续实施必须按阶段串行推进:
```text
阶段一 archive + commit
-> 阶段二 archive + commit
-> 阶段三 archive + commit
-> 阶段四 archive + commit
-> 阶段五 archive + commit
```
执行约束:
- 每个阶段都要先有 OpenSpec change,再实现、验证、归档到 devflow,并单独提交。
- 阶段未归档、未提交前,不进入下一阶段。
- 每个阶段只解决该阶段的问题,不顺手做下一阶段。
- 每个阶段至少要有主成功路径和新增失败路径测试。
- 提交前必须确认 diff 只包含当前阶段内容。
- 验收失败如果是代码问题,agent 自行修复;如果是设计决策不明确,停下来问用户。
- 不推送,除非用户明确要求。
### 0.5 当前下一阶段执行卡片
当前下一阶段是:
```text
阶段四:Composer 输出最终答案
```
建议 OpenSpec change id:
```text
executor-composer-final-answer
```
阶段四要做的事情:
- 新增 `chat_composer` Agent 或等价 Composer 调用。
- 新增 `chat-composer-prompt.md`。
- 在 `ChatService` 中,Verifier 完成后组装 Composer 输入。
- Composer 输入只能包含 Verifier 允许材料:`allowed_claims`、`allowed_hypotheses`、`missing_info`、`recommended_actions`、`rationale`。
- PASS、LOW_CONFID、REJECT 的最终用户答案都不能再读取 Executor 的 `user_facing_answer`。
- Composer 输出必须是严格 JSON,包含 `answer_summary`、`recommended_actions`、`user_facing_answer`。
- Composer malformed 时必须走安全降级,不得向用户泄露 raw JSON 或 Executor 原文。
阶段四明确不做:
- 不改 Planner。
- 不改 Gatekeeper 规则集。
- 不扩展 Verifier 判定枚举。
- 不新增数据库表。
- 不让 Composer 调用工具。
- 不让 Composer 重新判断根因。
- 不提前做阶段五 eval fixture 大扩展。
阶段四最小验收:
- Composer 输入不包含 raw tool output。
- 未通过 Verifier 的 claim 不进入最终答案。
- `REJECT` 最终答案不出现根因结论,`allowed_hypotheses=[]`。
- `LOW_CONFID` 最终答案区分已确认信息、可能方向和证据缺口。
- `PASS` 只有在存在允许输出的 root cause claim 时,才表达根因已确认。
- `ChatService` 不再依赖阶段一临时 V2 renderer 生成 PASS 用户答案。
---
## 0A. 背景与问题定义
### 0A.1 背景
近期 Chat 复杂诊断链路中,多条诊断会话被 Verifier 判为 `LOW_CONFID`。这些会话并不是没有调用工具;Executor 通常已经调用了 `lookup_knowledge`、`query_metrics`、`query_logs` 等 evidence tools。
真正的问题是:Executor 在综合输出阶段把三类内容混在一起写成“当前事故结论”:
1. 本轮工具真实返回的事实。
2. runbook、skill 或知识库里的通用模式。
3. 模型基于经验补全的推断。
当前 Executor 输出中还包含最终面向用户的自然语言字段:
```text
diagnosis_summary
user_facing_answer
```
这会诱导 Executor 提前进入“诊断报告 / 用户表达”模式,在证据不充分时把“可能方向”写成“确认结论”。后续 Verifier 虽然会拦截,但它面对的是自然语言答案和结构化字段混在一起的输出,校验边界不稳定。
典型风险包括:
- Executor 编造不存在的 `source_invocation_ids`。
- Executor 使用真实 invocation id,但 `evidence_excerpt` 与真实工具输出不一致。
- Executor 把 hypothesis 写成 confirmed claim。
- Executor 在 `user_facing_answer` 里夹带 claims 中没有的根因、错误码、指标值或修复建议。
- Verifier 被迫从自然语言里逐字抽事实,校验边界不稳定。
因此,本次改造不是为了“多加几个 Agent 显得完整”,而是为了收紧证据归因链路:Executor 只产出可校验材料,Verifier 只判断材料是否被证据支撑,最终表达交给 Composer。
---
### 0A.2 当前问题定义
本 issue 要解决的是:
```text
Executor 证据归因幻觉
```
更具体地说,Executor 已经调用了工具,但在最终输出时没有严格区分:
| 类型 | 定义 | 应进入哪里 |
|---|---|---|
| direct evidence | 本轮工具直接观测到的事实 | `claims` |
| reasonable inference | 能由证据合理推出但不是逐字出现的判断 | `claims`,但需要标注 `indirect`,并由 Verifier 判断是否说重 |
| hypothesis | 值得排查但尚未被当前证据确认的方向 | `hypotheses` |
| missing evidence | 还缺少哪些证据才能确认 | `missing_info` |
| user expression | 面向用户的自然语言答案 | Composer 输出,不由 Executor 输出 |
如果不拆开这些边界,Verifier 会长期承担两个不同任务:
1. 从自然语言中抽事实。
2. 判断事实是否有证据支撑。
这会让 Verifier 的职责过重,也让最终答案容易夹带未经校验的内容。
---
### 0A.3 当前实现
当前代码中的复杂 Chat 链路是三 Agent 顺序执行:
```text
chat_planner -> chat_executor -> chat_verifier
```
对应入口在:
```text
ChatService.executeChatComplex(...)
```
当前数据流:
```text
用户问题
-> Planner 生成 planner_plan
-> Executor 调用工具并输出 executor_evidence_v1
-> VerifierInputHook 构造 verifier payload
-> Verifier 输出 facts_checked/verdict
-> ChatService 根据 verdict 生成最终 answer
```
当前关键问题在 Executor 与 Verifier 之间:
```text
Executor 同时输出结构化 claims 和 user_facing_answer
Verifier 既要校验 structured output,又要扫描自然语言答案
```
这会让最终答案和证据归因之间出现缝隙。
---
## 0B. 目标设计
目标链路仍保持单条顺序链路,不新增 Controller,不改 Planner:
```text
chat_planner
-> chat_executor
-> VerifierInputHook 内 Gatekeeper
-> chat_verifier
-> chat_composer
-> final answer
```
职责边界:
| 组件 | 职责 |
|---|---|
| Planner | 选择 skill、拆解计划;本次不改 |
| Executor | 调用工具收集证据,输出结构化 claims/hypotheses/actions/missing_info |
| Gatekeeper | 在 Verifier 前做确定性校验,拦截伪造 ID、工具名不匹配、明显张冠李戴、非法 schema |
| Verifier | 判断 claims 是否能由证据合理推出,不再逐字扫描最终答案 |
| Composer | 只根据 Verifier 允许的材料生成最终用户答案 |
目标数据流:
```text
Executor raw JSON
-> parse JSON
-> Gatekeeper.validate(...)
-> Verifier claim-level validation
-> ChatService 过滤 allowed_claims/allowed_hypotheses
-> Composer 生成 user_facing_answer
```
核心原则:
- 单智能体优先:不为了抽象而新增 Controller;只有已经明确的职责边界才拆 Agent。
- Planner 不改:本 issue 的问题不在计划拆解,而在 Executor 输出和 Verifier 校验边界。
- Gatekeeper 不做诊断:只做确定性校验,拦截伪造 ID、字段退化、明显张冠李戴。
- Verifier 不再逐字扫描最终答案:主校验对象是结构化 `claims`。
- Composer 不补事实:只把 Verifier 允许的材料组织成中文答案。
---
## 0C. 分阶段实施总览
| 阶段 | 名称 | 目标 | 状态 | 退出条件 |
|---|---|---|---|---|
| 1 | Executor V2 输出契约 | 移除 Executor 最终表达字段,只输出结构化诊断材料 | 已完成并归档 | `executor_evidence_v2` 可运行,最终用户不再看到 raw JSON |
| 2 | Gatekeeper 接入 VerifierInputHook | 在 Verifier 前加入确定性拦截与审计 | 已完成并归档 | `gatekeeper_result` 进入 Verifier payload 和 `self_evaluation` |
| 3 | Verifier V2 可推导性校验 | 从 `facts_checked` 转向 `claim_checks`,判断 claims 是否可由证据推出 | 已完成并归档 | Verifier 不再从自然语言答案抽取额外事实 |
| 4 | Composer 最终表达 | 由 Composer 根据 Verifier 允许材料生成最终用户答案 | 下一阶段 | PASS/LOW_CONFID/REJECT 都不读取 Executor `user_facing_answer` |
| 5 | 回归评测与审计闭环 | 用测试和 eval fixtures 证明幻觉拦截链路有效 | 未开始 | 伪造 ID、张冠李戴、hypothesis 写成事实等场景都有回归覆盖 |
更详细的实施拆解见 `21. 实施阶段`。
阶段推进顺序必须串行:
```text
阶段一 archive + commit
-> 阶段二 archive + commit
-> 阶段三 archive + commit
-> 阶段四 archive + commit
-> 阶段五 archive + commit
```
不能在 Composer 未稳定时把回归评测阶段提前做成大杂烩。
---
## 0D. 范围与非目标
### In scope
- 调整 Executor 输出契约,从 `executor_evidence_v1` 演进到 `executor_evidence_v2`。
- 在 `VerifierInputHook` 中接入 Gatekeeper。
- 将 Gatekeeper 结果写入 Verifier payload 和 `diagnosis_session.self_evaluation`。
- 将 Verifier 主输出从 `facts_checked` 迁移到 `claim_checks`,兼容期保留旧字段。
- 新增 Composer 表达层,最终用户答案只来自 Verifier 允许材料。
- 补齐关键回归测试和 eval fixtures。
### Out of scope
本 issue 第一版明确不做以下事情:
- 不改 Planner 的输入输出。
- 不引入 Controller 或新的编排层。
- 不改变现有 retry 机制。
- 不要求 Gatekeeper 失败后自动回退 Executor 重试。
- 不新增数据库表。
- 不让 Gatekeeper 做根因判断。
- 不让 Composer 调用工具或重新诊断。
---
## 0E. 关键设计决策
| 决策 | 结论 | 原因 |
|---|---|---|
| 是否改 Planner | 不改 | 当前问题主要在 Executor 输出和 Verifier 校验边界 |
| Gatekeeper 放在哪里 | 放在 `VerifierInputHook` | 保持当前 workflow,不改 Executor hook 和编排 |
| Gatekeeper 失败是否重试 | 第一版不重试 | 先建立拦截和审计闭环,避免扩大改动面 |
| Executor 是否继续输出最终答案 | 不输出 | 防止未经 Verifier 的自然语言结论泄露 |
| Verifier 是否逐字扫描答案 | 不扫描 | 主校验对象改为 `claims` |
| Composer 是否可以新增事实 | 不可以 | Composer 只是表达层,不是诊断层 |
| `facts_checked` 是否保留 | 兼容期保留 | 当前低置信模板、retry_context、评测可能依赖 |
---
## 0F. 实现约束
实现时必须遵守:
- 优先保持现有三 Agent 复杂链路可运行。
- 每个阶段都应能单独测试和回滚。
- 第一版以“降低幻觉风险”为目标,不追求一次性重构所有历史字段。
- 所有最终用户可见答案都必须来自 Verifier 允许输出的材料。
- 所有 Gatekeeper 校验结果都必须进入审计,至少能回溯到 `self_evaluation.verifier_evaluation.gatekeeper_result`。
- 如果结构化输出 malformed,不允许回退到自然语言逐字抽取后给 PASS。
---
## 0G. OpenSpec / devflow 落地要求
这个 issue 后续按 OpenSpec-first 的方式实施。每个阶段都必须留下可审计痕迹:
```text
OpenSpec proposal/design/spec/tasks
-> 实现代码
-> 最小验证
-> archive 到 openspec/changes/archive
-> 回填 devflow/projects
-> git commit
```
阶段交付物:
| 交付物 | 要求 |
|---|---|
| OpenSpec change | 每个阶段一个独立 change,不能复用上个阶段的 active change |
| tests | 至少覆盖该阶段的新增失败路径和主成功路径 |
| devflow | 记录 brief、decisions、evidence、acceptance |
| commit | 每个阶段单独提交,提交前确认 diff 只包含本阶段内容 |
推荐验证命令:
```powershell
mvn "-Dtest=VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate --specs
```
如阶段新增专门测试,应把测试类加入 Maven `-Dtest` 列表。
后续 agent 每个阶段的固定执行清单:
1. 确认当前阶段:读取本 issue、`git status --short`、`openspec status`。
2. 确认 OpenSpec:没有 active change 时先创建;已有当前阶段 active change 时继续使用。
3. 实现阶段内代码:只改该阶段要求的 prompt/service/hook/test,不夹带下一阶段。
4. 验证:运行该阶段建议测试和 `cmd /c openspec validate ...`。
5. 回填任务:更新 OpenSpec `tasks.md`、`decisions.md`,记录实际验证命令和结果。
6. 归档:创建或更新 `devflow/projects/<date>-<change-id>/`,然后 `cmd /c openspec archive <change-id> -y`。
7. 复验:归档后再次运行最小测试和 `cmd /c openspec validate --specs`。
8. 提交:检查 `git diff --cached --stat`,确认只包含当前阶段,再单独 commit。
若步骤 4 或 7 失败:
- 代码或测试问题:自行修复并重新验证。
- 阶段边界或协议设计问题:停止,向用户说明阻塞点,不进入下一阶段。
---
## 0H. 推荐实现切片
建议不要把所有改动塞进一个大 PR。推荐拆成以下可独立验收的切片:
| 切片 | 内容 | 可单独合入条件 |
|---|---|---|
| PR-1 | Executor V2 prompt + parse-only 边界调整 | Executor 能输出 V2 JSON;不会再通过 Executor 直接生成最终用户答案 |
| PR-2 | Gatekeeper 基础框架 + schema/invocation 规则 | Verifier payload 和审计中出现 `gatekeeper_result`;伪造 invocation id 可被拦截 |
| PR-3 | Verifier V2 claim_checks + facts_checked 兼容 | Verifier 主校验 `claims`;现有低置信模板和 retry_context 不坏 |
| PR-4 | Composer 接入 + 最终答案渲染 | PASS/LOW_CONFID/REJECT 最终答案都不再读取 Executor `user_facing_answer` |
| PR-5 | eval fixtures + 回归测试补齐 | 覆盖伪造 ID、工具名不匹配、excerpt 张冠李戴、hypothesis 写成事实 |
每个切片都应保留当前复杂 Chat 链路可运行。若某个切片失败,优先回滚该切片,不要连带回滚已经稳定的前置切片。
---
## 0I. 为什么这样拆阶段
本 issue 的改造目标不是一次性重写 Chat 诊断链路,而是逐步切断“证据不足但自然语言先下结论”的通道。阶段拆分按风险来源分层:
| 阶段 | 切断的风险 | 为什么不能合并 |
|---|---|---|
| 阶段一 | Executor 提前输出最终诊断话术 | 先移除污染源,否则后面 Verifier/Composer 仍会被旧答案影响 |
| 阶段二 | 伪造 ID、工具名不匹配、非法 schema 等物理级幻觉 | 这类问题不需要 LLM 判断,先用代码低成本拦截 |
| 阶段三 | claim 与证据之间是否可推导 | 这是 Verifier 的语义职责,必须在 Gatekeeper 之后处理 |
| 阶段四 | 最终用户答案夹带未验证事实 | 只有 Verifier 输出稳定后,Composer 才知道哪些材料可用 |
| 阶段五 | 新链路是否真的防住历史幻觉场景 | 等链路完整后再做系统化回归,避免测试绑死临时实现 |
每个阶段的设计都必须满足两个条件:
- 可以独立验证,不依赖后续阶段已经完成。
- 失败时可以独立回滚,不破坏已经归档提交的前置阶段。
因此后续实现时不要把“顺手移除临时 renderer”“顺手扩展 Gatekeeper 规则”“顺手接 Composer”混进阶段三。阶段三只解决 Verifier `claim_checks` 和有效 verdict guardrail。
---
## 1. 设计目标
当前 `executor_evidence_v1` 同时包含结构化诊断材料和自然语言表达字段:
- `diagnosis_summary`
- `user_facing_answer`
这两个字段容易让 Executor 提前进入“总结报告 / 用户表达”模式,并可能把未证实内容写成确认结论。
V2 的目标是让 Executor 只输出结构化诊断材料,不负责最终用户表达。
---
## 2. 输出结构
```json
{
"answer_version": "executor_evidence_v2",
"claims": [
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "payment-service 出现请求超时日志。",
"support_level": "direct",
"evidence_bindings": [
{
"source_type": "tool_trace",
"source_id": "trace-1",
"tool_name": "query_logs",
"source_invocation_ids": [394],
"evidence_excerpt": "request timeout"
}
]
}
],
"hypotheses": [
{
"hypothesis_text": "连接池压力可能参与了超时问题。",
"basis": "当前已有超时现象,但缺少连接池 active/idle/pending 指标。",
"needed_evidence": ["HikariCP active/idle/pending 指标"]
}
],
"recommended_actions": [
{
"action_text": "补充查询连接池 active/idle/pending 指标。",
"reason": "用于确认连接池是否达到上限。",
"evidence_bindings": []
}
],
"missing_info": [
"缺少连接池指标,无法确认连接池耗尽是根因。"
]
}
```
---
## 3. 字段定义
| 字段 | 类型 | 必填 | 定义 |
|---|---|---:|---|
| `answer_version` | string | 是 | 固定为 `executor_evidence_v2` |
| `claims` | array | 是 | 已确认或有明确支撑的事实断言 |
| `hypotheses` | array | 否 | 合理怀疑但未被当前证据确认的方向 |
| `recommended_actions` | array | 否 | 建议补证据、排查或处理动作 |
| `missing_info` | array | 否 | 当前无法确认结论所缺少的证据 |
## 4. claims
```json
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "payment-service 出现请求超时日志。",
"support_level": "direct",
"evidence_bindings": []
}
```
| 字段 | 类型 | 必填 | 定义 |
|---|---|---:|---|
| `claim_id` | string | 是 | claim 唯一标识 |
| `claim_type` | string | 是 | claim 类型,例如 `symptom` / `root_cause` / `impact` / `risk` |
| `claim_text` | string | 是 | 已确认或有限确认的事实断言 |
| `support_level` | string | 是 | `direct` / `indirect` |
| `evidence_bindings` | array | 是 | 支撑该 claim 的证据绑定,不能为空 |
## 5. evidence_bindings
```json
{
"source_type": "tool_trace",
"source_id": "trace-1",
"tool_name": "query_logs",
"source_invocation_ids": [394],
"evidence_excerpt": "request timeout"
}
```
| 字段 | 类型 | 必填 | 定义 |
|---|---|---:|---|
| `source_type` | string | 是 | 证据来源类型,例如 `tool_trace` |
| `source_id` | string | 否 | evidence block id、trace_ref 或可定位标识;第一版不作为硬校验依据 |
| `tool_name` | string | 是 | 来源工具名 |
| `source_invocation_ids` | array | 是 | 来源 `tool_invocation.id` |
| `evidence_excerpt` | string | 是 | 从工具返回中摘取的原话、指标值、日志片段或关键数据 |
## 6. hypotheses
```json
{
"hypothesis_text": "连接池压力可能参与了超时问题。",
"basis": "当前已有超时现象,但缺少连接池 active/idle/pending 指标。",
"needed_evidence": ["HikariCP active/idle/pending 指标"]
}
```
| 字段 | 类型 | 必填 | 定义 |
|---|---|---:|---|
| `hypothesis_text` | string | 是 | 未证实但值得排查的方向 |
| `basis` | string | 是 | 它基于哪些已知证据,以及为什么仍只是推测 |
| `needed_evidence` | array | 是 | 要确认该假设还缺少的证据 |
## 7. recommended_actions
```json
{
"action_text": "补充查询连接池 active/idle/pending 指标。",
"reason": "用于确认连接池是否达到上限。",
"evidence_bindings": []
}
```
| 字段 | 类型 | 必填 | 定义 |
|---|---|---:|---|
| `action_text` | string | 是 | 建议动作 |
| `reason` | string | 是 | 建议原因 |
| `evidence_bindings` | array | 否 | 建议动作关联的证据绑定,可为空 |
## 8. 移除字段
V2 移除以下字段:
| 字段 | 移除原因 |
|---|---|
| `diagnosis_summary` | 容易让 Executor 提前总结诊断故事 |
| `user_facing_answer` | 容易夹带 claims 之外的确认式事实 |
最终用户答案应由 ChatService 或后续专门渲染阶段基于结构化字段生成。
---
## 9. Gatekeeper 设计
Executor 输出后、Verifier 接收前,增加一层确定性校验:
```text
Executor raw output
-> parse JSON
-> Gatekeeper
-> Verifier payload
```
Gatekeeper 的目标是拦截物理级幻觉,而不是替代 Verifier 做语义判断。
### 9.1 职责边界
| 组件 | 职责 |
|---|---|
| Gatekeeper | schema、引用 ID、工具名、excerpt、黑名单措辞等确定性校验 |
| Verifier | 判断 claim 是否被证据语义支撑、是否说重、是否缺关键证据 |
Gatekeeper 不判断根因是否正确,也不做新的检索。
### 9.2 接入位置
V1 接入位置保持在 `VerifierInputHook` 内,不改 Executor hook,不改 Chat workflow 编排:
```text
parseExecutorOutput(...)
-> buildVerifierTraceSummary(...)
-> Gatekeeper.validate(...)
-> verifierInput.put("gatekeeper_result", ...)
```
`VerifierInputHook` 的职责收敛为:
- 解析 Executor raw output。
- 构造 `executor_output_parse_status`。
- 构造 `tool_trace_summary`。
- 调用 Gatekeeper。
- 组装 Verifier payload。
除 JSON parse 与最小可解析性判断外,`VerifierInputHook` 不再维护独立校验规则。
后续持久化时,将 `gatekeeper_result` 写入:
```text
diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result
```
---
## 10. Gatekeeper 规则加载
Gatekeeper 采用“规则代码稳定、规则配置可调”的设计。
```text
Rule Index
-> Rule Metadata
-> Rule Implementation
```
### 10.1 索引层
索引层列出所有可用规则名和一句话描述,用于加载、展示和审计。
```yaml
rules:
- id: schema.executor_v2
name: Executor V2 Schema
description: 校验 Executor 输出是否符合 V2 schema
enabled: true
- id: evidence.invocation_ref
name: Invocation Reference Check
description: 校验 source_invocation_ids 是否属于当前 session 且工具名匹配
enabled: true
- id: evidence.excerpt_similarity
name: Excerpt Similarity Check
description: 校验 evidence_excerpt 与真实工具输出是否相似
enabled: true
- id: claim.hallucination_phrases
name: Hallucination Phrase Check
description: 禁止 confirmed claims 使用经验化、推测化措辞
enabled: true
- id: claim.evidence_utilization
name: Evidence Utilization Check
description: 检查 claim_text 与 evidence_excerpt 的关键词覆盖率
enabled: false
```
### 10.2 元数据层
元数据层定义规则参数、阈值、严重级别和适用字段。调整阈值或黑名单时只改元数据,不改代码。
```yaml
ruleMetadata:
schema.executor_v2:
severity: error
verdictImpact: low_confid_on_fail
params:
requiredFields:
- answer_version
- claims
deprecatedFields:
- diagnosis_summary
- user_facing_answer
evidence.excerpt_similarity:
severity: error
verdictImpact: low_confid_on_fail
params:
passThreshold: 0.5
warnThreshold: 0.3
compareAgainst:
- output_preview
- retrieval_details
- tool_trace_summary
claim.hallucination_phrases:
severity: error
verdictImpact: low_confid_on_fail
params:
targetFields:
- claims[].claim_text
blacklist:
- 通常情况下
- 根据经验
- 一般来说
- 我认为
- 理论上
- 可能是
- 推测
allowInFields:
- hypotheses[].hypothesis_text
- hypotheses[].basis
claim.evidence_utilization:
severity: warning
verdictImpact: warn_only
params:
minKeywordCoverage: 0.5
evidence.invocation_ref:
severity: error
verdictImpact: reject_on_fail
```
### 10.3 规则实现层
规则实现使用代码中的固定接口。新增全新类型规则需要代码;调整阈值、词表、启用状态不需要改代码。
```text
GatekeeperRule
id()
validate(context, metadata)
-> RuleResult
```
### 10.4 与 VerifierInputHook 原有校验的关系
V2 中,`VerifierInputHook` 原有的结构判断只保留 parse-only 能力:
```text
raw text
-> sanitized JSON
-> JsonNode / Map
-> parse status
```
以下校验统一迁移到 Gatekeeper:
- schema 字段是否完整。
- `answer_version` 是否正确。
- `claims` 是否为空或类型错误。
- `claims[].evidence_bindings` 是否为空。
- `source_invocation_ids` 是否存在。
- `tool_name` 是否匹配。
- `evidence_excerpt` 是否可信。
- 是否出现已移除字段 `diagnosis_summary` / `user_facing_answer`。
这样避免 `VerifierInputHook` 和 Gatekeeper 出现两套规则、两套错误口径。
---
## 11. 推荐规则集
### 11.1 Schema 校验
规则 id:
```text
schema.executor_v2
```
校验内容:
- 输出必须是 JSON object。
- `answer_version` 必须为 `executor_evidence_v2`。
- `claims` 必须存在且为 array。
- `hypotheses`、`recommended_actions`、`missing_info` 缺省时按空数组处理。
- 不允许出现 `diagnosis_summary`。
- 不允许出现 `user_facing_answer`。
Gatekeeper 输出给 Verifier 前应 normalize 结构:
```text
missing hypotheses -> []
missing recommended_actions -> []
missing missing_info -> []
```
也就是说,Verifier 和 Composer 可以假定这三个字段存在且为数组。
### 11.2 Invocation 引用校验
规则 id:
```text
evidence.invocation_ref
```
校验内容:
- `claims[].evidence_bindings` 不能为空。
- `source_invocation_ids` 必须为非空数组。
- 每个 invocation id 必须属于当前 session。
- `tool_name` 必须和对应 `tool_invocation.tool_name` 匹配。
- 不允许引用其它 session 的工具调用。
该规则只检查 `claims[].evidence_bindings`。`recommended_actions[].evidence_bindings` 可以为空,不参与硬失败判定。
### 11.3 Excerpt 相似度校验
规则 id:
```text
evidence.excerpt_similarity
```
校验内容:
- 将 `evidence_excerpt` 与真实工具输出进行相似度比对。
- 可比较来源包括:
- `tool_invocation.output_preview`
- `tool_invocation.retrieval_details`
- `tool_trace_summary.output_summary`
建议阈值:
| 相似度 | 结果 |
|---:|---|
| `>= 0.5` | pass |
| `0.3 - 0.5` | warn |
| `< 0.3` | fail |
如果只是 excerpt 相似度低,第一版判为 `LOW_CONFID` 级风险,不直接 `REJECT`,因为 `output_preview` 可能被截断,`tool_trace_summary` 也可能是摘要。
只有以下情况才进入 `REJECT` 级别:
- invocation id 不存在。
- invocation id 不属于当前 session。
- `tool_name` 与 invocation 的真实工具名不匹配。
- invocation 存在但 excerpt 明显指向另一个服务、错误码或指标值。
### 11.4 幻觉措辞校验
规则 id:
```text
claim.hallucination_phrases
```
校验内容:
- `claims[].claim_text` 不允许出现经验化、推测化措辞。
- `hypotheses` 中允许出现推测措辞。
默认黑名单:
```json
[
"通常情况下",
"根据经验",
"一般来说",
"可能是",
"推测",
"我认为",
"理论上"
]
```
### 11.5 证据利用率校验
规则 id:
```text
claim.evidence_utilization
```
校验内容:
- 提取 `claim_text` 中的实词。
- 提取 `evidence_excerpt` 中的实词。
- 计算关键词覆盖率。
伪代码:
```python
def check_evidence_utilization(claim_text, excerpt):
claim_keywords = extract_keywords(claim_text)
excerpt_keywords = extract_keywords(excerpt)
overlap = claim_keywords & excerpt_keywords
utilization = len(overlap) / len(claim_keywords)
return utilization >= 0.5
```
第一版建议作为 warning,不作为硬拒绝条件。
---
## 12. Gatekeeper 输出
Gatekeeper 第一版只保留必要审计字段。
```json
{
"gatekeeper_result": {
"status": "fail",
"failed_rules": ["evidence.invocation_ref"],
"warnings": ["evidence.excerpt_similarity"],
"errors": [
{
"rule_id": "evidence.invocation_ref",
"target": "claims[0].evidence_bindings[0]",
"message": "source_invocation_ids not found in current session"
}
]
}
}
```
### 12.1 字段定义
| 字段 | 类型 | 定义 |
|---|---|---|
| `status` | string | `pass` / `warn` / `fail` |
| `failed_rules` | array | 失败规则 id 列表 |
| `warnings` | array | warning 规则 id 列表 |
| `errors` | array | 最小错误明细 |
| `errors[].rule_id` | string | 失败规则 id |
| `errors[].target` | string | 出问题的字段路径 |
| `errors[].message` | string | 人类可读原因 |
### 12.2 总状态计算
```text
任一规则 fail -> gatekeeper status = fail
否则任一规则 warn -> gatekeeper status = warn
否则 gatekeeper status = pass
```
第一版不记录 `version`、`rule_set`、`summary`、`severity`、`details`、`checked_at` 等字段,避免审计结构过重。
Verifier 如需判断 Gatekeeper fail 是否应导致 `REJECT`,使用规则元数据中的 `verdictImpact`,不依赖落库字段。
---
## 13. 审计落点
Gatekeeper 结果需要进入两处:
### 13.1 Verifier payload
```json
{
"executor_structured_output": {},
"executor_output_parse_status": {},
"tool_trace_summary": [],
"gatekeeper_result": {}
}
```
Verifier 可根据 `gatekeeper_result` 调整判定,尤其是:
- `gatekeeper_result.status=fail` 时,不应输出 `PASS`。
- schema 或 invocation 引用失败时,应倾向 `LOW_CONFID` 或 `REJECT`。
### 13.1.1 parse status 与 schema status 边界
`executor_output_parse_status` 只表示 JSON 解析结果:
| parse status | 含义 |
|---|---|
| `valid` | raw output 可解析为 JSON object |
| `missing` | raw output 为空或不是 JSON |
| `malformed` | raw output 像 JSON 但解析失败 |
schema 合法性不再由 parse status 表达,统一交给 Gatekeeper:
```text
JSON parse valid but schema invalid
-> executor_output_parse_status.status = valid
-> gatekeeper_result.status = fail
-> failed_rules contains schema.executor_v2
```
### 13.2 self_evaluation
持久化位置:
```text
diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result
```
审计链路:
```text
executor_raw_output
-> executor_output_parse_status
-> gatekeeper_result
-> verifier_evaluation
-> final_answer
```
---
## 14. Verifier 输入结构
V2 中 Verifier 以结构化输出为主输入,不再逐字抽取自然语言事实。
```json
{
"original_query": "用户原始问题",
"executor_raw_output": "{raw executor output, debug/fallback only}",
"executor_structured_output": {
"answer_version": "executor_evidence_v2",
"claims": [],
"hypotheses": [],
"recommended_actions": [],
"missing_info": []
},
"executor_output_parse_status": {
"status": "valid",
"detail": "parsed executor evidence contract"
},
"tool_trace_summary": [],
"gatekeeper_result": {
"status": "pass",
"failed_rules": [],
"warnings": [],
"errors": []
},
"retry_context": null
}
```
| 字段 | 用途 |
|---|---|
| `original_query` | 判断结构化诊断是否围绕用户问题 |
| `executor_raw_output` | 原始输出,仅用于 debug/fallback,不作为主校验来源 |
| `executor_structured_output` | Verifier 主校验对象 |
| `executor_output_parse_status` | 判断结构化输出是否可用 |
| `tool_trace_summary` | 证据索引 |
| `gatekeeper_result` | Gatekeeper 物理级校验结果 |
| `retry_context` | 第二轮时判断缺口是否被补足 |
兼容期可以保留旧字段名:
```text
executor_final_answer
```
第一期 payload 可以同时包含 `executor_raw_output` 和 `executor_final_answer`,二者内容相同。`executor_final_answer` 语义上视为 deprecated;Verifier 不应在结构化输出合法时从该字段抽取额外事实。
---
## 15. Verifier 字段使用规则
### 15.1 executor_structured_output
Verifier 主要校验:
- `claims`
- `hypotheses`
- `recommended_actions`
- `missing_info`
重点是 `claims`:
```text
claim_text 是否能由 evidence_bindings 指向的证据合理推出?
support_level 是否说重?
是否引入证据外的新实体、新指标、新错误码或新根因?
```
### 15.2 tool_trace_summary
`tool_trace_summary` 用于判断 evidence binding 是否有语义支撑。
Verifier 不要求 claim 与证据逐字一致,而是判断可推导性:
```text
证据:payment-service CPU 92%,线程数 245
claim:payment-service 当前存在 CPU 使用率过高现象
=> direct_observation
claim:CPU 过高可能导致支付接口超时
=> reasonable_inference
claim:CPU 过高是支付超时的唯一根因
=> overstated
```
### 15.3 gatekeeper_result
Gatekeeper 负责物理合法性,Verifier 消费结果:
```text
status = pass
-> 正常校验 claims
status = warn
-> 正常校验 claims,但 rationale 可提及 warning
status = fail
-> 不允许 PASS
-> 相关 claim 至少应判 unsupported / external_unknown / contradicted
```
### 15.4 executor_output_parse_status
```text
status = valid
-> 使用结构化可推导性校验
status = missing / malformed
-> 不再回退到自然语言逐字抽取
-> verdict = LOW_CONFID
-> groundedness_score = 0
```
### 15.5 executor_raw_output
`executor_raw_output` 只用于审计和 debug。
当 `executor_structured_output` 合法时,Verifier 不应从 `executor_raw_output` 中抽取额外事实。
---
## 16. Verifier 输出结构
V2 Verifier 输出从 `facts_checked` 转向 `claim_checks`。
```json
{
"verdict": "LOW_CONFID",
"groundedness_score": 0.62,
"claim_checks": [
{
"claim_id": "claim-1",
"verification": "direct_observation",
"detail": "query_metrics 显示 CPU 使用率为 92%,可直接支撑 CPU 过高现象。",
"evidence_refs": [
{
"trace_ref": "trace-1",
"tool_name": "query_metrics",
"source_invocation_ids": [394],
"note": "指标摘要包含 CPU=92%"
}
]
}
],
"hypothesis_checks": [
{
"hypothesis_index": 0,
"verification": "reasonable_hypothesis",
"detail": "该假设明确标注为未证实,并列出需要补充的证据。"
}
],
"rationale": "已有证据支持超时现象,但根因仍缺少直接证据。"
}
```
兼容期建议同时保留 `facts_checked`:
- `claim_checks` 作为 V2 主字段。
- `facts_checked` 由 `claim_checks` 映射生成,供现有 Trace Workbench、eval 和降级输出继续使用。
- 待前端和评测全部迁移后,再移除 `facts_checked`。
### 16.1 字段定义
| 字段 | 类型 | 定义 |
|---|---|---|
| `verdict` | string | `PASS` / `LOW_CONFID` / `REJECT` |
| `groundedness_score` | number | 结构化 claim 的证据支撑评分 |
| `claim_checks` | array | 对 `claims` 的可推导性校验结果 |
| `claim_checks[].claim_id` | string | 被校验的 claim id |
| `claim_checks[].verification` | string | 可推导性判定 |
| `claim_checks[].detail` | string | 判定说明 |
| `claim_checks[].evidence_refs` | array | 使用的证据引用 |
| `hypothesis_checks` | array | 对 hypotheses 的边界校验 |
| `hypothesis_checks[].hypothesis_index` | number | hypothesis 数组下标 |
| `hypothesis_checks[].verification` | string | `reasonable_hypothesis` / `unsupported_hypothesis` / `overstated_as_fact` |
| `hypothesis_checks[].detail` | string | 判定说明 |
| `rationale` | string | 总体判定理由 |
### 16.1.1 facts_checked 兼容映射
兼容期内,ChatService 需要从 `claim_checks` 生成旧字段 `facts_checked`。
映射规则:
| `claim_checks[].verification` | `facts_checked[].verification` |
|---|---|
| `direct_observation` | `direct_evidence` |
| `reasonable_inference` | `indirect_support` |
| `overstated` | `indirect_support` |
| `unsupported` | `no_evidence` |
| `external_unknown` | `no_evidence` |
| `contradicted` | `contradicted` |
`facts_checked[].fact` 建议格式:
```text
{claim_id}: {claim_text}
```
`facts_checked[].is_critical` 规则:
```text
claim_type in ["root_cause", "symptom", "impact", "risk"] -> true
其它 -> false
```
`facts_checked[].evidence_refs` 直接复用 `claim_checks[].evidence_refs`。
### 16.2 claim verification 枚举
| verification | 含义 |
|---|---|
| `direct_observation` | 证据直接观测到该事实 |
| `reasonable_inference` | 证据没有逐字说明,但可以合理推出 |
| `overstated` | 有部分依据,但 claim 说得太满 |
| `unsupported` | 证据不足 |
| `external_unknown` | 引入证据外的新服务、数值、错误码、根因等 |
| `contradicted` | 与证据冲突 |
`external_unknown` 需要区分严重程度:
| 场景 | 建议 verdict |
|---|---|
| 引入证据外的核心服务名、订单号、错误码、关键指标值、根因 | `REJECT` |
| 引入非核心背景实体或表达不清 | `LOW_CONFID` |
---
## 17. Verifier 判定矩阵
### PASS
必须同时满足:
- `gatekeeper_result.status != fail`
- 所有核心 claims 均为 `direct_observation` 或 `reasonable_inference`
- 至少一个关键 claim 为 `direct_observation`
- 不存在 `overstated`
- 不存在 `unsupported`
- 不存在 `external_unknown`
- 不存在 `contradicted`
### LOW_CONFID
满足任一条件:
- 存在 `unsupported`
- 存在 `external_unknown`
- 存在 `overstated`
- 所有核心 claims 都只是 `reasonable_inference`
- `executor_output_parse_status.status` 为 `missing` 或 `malformed`
- `gatekeeper_result.status = fail`,但失败不属于严重伪造
### REJECT
满足任一条件:
- 任一核心 claim 为 `contradicted`
- Gatekeeper 发现严重伪造:
- 编造 `source_invocation_ids`
- 跨 session 引用
- `tool_name` 与 invocation 不匹配
- 使用真实 id 但 excerpt 明显张冠李戴
- Verifier 发现核心 `external_unknown`:
- 新增证据外服务名
- 新增证据外订单号
- 新增证据外错误码
- 新增证据外关键指标值
- 新增证据外根因
严重伪造由 Gatekeeper 规则元数据中的 `verdictImpact=reject_on_fail` 定义。
---
## 18. 与 V1 Verifier 的差异
| 项目 | V1 | V2 |
|---|---|---|
| 主校验对象 | `executor_final_answer` 和 structured output | `executor_structured_output` |
| 校验方式 | 从自然语言中抽取 facts 并逐条校验 | 对 claims 做可推导性校验 |
| 原始文本作用 | 事实抽取来源之一 | debug/fallback only |
| 输出字段 | `facts_checked` | `claim_checks` |
| Gatekeeper | 无 | 前置物理级校验 |
| malformed 输出 | 可回退自然语言校验 | 直接 LOW_CONFID |
---
## 19. Composer 设计
Composer 是最终表达层。它不负责诊断,不调用工具,不补充新事实。
```text
Executor structured output
-> Gatekeeper
-> Verifier
-> Composer
-> final answer
```
### 19.1 职责边界
| 组件 | 职责 |
|---|---|
| Executor | 输出结构化诊断材料 |
| Gatekeeper | 做物理级确定性校验 |
| Verifier | 判断 claims 是否可由证据合理推出 |
| Composer | 将 Verifier 允许输出的材料组织成用户可读中文答案 |
Composer 禁止:
- 调用工具。
- 重新诊断。
- 重新判断根因。
- 新增服务名、订单号、时间、指标值、错误码、根因。
- 把 hypothesis 写成 confirmed claim。
- 把相关性写成因果。
### 19.2 Composer 输入
Composer 输入应由 ChatService 根据 Executor、Gatekeeper、Verifier 的结果过滤得到。
```json
{
"original_query": "用户原始问题",
"verdict": "LOW_CONFID",
"allowed_claims": [
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "订单123支付失败期间出现 ERR_TIMEOUT,请求耗时 5.3 秒。"
}
],
"allowed_hypotheses": [
{
"hypothesis_text": "网关超时阈值可能偏低。",
"needed_evidence": ["网关超时阈值配置"]
}
],
"missing_info": [
"缺少网关侧具体配置参数,无法确认阈值是否过低。"
],
"recommended_actions": [
{
"action_text": "检查网关侧超时阈值配置",
"reason": "当前缺少网关配置参数,需确认阈值是否低于实际请求耗时。"
}
],
"rationale": "已有证据支持请求超时现象,但根因仍缺少直接证据。"
}
```
| 字段 | 类型 | 定义 |
|---|---|---|
| `original_query` | string | 用户原始问题 |
| `verdict` | string | Verifier verdict |
| `allowed_claims` | array | Verifier 允许作为确认事实输出的 claims |
| `allowed_hypotheses` | array | Verifier 允许作为合理推测输出的 hypotheses |
| `missing_info` | array | 证据缺口 |
| `recommended_actions` | array | 允许输出的建议动作 |
| `rationale` | string | Verifier 总体判定理由 |
Composer 不应接收 raw tool output,也不应接收未经筛选的完整 Executor 输出。
### 19.2.1 Composer 输入组装规则
ChatService 根据 Verifier 输出组装 Composer 输入:
| Verifier 结果 | Composer 输入 |
|---|---|
| `claim_checks[].verification = direct_observation` | 放入 `allowed_claims` |
| `claim_checks[].verification = reasonable_inference` | 可放入 `allowed_claims`,但表达时不得写成唯一根因 |
| `claim_checks[].verification = overstated` | 不放入 `allowed_claims`,可降级为 `allowed_hypotheses` 或 `missing_info` |
| `claim_checks[].verification = unsupported` | 不输出为事实,放入 `missing_info` |
| `claim_checks[].verification = external_unknown` | 不输出,必要时放入 `missing_info` |
| `claim_checks[].verification = contradicted` | 不输出,触发 `REJECT` 降级表达 |
`allowed_hypotheses` 只允许来自:
- 原始 `hypotheses`。
- `hypothesis_checks` 判为 `reasonable_hypothesis` 的条目。
- 被 Verifier 判为 `overstated` 后降级的 claim。
Composer 执行条件:
```text
Verifier 输出有效 verdict 后才执行 Composer。
executor_output_parse_status = missing/malformed 时,可跳过 Composer,直接使用固定低置信模板。
gatekeeper_result.status = fail 且 verdict = REJECT 时,Composer 只能接收 allowed_claims、missing_info、recommended_actions,不接收 allowed_hypotheses。
```
REJECT 场景下:
- `allowed_hypotheses` 必须为空。
- `user_facing_answer` 不得出现根因结论。
- 推荐动作只能是补证据或人工复核类动作。
### 19.3 Composer 输出
Composer 输出严格 JSON。
```json
{
"answer_summary": "当前已确认订单123支付失败期间出现 ERR_TIMEOUT,请求耗时 5.3 秒;网关阈值是否过低尚未确认。",
"recommended_actions": [
{
"action_text": "检查网关侧超时阈值配置",
"reason": "当前缺少网关配置参数,需确认阈值是否低于实际请求耗时。"
}
],
"user_facing_answer": "您的订单123在支付失败期间出现了请求超时,记录显示 ERR_TIMEOUT,耗时约 5.3 秒。目前还不能确认网关阈值配置就是根因,因为缺少网关侧具体配置参数。建议下一步检查网关超时阈值,并与该请求耗时进行比对。"
}
```
| 字段 | 类型 | 定义 |
|---|---|---|
| `answer_summary` | string | 一句话摘要,只能总结 Verifier 允许输出的内容 |
| `recommended_actions` | array | 面向用户的建议动作 |
| `recommended_actions[].action_text` | string | 建议动作 |
| `recommended_actions[].reason` | string | 建议原因 |
| `user_facing_answer` | string | 最终面向用户的中文答案 |
### 19.4 Verdict 表达规则
#### PASS
- 可以表达确认结论。
- 只能使用 `allowed_claims` 和 `recommended_actions`。
- 只有当 `allowed_claims` 中存在 root cause 类型 claim 时,才能使用“根因已确认”类表述。
#### LOW_CONFID
- 必须说明当前证据仍有缺口。
- 必须区分“已确认信息”和“可能方向”。
- 不得把 `allowed_hypotheses` 写成确认结论。
- 必须包含至少一个证据缺口或下一步建议。
#### REJECT
- 必须说明当前无法基于已获取证据生成可靠结论。
- 不得输出根因结论。
- 只输出已确认信息和下一步建议。
### 19.5 Composer Prompt 草案
```text
你是 Answer Composer。你的职责是把 Verifier 允许输出的结构化材料,组织成用户可读的中文答案。
边界:
- 你不负责诊断。
- 你不调用工具。
- 你不补充新事实。
- 你不重新判断根因。
- 你只能使用输入中的 allowed_claims、allowed_hypotheses、missing_info、recommended_actions、rationale。
输入字段:
- original_query:用户原始问题
- verdict:PASS / LOW_CONFID / REJECT
- allowed_claims:允许作为确认事实输出的结论
- allowed_hypotheses:允许作为合理推测输出的内容
- missing_info:证据缺口
- recommended_actions:建议动作
- rationale:Verifier 判定理由
表达规则:
1. 如果 verdict=PASS:
- 可以表达确认结论。
- 只能使用 allowed_claims 和 recommended_actions。
2. 如果 verdict=LOW_CONFID:
- 必须说明当前证据仍有缺口。
- 必须区分“已确认”和“可能方向”。
- 不得把 allowed_hypotheses 写成确认结论。
3. 如果 verdict=REJECT:
- 必须说明当前无法基于已获取证据生成可靠结论。
- 不得输出根因结论。
- 只输出已确认信息和下一步建议。
禁止:
- 禁止新增输入中不存在的服务名、订单号、时间、指标值、错误码、根因。
- 禁止把相关性写成因果。
- 禁止把假设写成事实。
- 禁止输出 Markdown。
- 禁止输出 JSON 之外的任何文字。
输出格式:
{
"answer_summary": "...",
"recommended_actions": [
{
"action_text": "...",
"reason": "..."
}
],
"user_facing_answer": "..."
}
```
---
## 20. 代码影响面
本 issue 按当前三 Agent 实现增量落地,不改 Planner,不引入 Controller。本节描述整体改造影响面;已完成阶段见 `21. 实施阶段`,当前下一步以阶段四 Composer 为准。
| 模块 | 当前职责 | 本次改动 |
|---|---|---|
| `chat-planner-prompt.md` | 选择 skill、拆解计划 | 不改 |
| `chat-executor-prompt.md` | 执行工具并输出 `executor_evidence_v1` | 改为输出 `executor_evidence_v2`,移除最终表达字段 |
| `VerifierInputHook` | 解析 Executor 输出、构造 Verifier payload | 收敛为 parse + trace summary + Gatekeeper + payload |
| `ToolTraceSummaryService` | 从 `tool_invocation` 汇总证据索引 | 原则上不改;只有 Gatekeeper 缺少比对字段时才补最小字段 |
| `chat-verifier-prompt.md` | 校验 `executor_final_answer` 与证据 | 改为校验 `executor_structured_output.claims` 的可推导性 |
| `ChatService.parseVerifierDecision(...)` | 解析 `facts_checked` | 增加 `claim_checks` 解析和 `facts_checked` 兼容生成 |
| `ChatService.persistVerifierEvaluation(...)` | 写入 verifier 审计结果 | 增加 `gatekeeper_result`、`claim_checks`、Composer 结果 |
| `ChatService` 最终答案渲染 | PASS 使用 `user_facing_answer`,LOW_CONFID/REJECT 用模板 | 改为由 Composer 或固定降级模板生成最终答案 |
第一版落地原则:
- Planner 不改。
- Gatekeeper 仍放在 Verifier 的 hook,即 `VerifierInputHook`。
- `VerifierInputHook` 原有结构校验迁移到 Gatekeeper,hook 自身只保留 JSON parse。
- 兼容期保留 `executor_final_answer`,但只作为 raw debug 字段。
- 兼容期保留 `facts_checked`,但由 `claim_checks` 映射生成。
---
## 21. 实施阶段
每个阶段都必须作为独立 OpenSpec change 落地。后续 agent 不应直接从本节复制代码实现,而应把本节转成该阶段的 `proposal.md`、`design.md`、`spec.md` 和 `tasks.md`。
### 阶段一:Executor V2 输出契约
状态:已完成并归档。
已完成记录:
- OpenSpec archive:`openspec/changes/archive/2026-07-07-executor-v2-output-contract`
- devflow:`devflow/projects/2026-07-07-executor-v2-output-contract`
- commit:`050cbc8 feat(agent): add executor evidence v2 contract`
目标:先让 Executor 不再输出最终诊断话术,只输出结构化诊断材料。
改动范围:
- 更新 `chat-executor-prompt.md`。
- `answer_version` 从 `executor_evidence_v1` 改为 `executor_evidence_v2`。
- 移除 `diagnosis_summary` 和 `user_facing_answer`。
- 保留 `claims`、`hypotheses`、`recommended_actions`、`missing_info` 的整体形状。
验收标准:
- Executor 示例输出可被 JSON 解析。
- 输出中不再出现 `diagnosis_summary`、`user_facing_answer`。
- `claims[].evidence_bindings` 仍要求非空。
- 现有流程即使尚未接入 Composer,也不会把 Executor raw JSON 直接当最终答案泄露给用户。
已知阶段性债务:
- `ChatService` 仍有临时 V2 renderer,用于 Composer 上线前避免 raw JSON 外泄。
- Composer 上线后需要移除或降级阶段一临时 V2 renderer,避免 PASS 答案继续绕过 Composer。
### 阶段二:Gatekeeper 接入 VerifierInputHook
状态:已完成并归档。
已完成记录:
- OpenSpec archive:`openspec/changes/archive/2026-07-07-executor-gatekeeper-hook`
- devflow:`devflow/projects/2026-07-07-executor-gatekeeper-hook`
- commit:`c5e496e feat(agent): add executor gatekeeper hook`
目标:在 Verifier 之前用确定性规则拦截物理级幻觉。
改动范围:
- 新增 Gatekeeper 校验服务,第一版先覆盖必要规则。
- 在 `VerifierInputHook` 中调用 Gatekeeper。
- 将 `gatekeeper_result` 写入 Verifier payload。
- 将 `gatekeeper_result` 持久化到 `self_evaluation.verifier_evaluation`。
- 更新 `chat-verifier-prompt.md`,明确 Gatekeeper fail 时不得 PASS。
第一版规则范围:
| 规则 | 必须实现 | 说明 |
|---|---:|---|
| `schema.executor_v2` | 是 | 校验 V2 基础字段、移除字段、数组字段 |
| `evidence.invocation_ref` | 是 | 校验 invocation id 存在于当前 session 且工具名匹配 |
| `evidence.excerpt_similarity` | 否 | 可作为后续阶段或 warn-only 增强 |
| `claim.hallucination_phrases` | 否 | 可作为后续配置化规则增强 |
| `claim.evidence_utilization` | 否 | 第一版不建议硬启用,避免中文分词误杀 |
验收标准:
- JSON parse 成功但 schema 不合法时,`executor_output_parse_status.status=valid`,`gatekeeper_result.status=fail`。
- 编造不存在的 `source_invocation_ids` 时,`failed_rules` 包含 `evidence.invocation_ref`。
- `tool_name` 与真实 invocation 不匹配时,Gatekeeper fail。
- 缺省 `hypotheses`、`recommended_actions`、`missing_info` 时,传给 Verifier 前会 normalize 为 `[]`。
- 旧的 `VerifierInputHook` 结构校验不再和 Gatekeeper 重复维护。
- `diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result` 可回溯到本轮校验结果。
建议测试:
- `ExecutorGatekeeperServiceTest`
- `VerifierInputHookTest`
- `ChatServiceSequentialAgentTest`
建议验证命令:
```powershell
mvn "-Dtest=ExecutorGatekeeperServiceTest,VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate --specs
```
阶段退出条件:
- OpenSpec change 已 archive。
- devflow 已回填 brief、evidence、decisions、acceptance。
- 本阶段代码和文档已单独 commit。
### 阶段三:Verifier V2 可推导性校验
状态:已完成并归档。
已完成记录:
- OpenSpec archive:`openspec/changes/archive/2026-07-07-executor-verifier-claim-checks`
- devflow:`devflow/projects/2026-07-07-executor-verifier-claim-checks`
- commit:`1b31e78 feat(agent): add verifier claim checks`
目标:Verifier 不再逐字扫描自然语言,而是校验 claim 是否能由证据合理推出。
改动范围:
- 更新 `chat-verifier-prompt.md`。
- Verifier 主输入改为 `executor_structured_output`。
- 输出新增 `claim_checks`。
- 兼容期继续输出或由代码生成 `facts_checked`。
- `parseVerifierDecision(...)` 能处理 `claim_checks` 和旧 `facts_checked`。
核心设计:
- Verifier 主校验对象是 `executor_structured_output.claims`。
- `executor_raw_output` / `executor_final_answer` 只作为 debug/fallback 字段,不作为新增事实来源。
- Verifier 不要求 claim 与证据逐字一致,而是判断是否可以合理推出。
- Gatekeeper fail 时,Verifier 不允许输出 `PASS`。
验收标准:
- `direct_observation`、`reasonable_inference`、`overstated`、`unsupported`、`external_unknown`、`contradicted` 均有测试覆盖。
- `gatekeeper_result.status=fail` 时 Verifier 不允许输出 `PASS`。
- malformed/missing Executor 输出不回退自然语言抽事实,直接进入 `LOW_CONFID`。
- `facts_checked` 兼容字段能继续支撑现有低置信模板、retry_context 和评测用例。
建议测试重点:
- `parseVerifierDecision(...)` 能解析 `claim_checks`。
- `facts_checked` 能从 `claim_checks` 兼容映射。
- `gatekeeper_result.status=fail` + Verifier 返回 PASS 时,代码侧应降级或测试 prompt 禁止该行为。
- `unsupported` / `overstated` 能进入低置信模板需要的 missing evidence 语义。
建议验证命令:
```powershell
mvn "-Dtest=ExecutorGatekeeperServiceTest,VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate --specs
```
阶段退出条件:
- Verifier prompt 已不再要求逐字扫描最终自然语言答案。
- 审计中同时可见 `claim_checks` 和兼容 `facts_checked`。
- 现有低置信重试链路不回归。
- OpenSpec change 已 archive。
- devflow 已回填 brief、evidence、decisions、acceptance。
- 本阶段代码和文档已单独 commit。
### 阶段四:Composer 输出最终答案
状态:下一阶段,待启动。
建议 OpenSpec change:
```text
openspec/changes/executor-composer-final-answer
```
目标:把最终用户表达从 Executor 中移出,由 Composer 基于 Verifier 允许的材料生成。
改动范围:
- 新增 `chat-composer-prompt.md` 或等价 Composer 调用。
- ChatService 在 Verifier 之后组装 Composer 输入。
- Composer 只接收 `allowed_claims`、`allowed_hypotheses`、`missing_info`、`recommended_actions`、`rationale`。
- PASS/LOW_CONFID/REJECT 均不再读取 Executor 的 `user_facing_answer`。
核心设计:
- Composer 是表达层,不是诊断层。
- Composer 不接收 raw tool output。
- Composer 不接收未经筛选的完整 Executor output。
- ChatService 负责根据 `claim_checks` 过滤出 `allowed_claims`。
- `overstated` claim 不得作为确认事实输出,可降级为 hypothesis 或 missing_info。
建议 OpenSpec 内容:
- `proposal.md`:说明为什么需要 Composer,重点写清“最终表达不能再读 Executor 输出”。
- `design.md`:说明 Composer 输入过滤、输出解析、malformed fallback、审计落点。
- `specs/.../spec.md`:补充 Composer 最终答案的行为场景,能力归属可沿用现有 `chat-verifier-agent` 或新增更合适的 capability,但必须保持命名清晰。
- `tasks.md`:按 prompt、ChatService 组装、输出解析、审计、测试、归档拆任务。
- `decisions.md`:记录 PASS/LOW_CONFID/REJECT 的降级策略和 Composer 失败 fallback。
建议代码入口:
- `src/main/resources/prompts/chat-composer-prompt.md`
- `src/main/java/com/superbiz/agent/service/ChatService.java`
- `src/test/java/com/superbiz/agent/service/ChatServiceSequentialAgentTest.java`
- 如需要新增专门测试,可新增 Composer 输入过滤或输出解析的 focused test。
验收标准:
- Composer 输出严格 JSON,包含 `answer_summary`、`recommended_actions`、`user_facing_answer`。
- Composer 不接收 raw tool output。
- `REJECT` 时 `allowed_hypotheses=[]`,最终答案不出现根因结论。
- `LOW_CONFID` 时必须区分已确认信息和可能方向。
- `PASS` 时只有存在 root cause 类型 allowed claim,才允许表达“根因已确认”。
- `ChatService` 不再依赖临时 V2 renderer 生成 PASS 用户答案。
建议测试重点:
- Composer 输入不包含 raw tool output。
- 未通过 Verifier 的 claim 不进入最终答案。
- `LOW_CONFID` 能输出已确认信息、缺口、下一步建议。
- `REJECT` 不输出 root cause 结论。
- Composer malformed 输出时有降级策略,不向用户泄露 raw JSON。
建议验证命令:
```powershell
mvn "-Dtest=ChatServiceSequentialAgentTest" test
cmd /c openspec validate executor-composer-final-answer
cmd /c openspec validate --specs
```
阶段退出条件:
- 最终用户答案唯一来源是 Composer 或固定降级模板。
- Executor 的 `user_facing_answer` 不再参与任何最终答案路径。
- Stage 1 的临时 renderer 被移除或明确只作为关闭 Composer 时的安全降级,不恢复 Executor 表达。
- OpenSpec change 已 archive。
- devflow 已回填 brief、evidence、decisions、acceptance。
- 本阶段代码和文档已单独 commit。
### 阶段五:回归评测与审计闭环
状态:未开始。
目标:确认新链路真的降低证据归因幻觉,而不是只改变字段名。
改动范围:
- 扩展 `VerifierInputHookTest`。
- 扩展 `ChatServiceSequentialAgentTest`。
- 增加诊断 eval fixtures,覆盖伪造 ID、张冠李戴、过度推断、缺证据降级。
- 检查 `diagnosis_session.self_evaluation` 中的审计字段。
验收标准:
- 伪造 invocation id 必须进入 `REJECT` 或至少不可 `PASS`。
- excerpt 相似度不足但 invocation 合法时,默认进入 `LOW_CONFID`,不直接误杀为严重伪造。
- Executor 把 hypothesis 写进 claim 时,Verifier 至少判 `overstated` 或 `unsupported`。
- 最终答案中不再出现未通过 Verifier 的 claim。
- 审计链路能从 final answer 回溯到 Composer 输入、Verifier 判定、Gatekeeper 结果、tool invocation。
建议 fixture 场景:
| 场景 | 预期 |
|---|---|
| 伪造不存在的 invocation id | Gatekeeper fail,最终不可 PASS |
| 引用真实 id 但 tool_name 不匹配 | Gatekeeper fail,倾向 REJECT |
| excerpt 与真实工具输出明显不符 | Gatekeeper fail 或 warn,最终不可 PASS |
| hypothesis 写成 root_cause claim | Verifier 判 `overstated` 或 `unsupported` |
| Composer 输入不含某错误码但输出包含该错误码 | 测试失败 |
阶段退出条件:
- 关键回归测试已进入 CI 可运行测试集合。
- eval fixture 覆盖证据归因幻觉的主路径。
- 文档、OpenSpec、devflow 与实际代码行为一致。
---
## 22. 兼容与回滚策略
### 22.1 兼容字段
第一版保留以下兼容字段,降低一次性改动风险:
| 字段 | 保留原因 | 后续处理 |
|---|---|---|
| `executor_final_answer` | 当前 Verifier payload、fallback、日志中仍使用该名称 | 作为 deprecated raw 字段保留一版 |
| `facts_checked` | 当前低置信模板、retry_context、评测可能依赖 | 从 `claim_checks` 映射生成 |
| `traceability_version` | 当前审计结构已有字段 | V2 可改为 `v2`,但不作为功能判断依据 |
### 22.2 回滚开关
建议保留最小运行时开关:
```text
structuredOutputV2.enabled
gatekeeper.enabled
composer.enabled
```
回滚策略:
- 仅 Executor V2 出问题:关闭 `structuredOutputV2.enabled`,回到 V1 prompt。
- Gatekeeper 误杀:关闭 `gatekeeper.enabled`,Verifier 仍可按 V2 claim 校验运行。
- Composer 输出异常:关闭 `composer.enabled`,回到固定 LOW_CONFID/REJECT 模板;PASS 暂不直接使用 Executor 输出。
即使回滚 Composer,也不能恢复使用 Executor 的 `user_facing_answer`,否则会重新引入本 issue 要解决的问题。
---
## 23. 数据库与审计最小字段
不新增表,第一版继续写入 `diagnosis_session.self_evaluation`。
建议结构:
```json
{
"verifier_evaluation": {
"verdict": "LOW_CONFID",
"groundedness_score": 0.62,
"critical_fact_count": 1,
"claim_checks": [],
"facts_checked": [],
"rationale": "...",
"round": 1,
"traceability_version": "v2",
"executor_output_parse_status": {},
"executor_structured_output": {},
"tool_trace_summary": [],
"gatekeeper_result": {},
"composer_output": {}
}
}
```
字段控制原则:
- Gatekeeper 只落 `status`、`failed_rules`、`warnings`、`errors`。
- 不在数据库中保存规则元数据完整快照。
- 不新增 `checked_at`、`rule_set_version`、`details` 等重字段。
- 如果后续需要复盘规则版本,再单独设计审计版本字段。
---
## 24. 实施前待确认点
以下问题不阻塞第一阶段,但实施前需要明确默认答案:
| 问题 | 建议默认 |
|---|---|
| Composer 是第四个 Agent 还是普通服务调用? | 作为 `chat_composer` Agent,但由 ChatService 在 Verifier 后过滤输入再调用 |
| `schema.executor_v2` 失败是否 REJECT? | 第一版 `LOW_CONFID`,因为它可能是格式退化,不一定是伪造 |
| `evidence.excerpt_similarity` 失败是否 REJECT? | 默认 `LOW_CONFID`;只有明显张冠李戴才 REJECT |
| `claim.evidence_utilization` 是否启用? | 第一版禁用或 warn-only,避免中文分词误杀 |
| 是否改 Planner? | 不改 |
| 是否改现有重试机制? | 不改;Gatekeeper 失败第一版不触发自动回退重试 |
---
## 25. Definition of Done
本 issue 完成时,需要同时满足:
- Executor 不再输出 `diagnosis_summary` 和 `user_facing_answer`。
- Gatekeeper 已接入 `VerifierInputHook`,并进入 Verifier payload 与 `self_evaluation`。
- Verifier 以 `claim_checks` 为主输出,并保留 `facts_checked` 兼容。
- Composer 负责最终 `user_facing_answer`。
- PASS 答案不包含未通过 Verifier 的 claim。
- LOW_CONFID/REJECT 答案不泄露 Executor 原始结论。
- 关键回归测试覆盖伪造 ID、工具名不匹配、excerpt 张冠李戴、hypothesis 写成事实、schema 退化。