# Executor Structured Output V2 可执行设计与实施 Issue > 归档说明:本文记录旧的分阶段实施方案,其中的兼容期和待启动状态不再适用。当前实现以 `mvp/architecture/executor-evidence-pipeline-refactor.md` 和 OpenSpec 主规格为准。 **状态**:阶段四待启动,前三阶段已归档并提交 **严重程度**:高 **创建日期**: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/-/`,然后 `cmd /c openspec archive -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 退化。