feat(harness): add information gain stop and audit

This commit is contained in:
aruo
2026-07-27 01:03:34 +08:00
parent de5a5b09d9
commit d0452184ee
92 changed files with 5019 additions and 122 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
# MVP 架构文档
**更新日期**:2026-07-23
**更新日期**:2026-07-26
**状态**:当前单 Diagnosis Agent + Harness 架构
当前文档入口:
@@ -11,6 +11,7 @@
| [agent-orchestration.md](agent-orchestration.md) | 单 Diagnosis ReAct Agent 的职责、执行方式和 reasoning 采集边界 |
| [harness-quality-gates.md](harness-quality-gates.md) | Run、Tool、Evidence、Semantic、Release 与 Trace Recorder 门禁 |
| [session-trace-lifecycle.md](session-trace-lifecycle.md) | sessionId/runId、SSE、统一 Timeline 和 reasoning audit 生命周期 |
| [diagnosis-information-gain-stop-architecture.md](diagnosis-information-gain-stop-architecture.md) | 已实施的信息增益评价、Harness 饱和检测、Draft 合同失败降级与证据不足停止设计 |
2026-07-22 前的多角色编排、双入口和旧证据链文档已移动到 `archive/2026-07-22-legacy/`,仅用于历史决策追溯,不代表当前运行时。
@@ -0,0 +1,608 @@
# Diagnosis Agent 信息增益与停止控制架构
**更新日期**:2026-07-27
**状态**:已实施,待归档
**关联 Issue**:[ISS-016](../issues/active/ISS-016-diagnosis-information-gain-stop-contract.md)
## 1. 背景
当前 Diagnosis ReAct Agent 在知识库没有直接答案、日志持续为空或查询条件不足时,可能继续改写查询并反复调用 Tool,直到资源预算耗尽。此时 Run 被发布为技术失败,用户只能看到通用错误,而不是“已完成有限排查,但证据不足”。
本设计解决两个不同问题:
1. 查询何时已经不再产生有效信息,必须停止继续调用 Tool。
2. 停止后如何避免模型在证据不足时生成无依据结论。
## 2. 核心原则
```text
Tool:返回客观数据
Harness:判断查询过程是否还允许继续
Model:判断数据是否推进了当前诊断,并通过实际 Tool Call 或 Draft 表达行为
SemanticGuard:判断最终结论是否有证据支撑
Release:统一把 Draft 或 Harness 停止投影为安全的 SUCCESS / FALLBACK
```
停止权属于 Harness。模型可以建议继续,但不能绕过 Harness 的饱和状态和硬资源边界。
“没有找到根因”是合法业务结果,不等于执行失败。模型被允许输出 `conclusion=null` 的 `DiagnosisDraft`;最终仍只使用 `SUCCESS / FALLBACK / FAILED / CANCELLED`,不增加第二套诊断终态。
## 3. 总体架构
```mermaid
flowchart TD
U["用户提出诊断问题"] --> M["Diagnosis Agent"]
P["ReAct Prompt<br/>允许合法放弃"] -.-> M
C["会话上下文<br/>有界历史和已检查范围"] -.-> M
M -->|"Tool Call Envelope"| B["Harness 调用门禁"]
B --> A["应用 previous_observation<br/>更新连续 NO_GAIN"]
A --> GATE{"是否允许继续"}
GATE -->|"INFORMATION_SATURATED"| STOP["拒绝调用<br/>注入 STOP_REQUIRED"]
GATE -->|"BUDGET_LIMIT_REACHED"| BSTOP["停止调用<br/>记录资源保护原因"]
GATE -->|"允许调用"| T["剥离控制字段<br/>执行业务 Tool"]
T -->|"执行失败"| ERR["Tool 状态:FAILED"]
ERR --> EP["技术故障策略<br/>有限重试或 FAILED 结束"]
T -->|"执行成功"| CR["Canonical Tool Result<br/>完整内部结果"]
CR --> CV["Harness Control View"]
CR --> AP["Agent Observation Projector"]
CR --> STORE["Canonical Store"]
AP --> MO["Model Observation<br/>白名单有界结果"]
MO --> SM["模型阅读返回内容"]
CV --> O{"Harness 客观判断"}
O -->|"evidence_status=NO_EVIDENCE"| NG1["Harness 标记 NO_GAIN"]
O -->|"重复 Tool + 相同 scope"| NG1
O -->|"其他成功非空结果<br/>包括 REFERENCE"| SM
SM --> SD{"语义信息增益"}
SD -->|"推进或排除诊断假设"| G["GAINED"]
SD -->|"没有可验证的新事实"| NG2["NO_GAIN"]
G --> M
NG1 --> M
NG2 --> M
M -->|"DiagnosisDraft"| SNAP["结束时投影 ProgressSnapshot"]
M -->|"空或非法 Draft"| DRAFTERR["丢弃非法内容<br/>投影当前进展"]
DRAFTERR -->|"有已验真 observed facts"| SNAP
DRAFTERR -->|"无安全进展"| EP
STOP -->|"给模型一次合法完成机会"| M
STOP -->|"仍无合法 Draft"| SNAP
BSTOP --> SNAP
STORE --> SNAP
SNAP --> RELEASE["DiagnosisReleaseUseCase"]
RELEASE -->|"conclusion 非空"| GUARD["EvidenceGuard / Repair / SemanticGuard"]
RELEASE -->|"conclusion 为空或无 Draft"| SAFE["确定性 SafeFallback<br/>不修复结论"]
GUARD -->|"证据支持"| SUCCESS["SUCCESS"]
GUARD -->|"无证据推断"| SAFE
SAFE --> FALLBACK["FALLBACK"]
EP --> FAILED["FAILED"]
SUCCESS --> APP["ChatApplicationUseCase<br/>持久化"]
FALLBACK --> APP
FAILED --> APP
APP --> SSE["ChatSseSession<br/>发送 content/failure + done"]
```
## 4. 状态模型
状态按职责分开,不使用一个枚举表达整个生命周期。
| 维度 | 状态 | 负责人 | 含义 |
|---|---|---|---|
| Tool 执行 | `SUCCEEDED / FAILED` | ToolBoundary | Tool 是否完成技术执行 |
| 信息增益 | `GAINED / NO_GAIN` | Harness 或模型 | 本次结果是否推进当前诊断 |
| 收集生命周期 | `COLLECTING / SATURATED` | Harness | 是否允许继续调用 Tool |
| 内部停止原因 | `INFORMATION_SATURATED / BUDGET_LIMIT_REACHED` | Harness | 为什么停止继续调用 Tool |
| Fallback 原因 | `SafeFallback.type` | DiagnosisReleaseUseCase | 为什么没有发布诊断结论 |
| 最终发布 | `SUCCESS / FALLBACK / FAILED / CANCELLED` | Release | 对外发布结果 |
错误码、停止原因、Fallback 原因和 RAG 相关度是附带字段,不提升为全局生命周期状态。`CONFIRMED / INSUFFICIENT_EVIDENCE / NEED_MORE_INFO` 不作为第二套诊断状态;其中后两种语义由 `SafeFallback.type` 表达。
当前实现中的兼容映射是:内部 `PROJECTING` 不对外暴露,`READY` 对应目标语义 `SUCCEEDED`,`ERROR` 对应目标语义 `FAILED`。
不定义独立的模型行动状态。模型发起 Tool Call 表示继续收集,输出正常 `DiagnosisDraft` 表示完成诊断,输出证据不足 Draft 表示主动停止;Harness 直接从实际输出推导行为。
## 5. Tool 结果与上下文边界
Tool 不判断业务结论,只提供事实和可机械计算的元信息。
```text
tool_call_id 本次调用的唯一标识
execution_status SUCCEEDED | FAILED
returned_count 本次实际返回的记录数量
scope 本次查询实际覆盖的结构化范围
metadata Tool 特有的有限元信息
result 提供给模型的有界结果
```
字段说明:
- `returned_count` 只表达“返回了多少条”,不表示这些内容有诊断价值。
- `scope` 用于识别相同 Tool、相同查询范围的重复调用,例如企业、时间窗、服务和过滤条件。
- `metadata` 保存 Tool 特有信息,例如 RAG 的 `relevance_level`、是否截断和分页信息。
- 第一版不引入 `new_count`。它要求为不同 Tool 建立稳定的结果指纹,而且“新数据”也不等于“有效数据”。
### 5.1 客观信号来源
这些信号不是模型推导的:
| 信号 | 来源 | 含义 |
|---|---|---|
| `PROJECTING / READY / ERROR` | ToolBoundary | 当前实现的调用生命周期;目标外部语义映射为 `SUCCEEDED / FAILED` |
| `EVIDENCE_FOUND / NO_EVIDENCE / ERROR` | Tool Result Projector / ToolBoundary | 是否存在候选结果或发生技术错误 |
| `PRECISE / HIGHLY_RELEVANT / REFERENCE / null` | RAG 后处理规则 | RAG 候选内容的相关度 |
| 重复 `tool + scope` | Harness | 本次查询范围是否与历史调用等价 |
`NO_EVIDENCE` 由各 Projector 根据结果集合是否为空确定。`REFERENCE` 由 RAG 根据归一化相似度及查询提示命中情况计算。两者都不是 LLM 生成的状态,但职责不同:`NO_EVIDENCE` 可由 Harness 机械判定为 `NO_GAIN`;`REFERENCE` 只表示候选内容相关度一般,仍由模型判断是否推进当前诊断。
改造前 `RagResultProjector` 只根据 `evidence` 是否为空生成 `EVIDENCE_FOUND / NO_EVIDENCE`,没有保留上游 `relevanceLevel`,因此会出现:
```text
relevanceLevel = REFERENCE
evidenceBlocks 非空
-> RagResultProjector
-> evidence_status = EVIDENCE_FOUND
```
这不是两个判断冲突,而是 Projector 丢失了相关度维度。当前实现已兼容读取上游 `relevanceLevel` 或 `relevance_level`,并统一保留为 `relevance_level`。
### 5.2 Tool 双视图架构
内部控制信息和模型观察不能继续共用一个无差别 JSON。标准化结果产生两个明确视图:
```mermaid
flowchart TD
A["Tool 原始返回"] --> B["结果标准化"]
B --> C["Canonical Tool Result<br/>完整内部结果"]
C --> D["Harness Control View"]
C --> E["Agent Observation Projector"]
D --> F["Harness<br/>重复检测、低收益计数、预算、饱和状态"]
D --> G["Canonical Store"]
E --> H["Model Observation<br/>最小必要字段"]
H --> I["ToolCallResponse.content"]
I --> J["模型上下文"]
```
改造前会把完整 `agent_result` 作为 `ToolCallResponse.content` 交给模型。当前实现已改为白名单投影,Harness 控制字段默认不进入模型上下文。
字段边界:
| 字段 | Harness | 模型 | 说明 |
|---|---:|---:|---|
| `tool_call_id` | 是 | 是 | 引用与归属校验 |
| 实际查询 `scope` | 是 | 是 | 重复检测与有界负向观察 |
| 有界 `evidence` | 是 | 是 | 模型语义判断所需内容 |
| `evidence_status` | 是 | 是 | 候选结果是否为空或失败 |
| `relevance_level` | 是 | 是 | 只暴露粗粒度标签,不暴露原始分数 |
| `truncated` | 是 | 是 | 提醒模型结果并不完整 |
| `returned_count` | 是 | 否 | Harness 客观统计 |
| 原始相似度和检索轨迹 | 是 | 否 | 内部质量与审计信息 |
| 规范化 scope、重复指纹 | 是 | 否 | Harness 控制信息 |
| 低收益计数、阈值、预算 | 是 | 否 | Run 内部状态 |
| 原始 Tool Response | 是 | 否 | 不进入模型上下文 |
只有当 Harness 必须改变模型行为时,才注入有界控制指令,例如:
```json
{
"stop_required": true,
"reason": "INFORMATION_SATURATED",
"checked_scopes": ["本 Run 已检查范围的有界摘要"]
}
```
计数器、阈值和剩余预算不随控制指令进入模型上下文。
### 5.3 Tool 调用与投影流程
```mermaid
sequenceDiagram
participant Model as Diagnosis Agent
participant Interceptor as Harness Tool Interceptor
participant Gate as Harness Gate
participant Adapter as Tool Adapter
participant Backend as Tool Backend
participant Projector as Result Normalizer / Projector
participant Progress as Progress Tracker
participant Store as Canonical Store
Model->>Interceptor: Tool Call Envelope
Interceptor->>Progress: 校验并应用 previous_observation(如有)
Progress-->>Interceptor: 更新后的收集状态
Interceptor->>Gate: 校验 Run、授权、预算和饱和状态
alt 已经 SATURATED
Gate-->>Interceptor: STOP_REQUIRED
Interceptor-->>Model: 有界停止指令
else 达到硬预算
Gate-->>Interceptor: BUDGET_LIMIT_REACHED
else 允许调用
Gate-->>Interceptor: ALLOW
Interceptor->>Interceptor: 剥离 previous_observation
Interceptor->>Adapter: input 中的 typed request
Adapter->>Backend: 执行只读查询
Backend-->>Adapter: raw response
Adapter->>Projector: 标准化原始结果
Projector-->>Progress: Harness Control View
Projector-->>Store: Canonical Tool Result
Projector-->>Interceptor: Model Observation
Interceptor-->>Model: 有界 Tool Observation
end
```
重复查询由 Harness 根据 `tool_name + normalized_scope` 判断,不属于 Tool 返回状态。规范化只处理确定性的业务参数,例如时间格式、无序数组和非业务字段;第一版不判断自然语言改写是否语义等价。
### 5.4 结束时进展投影
每次 Tool 调用完成后只追加 Canonical Tool Result,不在每轮维护另一份用户可见摘要。Tool Loop 因 Agent 输出 Draft、信息饱和或预算限制结束时,一次性生成内部 `ProgressSnapshot`:
```mermaid
flowchart LR
T["每次 Tool 完成"] --> C["Canonical Tool Result"]
C --> S["Canonical Store"]
S -->|"Tool Loop 结束时只读投影"| P["ProgressSnapshot"]
P --> R["DiagnosisReleaseUseCase"]
R --> O["SafeFallback.observed_facts"]
```
`ProgressSnapshot` 不是新的真理源,也不进入模型上下文。它只包含来源、实际 scope、客观结果摘要、截断标记和 Harness `stop_reason`;原始 Tool Response、Prompt、内部 thought、计数器和剩余预算不进入发布内容。
现有 `SafeFallback` 和前端已经支持 `observed_facts / verified_sources / limitations / next_steps`,因此不新增前端协议。`FallbackType` 增加 `INSUFFICIENT_EVIDENCE / MISSING_REQUIRED_CONTEXT`,分别表达有限排查后证据不足和缺少有效查询条件。
## 6. 信息增益契约
信息增益只保留两个值:
```text
information_gain = GAINED | NO_GAIN
```
### 6.1 GAINED
满足以下任一条件:
- 返回了与当前问题直接相关、可验证的新事实。
- 新事实确认了一个当前诊断假设。
- 新事实排除了一个当前诊断假设。
- 新事实实质性缩小了故障范围。
“排除假设”也是信息增益。信息增益不要求得到最终根因。
### 6.2 NO_GAIN
满足以下任一条件:
- 内容为空、重复或只有通用参考资料。
- 数据虽然非空,但与当前问题没有直接关系。
- 没有新增可验证事实,也没有改变任何诊断假设。
- 只是建议继续查询另一个位置,没有提供新的诊断事实。
- 模型无法判断结果是否推进诊断。
不增加 `UNKNOWN`。无法判断时归入 `NO_GAIN`,避免它成为无限继续查询的出口。
### 6.3 谁负责赋值
```text
FAILED
-> 不产生 information_gain,进入技术故障流程
SUCCEEDED + evidence_status=NO_EVIDENCE
或重复的 tool_name + normalized_scope
-> Harness 直接赋 NO_GAIN
其他 SUCCEEDED 非空结果,包括 relevance_level=REFERENCE
-> 模型必须赋 GAINED 或 NO_GAIN
```
模型只判断“本次结果是否推进当前诊断”,不评价 Tool 产品质量,也不输出 0 到 100 的主观分数。
## 7. 模型步骤契约
模型收到未被 Harness 客观标记为 `NO_GAIN` 的成功非空结果后,如果要继续调用 Tool,必须在下一次 Tool Call 中给出上一调用的信息增益。该设计不要求模型显式声明下一步行动。
```json
{
"previous_observation": {
"tool_call_id": "call-123",
"information_gain": "NO_GAIN"
},
"input": {
"query": "下一次查询参数"
}
}
```
约束:
1. `previous_observation` 只在上一轮存在待模型评价的 Tool Observation 时必填;首次调用以及上一轮已由 Harness 客观赋值时省略。
2. `tool_call_id` 必须指向当前 Run 中最后一个尚未评价的成功 Tool 调用。
3. Harness 必须先校验并应用 `information_gain`,再判断是否允许执行本次 Tool 调用。
4. Harness 消费并剥离 `previous_observation`,业务 Tool 只接收 `input` 中的原有业务参数。
5. 上一个待语义评价的结果未被评价时,Harness 不接受新的 Tool 调用。
6. Harness 已经进入 `SATURATED` 时,新的 Tool Call 被拒绝,并向模型注入 `STOP_REQUIRED`。
7. 模型主动判断没有合理查询方向时,应直接输出证据不足 Draft,不必等待 Harness 强制停止,也无需为放行下一次调用而回传最后一轮评价。
模型行为直接从实际输出推导:
```text
Tool Call -> 继续收集
正常 DiagnosisDraft -> 完成诊断
证据不足 DiagnosisDraft -> 主动停止
```
该 Envelope 是模型侧 Tool 调用协议。服务端为动态注册的 Tool Schema 统一增加 Envelope,Harness 在边界处消费控制字段,业务 Tool 的入参协议保持不变。这是一项有意的协议变更,影响模型 Tool Schema 生成、Tool Call 解析、Harness 拦截和相关测试,不影响 Tool Backend。
## 8. Harness 饱和规则
第一版使用连续低收益计数,不维护复杂进展账本。硬调用上限是独立资源保护,不作为信息饱和条件。
```text
evidence_status=NO_EVIDENCE -> consecutiveLowYield + 1
重复 Tool + 相同 scope -> consecutiveLowYield + 1
成功非空结果(包括 REFERENCE)+ 模型 NO_GAIN
-> consecutiveLowYield + 1
其他成功非空结果 + 模型 GAINED -> consecutiveLowYield = 0
FAILED -> 技术故障流程,不计入低收益
consecutiveLowYield >= stopAfterConsecutiveNoGain
-> SATURATED
-> stop_reason=INFORMATION_SATURATED
达到单 Tool 或 Run 硬调用上限 -> stop_reason=BUDGET_LIMIT_REACHED
```
配置项 `stop-after-consecutive-no-gain` 默认值为 `2`、必须大于等于 `1`,只在 Run 启动时读取并固定,不进入模型上下文。默认值需要通过固定 E2E 和评测集校准,不作为不可调整的业务真理。
伪代码:
```text
onToolResult(result):
if result.execution_status == FAILED:
handleTechnicalFailure(result)
return
if isEmpty(result) or isRepeatedScope(result):
applyInformationGain(NO_GAIN)
return
requireModelAssessment(result.tool_call_id)
applyInformationGain(gain):
if gain == GAINED:
consecutiveLowYield = 0
else:
consecutiveLowYield += 1
if consecutiveLowYield >= stopAfterConsecutiveNoGain:
collectionState = SATURATED
stopReason = INFORMATION_SATURATED
onHardLimitReached():
stopReason = BUDGET_LIMIT_REACHED
stopToolCollection()
```
`SATURATED` 是当前 Run 的 Tool 收集终态。模型不能在同一 Run 中把它恢复为 `COLLECTING`。用户补充新范围或新事实后,应开启新的诊断 Run。
## 9. 控制与发布边界
### 9.1 Harness 层:物理停止
Harness 使用 `NO_EVIDENCE`、重复的 `tool_name + normalized_scope` 和连续 `NO_GAIN` 检测信息饱和。RAG `REFERENCE` 与其他成功非空结果一样,由 Diagnosis Agent 通过下一次 Tool Call Envelope 回传语义评价。硬调用上限只产生 `BUDGET_LIMIT_REACHED`,不伪装成信息饱和。
### 9.2 SemanticGuard 层:无证据推断兜底
只有 `DiagnosisDraft.conclusion` 非空时才进入完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。SemanticGuard 在最终发布前检查:
- 正常诊断中的关键结论是否绑定已验证证据。
- `REFERENCE` 和通用资料是否被错误当作当前故障事实。
- scoped `NO_EVIDENCE` 是否被错误解释为“故障不存在”。
- 证据不足时是否生成了确定性根因。
发现无证据推断时,不继续 Tool Loop,而是转换为有界的证据不足结果。
`conclusion=null` 是合法业务结果,不触发 EvidenceRepair,也不要求额外调用 SemanticGuard 来证明“没有结论”。已有 analysis 时只校验其 Tool 引用真实性;Release 从 `ProgressSnapshot` 和 `limitations` 生成安全 Fallback。零次 Tool 调用且 `limitations.missing_info` 非空时,直接生成 `MISSING_REQUIRED_CONTEXT` Fallback。
### 9.3 ReAct Prompt 层:合法放弃许可证
Prompt 必须明确:
- 不要求模型必须得出诊断结论或根因,但必须输出一个诚实、有界的完整 Draft。
- 无法得到足够证据时,`conclusion=null` 是成功完成,不是失败。
- Tool 调用次数可以为零。缺少企业、时间范围、服务或错误信息,且不存在预期能产生新诊断信息的明确查询时,直接在 `limitations.missing_info` 中列出缺口。
- 不得为了表现“已经排查”而调用没有明确目标的 Tool。
- Tool 返回内容事实正确、表述完整或结果非空,不代表它对当前诊断有信息增益。
- 通用知识、背景说明、重复内容或不能改变当前判断的内容属于 `NO_GAIN`。
- `NO_GAIN` 后不得通过改写相似关键词或重复相同 scope 继续尝试。
- 如果仍存在范围明确、可能产生新诊断信息的不同查询,可以继续;否则应主动停止。
- 不得为了表现“尽力”而重复或扩大无明确目标的查询。
- 收到 `STOP_REQUIRED` 后不得继续调用 Tool。
Prompt 不包含 Tool 名称、Tool Schema 或需要展开 Schema 的文本占位符。Tool 只通过服务端的模型原生 Tool Calling 通道注册;通用 Envelope 由服务端包装动态 Tool Schema。
### 9.4 会话上下文层:历史边界认知
每条 Tool 结果只以白名单 `Model Observation` 进入模型上下文一次。Harness 不在后续步骤中重复注入完整 Tool 结果、原始响应或内部进展账本。
只有 Harness 必须改变模型行为时,才注入有界停止指令:
```text
stop_required
stop_reason
已检查 Tool 和 scope 的有界摘要
```
不向模型注入 `consecutiveLowYield`、阈值、剩余预算、重复指纹、原始相似度、完整 Tool 原始载荷或内部 thought。
### 9.5 Release 层:统一安全发布
`DiagnosisReleaseUseCase` 是诊断业务发布的唯一决策入口:
```text
DiagnosisDraft.conclusion 非空
-> EvidenceGuard
-> 必要时 EvidenceRepair
-> SemanticGuard
-> SUCCESS 或 FALLBACK
DiagnosisDraft.conclusion 为空
-> 不执行 EvidenceRepair
-> 校验已有 Tool 引用(如有)
-> ProgressSnapshot + limitations
-> FALLBACK
Harness 已停止且没有 DiagnosisDraft
-> stop_reason + ProgressSnapshot
-> 确定性 FALLBACK
最终 Draft 为空或违反严格 JSON/Schema 合同
-> 丢弃非法 Draft,不做宽松提取或模型修复
-> ProgressSnapshot 有已验真 observed facts:INSUFFICIENT_EVIDENCE
-> 无安全进展:FAILED
```
Draft 合同失败不是新的 `stop_reason`。Trace 只记录固定失败类别、输出字节数和是否存在可发布进展,不记录模型原文、字段值或解析异常文本。
`ChatApplicationUseCase` 只负责编排、持久化,以及把无法形成安全业务内容的不可恢复故障映射为 `FAILED / CANCELLED`。预算 Fallback 不再由它单独构造。`ChatSseSession` 只发送 `content|failure + done`,不判断诊断语义。
## 10. 生命周期
```mermaid
stateDiagram-v2
[*] --> COLLECTING
COLLECTING --> COLLECTING: GAINED / 低收益清零
COLLECTING --> COLLECTING: NO_GAIN / 未达到阈值
COLLECTING --> SATURATED: 连续 NO_GAIN 达到阈值
COLLECTING --> BUDGET_STOP: 达到硬调用上限
COLLECTING --> TECHNICAL_FAILURE: 不可恢复的 Tool 或基础设施故障
COLLECTING --> DRAFT_READY: 模型输出 DiagnosisDraft
COLLECTING --> DRAFT_INVALID: 最终 Draft 为空或违反合同
COLLECTING --> CANCELLED: 用户取消
SATURATED --> RELEASE_INPUT: INFORMATION_SATURATED + ProgressSnapshot
BUDGET_STOP --> RELEASE_INPUT: BUDGET_LIMIT_REACHED + ProgressSnapshot
DRAFT_READY --> RELEASE_INPUT: Draft + ProgressSnapshot
DRAFT_INVALID --> RELEASE_INPUT: 有已验真 observed facts
DRAFT_INVALID --> FAILED: 无安全进展
RELEASE_INPUT --> EVIDENCE_GUARD: conclusion 非空
RELEASE_INPUT --> FALLBACK: conclusion 为空或无 Draft
EVIDENCE_GUARD --> SEMANTIC_GUARD: 引用有效或修复成功
EVIDENCE_GUARD --> FALLBACK: 引用无法安全验证
SEMANTIC_GUARD --> SUCCESS: 证据支持最终结论
SEMANTIC_GUARD --> FALLBACK: 无证据推断
TECHNICAL_FAILURE --> FAILED
SUCCESS --> [*]
FALLBACK --> [*]
FAILED --> [*]
CANCELLED --> [*]
```
## 11. 示例
查询:`诊断切换企业失败的问题`
```text
第 1 次 lookup_knowledge
execution_status = SUCCEEDED
returned_count = 5
relevance_level = REFERENCE
information_gain = NO_GAIN(模型)
consecutiveLowYield = 1
第 2 次 query_logs
execution_status = SUCCEEDED
returned_count = 0
information_gain = NO_GAIN(Harness)
consecutiveLowYield = 2
Harness
collectionState = SATURATED
拒绝新的 Tool 调用
注入 STOP_REQUIRED
最终结果
stop_reason = INFORMATION_SATURATED
release_outcome = FALLBACK
SafeFallback.type = INSUFFICIENT_EVIDENCE
展示已检查范围、客观结果、限制和需要补充的信息
不发布 INTERNAL_FAILURE
```
如果第二次查询返回了能够排除某个假设的日志,模型应标记 `GAINED`,低收益计数清零,允许继续进行有目标的诊断。
如果用户首次请求即缺少企业、时间范围等有效查询条件,模型可以零次 Tool 调用直接输出 `conclusion=null`,把缺口写入 `limitations.missing_info`。Release 将其发布为 `FALLBACK + MISSING_REQUIRED_CONTEXT`,不执行 EvidenceRepair 或 SemanticGuard。
## 12. 非目标
- 不通过简单增加 Tool、Token 或超时预算解决空转。
- 不让 Tool 或 Harness 判断业务根因。
- 不增加多级质量分数、`UNKNOWN` 或复杂状态矩阵。
- 不引入 `new_count` 和跨 Tool 通用内容指纹。
- 不新增独立 Progress Judge 模型调用。
- 不重新引入 Planner/Executor/Verifier/Composer 多角色 Graph。
## 13. 模型 Token 与 Tool 拒绝审计
Run 使用 `RunBudget` 作为 Token 总账,`diagnosis_trace_event` 作为模型调用明细账,`AgentStep.token_count` 只作为 Diagnosis Agent 轮次摘要,不新增独立 Token 表。
```mermaid
flowchart LR
R["Router / System / Knowledge / Repair / Semantic"] --> G["GuardModelCall"]
A["Diagnosis ReAct round"] --> I["HarnessModelInterceptor"]
G --> U["Provider Usage"]
I --> U
U --> L["Run ModelCallLedger"]
U --> B["RunBudget Token 总账"]
U --> T["MODEL_TOKEN_USAGE Trace"]
I --> S["AgentStep.token_count"]
L --> F["RUN_FINISHED 对账摘要"]
B --> F
```
每个 `MODEL_TOKEN_USAGE` 只记录:
- `component` 与 `component_round`;
- `usage_available`;
- Usage 可用时的 `input_tokens / output_tokens / total_tokens`。
Usage 不可用时不写 Token 字段,不把未知值记成零;Run 结束以 `usage_unavailable_count` 和 `tokens_reconciled=false` 暴露缺口。`RUN_FINISHED` 同时记录预算总量与审计明细合计,便于 exact Run 对账。
Tool 请求进入 `HarnessToolInterceptor` 后若被进展协议、重复 scope、信息饱和或观察合同门禁拒绝,写入 `TOOL_REQUEST_REJECTED`。事件只含安全 Tool Call ID、Tool name 和稳定错误码,不含业务参数、normalized scope 正文、原始响应或内部异常。实际执行仍只由 `TOOL_INVOCATION` 表达,因此两类数量不能混用。
真实审计发现:`INVALID_PROGRESS_PROTOCOL` 已可完整观察,但当前不会增加 `NO_GAIN` 或触发信息饱和。连续协议拒绝可能产生高 Token 空转,这属于待确认的停止行为改造,不属于 Token 审计本身。
## 14. 验收标准
- [x] Tool 执行状态、信息增益、收集状态和发布状态职责分离。
- [x] RAG `REFERENCE` 不再自动等同于诊断证据。
- [x] `NO_EVIDENCE` 和重复的 `tool_name + normalized_scope` 被 Harness 确定性标记为 `NO_GAIN`;`REFERENCE` 由模型评价。
- [x] 模型继续调用 Tool 前,对上一轮待评价结果给出 `GAINED` 或 `NO_GAIN`。
- [x] 下一次 Tool Call Envelope 能回传上一轮语义评价;Harness 应用评价后才决定是否放行,并在调用业务 Tool 前剥离控制字段。
- [x] 不引入独立 `next_action`;Harness 从 Tool Call 或 Draft 等实际输出推导模型行为。
- [x] `stop-after-consecutive-no-gain` 可配置且默认值为 `2`;连续低收益达到阈值后 Harness 阻止新的 Tool 调用。
- [x] 只对 `tool_name + normalized_scope` 做确定性去重,第一版不承诺自然语言语义去重。
- [x] `INFORMATION_SATURATED` 与 `BUDGET_LIMIT_REACHED` 分开,硬预算不进入 `SATURATED`。
- [x] `FAILED` 不被统计为 `NO_GAIN`,技术故障与证据不足保持区分。
- [x] 模型可以零次 Tool 调用输出 `conclusion=null + limitations.missing_info`,收到 `STOP_REQUIRED` 后必须停止。
- [x] `conclusion=null` 不触发 EvidenceRepair;只有存在结论时才执行完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。
- [x] SemanticGuard 能把无证据确定性结论转换为安全的证据不足结果。
- [x] `DiagnosisReleaseUseCase` 统一处理 Draft、信息饱和和预算终止,并复用 `SafeFallback.observed_facts` 展示 `ProgressSnapshot`。
- [x] 空或非法 Draft 仅在有已验真进展时降级为 `INSUFFICIENT_EVIDENCE`,无安全进展继续 fail closed。
- [x] 不引入 `CONFIRMED / INSUFFICIENT_EVIDENCE / NEED_MORE_INFO` 第二套生命周期状态;后两种语义由 `SafeFallback.type` 表达。
- [x] Tool Schema 只通过模型原生 Tool Calling 通道注册,不拼接进 Prompt。
- [x] 原始复现 Query 在预算耗尽前正常收敛,不再发布通用 `INTERNAL_FAILURE`。
- [x] Trace 能解释每轮信息增益和停止原因,但不保存原始 Tool 载荷或内部 thought。
- [x] 模型调用 Token 可按组件和轮次对账,Run 总账与明细账差异显式可见。
- [x] Tool 请求拒绝与实际 Tool invocation 分开审计,且拒绝事件不泄露 payload。
+11 -1
View File
@@ -1,6 +1,6 @@
# Harness 与质量门禁
**更新日期**:2026-07-23
**更新日期**:2026-07-27
**状态**:当前可运行架构
## 1. Harness 定位
@@ -57,6 +57,16 @@ SemanticGuard 使用隔离的单轮模型调用,只接收原始 query、完整
Trace 写入失败只记录警告,不应改变业务执行结果;`details` 禁止包含 Prompt、Thought、Draft 正文或 raw Tool payload。普通 Trace API 按 `sequence_no, id` 返回 Timeline。
### 6.1 Token 对账
所有 Harness 模型入口统一从 Provider Usage 记录 `MODEL_TOKEN_USAGE`:Router、System Chat、Knowledge Answer、Diagnosis Agent、Evidence Repair 和 SemanticGuard。事件按组件和组件轮次记录 input/output/total Token;Usage 不可用时只记录 unavailable,不估算消耗。
`RUN_FINISHED` 同时记录 RunBudget 总账、模型调用明细合计、不可用 Usage 数量和 `tokens_reconciled`。Diagnosis Agent 的每轮 total Token 还会回填 `AgentStep.token_count`;其他模型组件不创建伪 AgentStep。
### 6.2 Tool 拒绝
`TOOL_INVOCATION` 只表示进入业务 ToolBoundary 的实际执行。协议错误、重复 scope、信息饱和或观察合同拒绝使用独立 `TOOL_REQUEST_REJECTED`,只记录 Tool Call ID、Tool name 和稳定错误码。预算 Tool 计数、实际执行次数和 Harness 拒绝次数是三个不同观察维度。
## 7. Audit 安全
AgentStep 不保存 Prompt、消息正文、模型正文、Tool arguments 或 Thought。ToolInvocation 不保存完整 request、SQL/日志 query、raw response 或 Agent projection。Provider reasoning 仅写入独立 `agent_reasoning_audit`,不进入普通 Trace 或发布结果;无 Provider 内容时必须记录 unavailable,不能伪造。应用日志不得打印这些字段。
+2 -1
View File
@@ -1,6 +1,6 @@
# MVP Issues 索引
**更新日期**:2026-07-23
**更新日期**:2026-07-26
**状态**:按活跃问题、设计笔记、RAG 问题集和已归档问题整理
## 目录约定
@@ -17,6 +17,7 @@
| 名称 | 标题 | 严重程度 | 状态 | 文件 |
|---|---|---|---|---|
| ISS-015 | 诊断运行质量与 Reasoning 审计收敛 | 高 | 待实施 | [active/ISS-015-diagnosis-runtime-quality-and-reasoning-audit.md](active/ISS-015-diagnosis-runtime-quality-and-reasoning-audit.md) |
| ISS-016 | 诊断 Agent 缺少基于信息增益的停止契约 | 高 | 已实施,待归档 | [active/ISS-016-diagnosis-information-gain-stop-contract.md](active/ISS-016-diagnosis-information-gain-stop-contract.md) |
## 设计笔记
@@ -0,0 +1,358 @@
# ISS-016 诊断 Agent 缺少基于信息增益的停止契约
**状态**:已实施;审计发现协议拒绝空转,暂不归档
**严重程度**:高
**发现时间**:2026-07-26
**来源**:知识库无直接答案场景的真实 E2E 与停止策略复盘
**关联**:ISS-015、ISS-012、ISS-002、ISS-009
**架构设计**:[Diagnosis Agent 信息增益与停止控制架构](../../architecture/diagnosis-information-gain-stop-architecture.md)
---
## 1. 背景
当前单体 Diagnosis ReAct Agent 已具备 Tool 调用、Run 预算、EvidenceGuard、SemanticGuard、Trace 和安全发布边界,但停止条件仍主要依赖模型自行结束或 Harness 资源预算耗尽。
这使系统能够限制一次 Run 最多消耗多少资源,却不能稳定判断“继续查询是否还可能增加有效诊断信息”。当知识库只有通用参考资料、日志查询持续为空或用户缺少必要查询条件时,Agent 可能不断改写关键词和扩大尝试,最终由预算被动终止。
该问题不能简化为“Prompt 没写好”或“预算太大”:Prompt、模型终态契约、Tool 客观结果、模型语义评价和 Harness 强制停止之间缺少完整闭环。
## 2. 真实复现
用户 Query:
```text
诊断切换企业失败的问题
```
真实 E2E:
- `sessionId=iss015-enterprise-switch-e2e-20260726000222`
- `runId=054afbb1-0bb3-48d2-8cb8-ddb31d20b2b6`
- SSE:`metadata -> ROUTING -> DIAGNOSIS_RUNNING -> failure(INTERNAL_FAILURE) -> done(FAILED)`
- Run 耗时约 48 秒。
- Agent 已完成多轮模型和 Tool 调用。
- `query_logs` 多次返回 `NO_EVIDENCE`。
- `lookup_knowledge` 的检索层结果主要为 `REFERENCE`,但 Harness 投影仍表现为 `EVIDENCE_FOUND`。
- 最后一次 Tool 调用触发 `BUDGET_EXHAUSTED`,Run 未生成最终 Draft,也未进入安全校验阶段。
- 数据库和 Trace 中存在执行过程,但用户只能看到“当前暂时无法处理该请求,请稍后重试”。
这不是“没有执行过程”,而是 Agent 未能在无新增信息时主动结束,预算终止又被发布链路映射成了技术失败。
### 2.1 实施后验证
同一 Query 的最终 named SSE E2E:
- `sessionId=iss016-final-20260726-a`
- `runId=3ab22ed7-d0ed-45d8-b928-dce5790c0542`
- SSE 发布 `SAFE_FALLBACK`,`type=MISSING_REQUIRED_CONTEXT`,最终 `done.outcome=FALLBACK`。
- 数据库记录 `status=SUCCESS`、`intent=DIAGNOSIS`、`release_outcome=FALLBACK`、`tool_call_count=0`、`total_token_count=2890`,answer 非空。
- Trace 为 `RUN_STARTED -> ROUTING_ATTEMPT -> ROUTING_DECISION -> AGENT_MODEL_STEP -> EVIDENCE_GUARD_INITIAL -> RELEASE_DECISION/FALLBACK -> RUN_FINISHED/FALLBACK`。
本次零 Tool 是合法行为:原 Query 缺少企业、时间范围和错误信息,模型直接报告缺失上下文。另有 focused tests 固定“非法 Draft + 已验真 ProgressSnapshot -> INSUFFICIENT_EVIDENCE”和“非法 Draft + 无安全进展 -> FAILED”边界,避免模型随机返回非 JSON 时再次丢失已完成过程。
### 2.2 Token 与拒绝审计补充验证
2026-07-27 增加按模型组件/轮次的 Token 明细、AgentStep Token 回填、Run 对账摘要和 Tool 请求拒绝 Trace。审计事件不保存 Prompt、模型正文、Tool 参数或原始响应。
缺少上下文 E2E:
- `sessionId=audit-e2e-20260727-001`
- `runId=87f38bea-108b-482a-8b72-d0890cc825f2`
- Router Token `248`,Diagnosis Agent Token `2635`,Run 总 Token `2883`。
- `step_count=1`,对应 `AgentStep.token_count=2635`;Tool 预算计数和实际执行均为 `0`。
- `RUN_FINISHED.tokens_reconciled=true`,Trace 序列 `1..9` 连续。
有界 Tool E2E:
- `sessionId=audit-e2e-20260727-003`
- `runId=3f8f4a0c-3b96-4942-be1e-61cc1431b337`
- 5 次 Tool 实际执行:`lookup_knowledge=4`、`query_logs=1`。
- 9 次 Tool 请求被 `INVALID_PROGRESS_PROTOCOL` 拒绝,均有独立 `TOOL_REQUEST_REJECTED`,没有伪装成 canonical invocation。
- 13 个 Diagnosis Agent 轮次加 1 个 Router 调用,总 Token `68469`;各轮 AgentStep Token 之和加 Router Token与 Run 总量一致。
- `RUN_FINISHED.tokens_reconciled=true`,Trace 序列 `1..52` 连续。
该 E2E 证明审计可观察、可对账、可按组件和轮次重放,也暴露了新的停止缺口:连续协议拒绝当前不产生 `NO_GAIN`,模型可在 Harness 拒绝后继续消耗模型轮次。这个 Run 的 Token 消耗不合理;后续需要单独确认“连续 `INVALID_PROGRESS_PROTOCOL` 是否进入确定性停止”的行为设计,不能仅靠提高或降低预算掩盖。
## 3. 核心问题
### 3.1 成功定义只有“完成诊断”
如果模型的唯一合法输出是完整 `DiagnosisDraft`,那么“证据不足”和“需要用户补充信息”不是一等终态。即使模型已经知道当前信息无法支持根因,它仍可能认为任务尚未完成,并继续调用 Tool。
### 3.2 Tool 客观结果与诊断价值混淆
Tool 可以可靠说明查询是否成功、范围、数量、检索分数、是否为空和是否截断,但不能判断结果是否支持当前诊断假设。
例如:
- 检索到三篇相似文档,只能说明存在候选内容,即当前兼容状态 `EVIDENCE_FOUND`;
- 文档是直接证据、背景材料还是无关内容,需要模型结合当前假设判断;
- 当前范围没有日志,只能说明 scoped `NO_EVIDENCE`,不能说明故障不存在。
把检索层 `REFERENCE` 直接表达为诊断层 `EVIDENCE_FOUND`,会向模型发送“仍在取得进展”的错误信号。
### 3.3 模型没有显式的信息价值判断义务
当前系统主要通过“模型是否继续调用 Tool”推测它是否认为查询有价值。对于无法由代码确定质量的成功非空结果,模型没有被要求给出最小、机器可读的信息增益判断:
```text
information_gain = GAINED | NO_GAIN
```
因此 Harness 无法区分“合理继续收集证据”和“换关键词重复尝试”。
### 3.4 Harness 只有资源预算,没有无进展契约
模型调用次数、Tool 调用次数、Token、字节和超时属于资源边界。它们是最后的安全保护,不应承担正常停止策略。
Harness 当前缺少以下可观察状态:
- 连续 `NO_GAIN` 次数;
- 重复或等价查询;
- 当前收集状态 `COLLECTING / SATURATED`;
- 模型对需要语义评价的结果给出的 `GAINED / NO_GAIN`。
## 4. 顶层设计原则
### 4.1 不要求模型必须找到根因
诊断任务的首要目标是避免无依据结论,而不是每次都输出根因。模型只输出 `DiagnosisDraft`,不产生额外的诊断生命周期状态:
```text
有受证据支持的 conclusion
-> ReleaseOutcome.SUCCESS
conclusion=null,且形成了诚实、有界的结果
-> ReleaseOutcome.FALLBACK
不可恢复的 Tool、模型或基础设施故障
-> ReleaseOutcome.FAILED
```
证据不足和缺少查询条件是 `FALLBACK` 的不同原因,不是与 `SUCCESS / FALLBACK / FAILED / CANCELLED` 平行的第二套状态机。现有 `SafeFallback.type` 负责表达 `INSUFFICIENT_EVIDENCE / MISSING_REQUIRED_CONTEXT` 等发布原因。
### 4.2 Tool 提供事实,模型判断价值
| 组件 | 职责 |
|---|---|
| Tool / Adapter | 返回客观执行状态、查询范围、数量、来源和有界结果 |
| Diagnosis Agent | 判断结果对当前假设的语义价值 |
| Harness | 记录进展、识别重复和不一致、执行确定性停止 |
| EvidenceGuard / SemanticGuard | 校验最终引用真实性和结论支持度 |
| Release | 把正常停止与技术失败发布为不同用户结果 |
不应让 Tool 声称业务证据价值,也不应让 Harness 通过规则替代模型完成诊断推理。
### 4.3 模型主动停止,Harness 确定性兜底
模型应优先根据 Prompt 和显式进展状态主动选择结束;模型未能收敛时,Harness 必须根据可验证规则停止后续 Tool 调用。
只加强 Prompt 不足以形成系统保证;只增加硬预算则会继续产生高成本、低信息量的失败 Run。
## 5. 目标交互契约
### 5.1 已确认的 Tool 侧决策
1. Tool 由服务端通过模型原生 Tool Calling 通道动态注册;Prompt 不写死 Tool 名称或 Schema,也不重复插入 Tool Schema 占位符。
2. Tool 和 Projector 只产生客观结果,不判断业务根因和语义信息增益。
3. `NO_EVIDENCE` 由 Projector 根据结果集合为空确定,不是 LLM 推导。
4. `REFERENCE` 由 RAG 后处理规则根据归一化相似度及查询提示命中情况确定,不是 LLM 推导。
5. `RagResultProjector` 必须兼容读取上游 `relevanceLevel` 或 `relevance_level`,并统一保留为 `relevance_level`。
6. `EVIDENCE_FOUND` 暂时保留,但只表示存在候选内容,不表示存在能够支持诊断结论的证据。
7. 第一版不引入 `new_count`,不建立跨 Tool 通用内容指纹。
8. 不引入显式 `next_action`。模型发起 Tool Call 表示继续,输出 Draft 表示结束。
### 5.2 Tool 结果双视图
Tool 原始结果经过标准化后产生内部 Canonical Tool Result,再分别提供:
| 视图 | 消费者 | 内容 |
|---|---|---|
| Harness Control View | Harness / Canonical Store | 执行状态、数量、规范化 scope、相关度、重复指纹、预算与饱和控制信息 |
| Model Observation | Diagnosis Agent | `tool_call_id`、实际 scope、有界 evidence、`evidence_status`、粗粒度 `relevance_level`、`truncated` |
原始相似度、检索轨迹、重复指纹、低收益计数、阈值、预算和原始 Tool Response 不进入模型上下文。只有 Harness 必须改变模型行为时,才注入有界 `STOP_REQUIRED` 指令和已检查范围摘要。
详细 Tool 架构图和调用流程图见架构文档的“Tool 结果与上下文边界”章节。
### 5.3 信息增益与停止
信息增益只保留:
```text
information_gain = GAINED | NO_GAIN
```
赋值规则:
```text
FAILED
-> 技术故障流程,不产生 information_gain
evidence_status=NO_EVIDENCE
或重复的规范化 tool + scope
-> Harness 直接赋 NO_GAIN
其他成功非空结果,包括 relevance_level=REFERENCE
-> Diagnosis Agent 判断 GAINED / NO_GAIN
```
Harness 使用连续 `NO_GAIN` 维护 `COLLECTING / SATURATED`。连续次数达到配置项 `stop-after-consecutive-no-gain` 后进入 `SATURATED`;默认值为 `2`,只对新 Run 生效,且不进入模型上下文。
硬调用上限不等于信息饱和。Harness 分别记录:
```text
连续 NO_GAIN 达到阈值 -> stop_reason=INFORMATION_SATURATED
达到 Tool 或 Run 硬预算 -> stop_reason=BUDGET_LIMIT_REACHED
```
`SATURATED` 只表示信息饱和;预算限制属于独立的资源保护终止原因。
模型不显式声明行动状态。Harness 从模型实际发起 Tool Call 或输出 Draft 推导其行为。
### 5.4 Tool Call Envelope 协议
模型通过下一次 Tool Call 的通用 Envelope 回传上一轮语义评价:
```json
{
"previous_observation": {
"tool_call_id": "call-123",
"information_gain": "NO_GAIN"
},
"input": {
"query": "下一次查询参数"
}
}
```
协议约束:
- `previous_observation` 只在上一轮存在待模型评价的 Tool Observation 时必填;首次调用以及上一轮已由 Harness 客观赋值时省略。
- Harness 在执行新 Tool 前校验 `tool_call_id`,应用 `information_gain` 并更新 `COLLECTING / SATURATED`。
- 如果应用评价后进入 `SATURATED`,Harness 拒绝本次 Tool 调用并返回 `STOP_REQUIRED`。
- Harness 消费并剥离 `previous_observation`;业务 Tool 只接收 `input` 中原有的业务参数。
- 模型选择直接输出 DiagnosisDraft 时,不存在需要放行的下一次 Tool Call,因此无需额外回传最后一轮评价;Harness 将其记录为模型主动结束。
- 上一个待评价结果未完成评价时,Harness 不接受新的 Tool 调用。
这是模型侧 Tool 调用协议变更,但不改变业务 Tool 的入参协议,也不增加独立 Progress Judge 调用,不把 Harness 内部状态暴露给模型,不把 `information_gain` 混入最终用户可见的 DiagnosisDraft。
### 5.5 已确认的 Prompt 原则
Prompt 使用中文,并保持最小职责,不解释 Projector、计数器、阈值、预算或 Harness 状态机。Tool 名称和 Schema 不写死在正文中,也不拼接进 Prompt,由服务端通过模型原生 Tool Calling 通道动态注册。
Prompt 必须明确:
- 模型不必须得出诊断结论或根因,但必须完成一个诚实、有界的 `DiagnosisDraft`;
- `conclusion=null` 的证据不足结果是合法完成,不是失败;
- Tool 调用次数可以为零;缺少有效查询所需的企业、时间、服务或错误信息时,应直接在 `limitations.missing_info` 中列出缺口;
- 不得为了表现“已经排查”而执行没有明确范围、预期不会产生新诊断信息的 Tool 调用;
- Tool 返回内容事实正确、表述完整或结果非空,不代表它对当前诊断有信息增益;
- 只有新增事实确认、排除或缩小当前诊断假设时,才属于 `GAINED`;
- 通用知识、背景说明、重复内容或不能改变当前判断的内容属于 `NO_GAIN`;
- `NO_GAIN` 后不得通过改写相似关键词或重复相同 scope 继续尝试;
- 如果仍存在范围明确、并可能产生新诊断信息的不同查询,可以继续;否则应主动停止并完成证据不足结果;
- 收到服务端 `STOP_REQUIRED` 后必须停止调用 Tool。
Prompt 不要求模型输出 `next_action`。模型发起 Tool Call 或输出 Draft 即表达其实际选择。
## 6. 实施阶段(已完成)
### 阶段 1:终态与 Prompt 契约
- 保持 `conclusion=null` 作为合法的证据不足 Draft,并明确“有依据地停止”属于成功完成。
- 按 5.5 的最小原则重写中文 Prompt,Tool 定义只通过模型原生 Tool Calling 通道动态注册。
- 允许零次 Tool 调用;查询条件不足时使用现有 `limitations.missing_info`,不新增 `required_context`。
- 明确正确但不能推进当前诊断的 Tool 内容属于 `NO_GAIN`,不得触发等价重试。
- 保持 Tool 预算作为最终安全保护。
### 阶段 2:Tool 状态与模型评价解耦
- 标准化 Canonical Tool Result,并拆分 Harness Control View 与 Model Observation。
- `RagResultProjector` 兼容并保留 `relevance_level`。
- 不把检索命中、候选文档或 `REFERENCE` 自动等同于诊断证据。
- 通过白名单控制进入模型上下文的 Tool 字段。
### 阶段 3:Run 级最小进展状态
- 在 RunContext 中维护已检查 `tool + scope`、连续 `NO_GAIN` 和 `COLLECTING / SATURATED`。
- 只对 `tool_name + normalized_scope` 做确定性参数去重;第一版不做自然语言语义去重。
- 使用配置项 `stop-after-consecutive-no-gain` 控制连续低收益阈值,默认值为 `2`。
- 实现通用 Tool Call Envelope,由下一次 Tool Call 回传上一轮 `information_gain`,并在进入业务 Tool 前剥离控制字段。
- 不把 Prompt、内部 thought 或原始 Tool 载荷混入普通 Trace 和用户结果。
### 阶段 4:Harness 确定性停止
- 拒绝相同 `tool_name + normalized_scope` 的重复查询。
- 达到无进展条件时阻止新的 Tool 调用。
- 分别产生 `INFORMATION_SATURATED / BUDGET_LIMIT_REACHED`,不把硬预算终止伪装成信息饱和。
### 阶段 5:安全发布与 E2E
- 每次 Tool 完成后只保存 Canonical Tool Result;Tool Loop 结束时一次性投影有界 `ProgressSnapshot`。
- `DiagnosisReleaseUseCase` 同时接受正常 Draft 和 `stop_reason + ProgressSnapshot`,统一产生 `SUCCESS / FALLBACK`;`ChatApplicationUseCase` 不再单独决定预算 Fallback 的业务内容。
- 复用现有 `SafeFallback.observed_facts / limitations / next_steps` 和前端渲染协议,并为 `FallbackType` 增加 `INSUFFICIENT_EVIDENCE / MISSING_REQUIRED_CONTEXT`。
- `conclusion=null` 不进入 EvidenceRepair;有 Tool 引用时只验证引用真实性,并从 `ProgressSnapshot` 生成 Fallback。只有存在结论时才执行完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。
- 不依赖预算耗尽后的额外模型调用来生成 Fallback。
- 前端明确区分证据不足、需要补充信息和技术故障。
## 7. 非目标
- 不通过简单增加 Tool/Token 预算解决问题。
- 不把“降低最大 Tool 调用次数”当作信息增益策略。
- 不引入 `new_count`、多级质量评分或显式 `next_action`。
- 不增加独立 Progress Judge 模型调用。
- 不依赖 Prompt 作为唯一控制手段。
- 不让 Tool 或规则代码判断业务根因。
- 不重新引入已由 ISS-014 删除的 Planner/Executor/Verifier/Composer 旧业务 Graph。
- 不在本 Issue 中解决 Reasoning 原文审计治理;该内容仍由 ISS-015 跟踪。
## 8. 验收标准
- [x] 不引入 `CONFIRMED / INSUFFICIENT_EVIDENCE / NEED_MORE_INFO` 第二套诊断生命周期状态;最终状态只使用 `SUCCESS / FALLBACK / FAILED / CANCELLED`。
- [x] Tool 客观命中状态与模型诊断价值评价在契约和 Trace 中明确分离。
- [x] Tool 结果拆分为 Harness Control View 和白名单 Model Observation,Harness 内部计数、阈值和预算不进入模型上下文。
- [x] `RagResultProjector` 保留 `relevance_level`,且模型与 Harness 都能获得各自所需的有界视图。
- [x] `NO_EVIDENCE` 和重复的规范化 `tool + scope` 被 Harness 确定性标记为 `NO_GAIN`;RAG `REFERENCE` 由模型判断 `GAINED / NO_GAIN`。
- [x] 模型继续调用 Tool 前,对上一轮待评价结果返回 `GAINED / NO_GAIN`,不返回 `next_action`。
- [x] 下一次 Tool Call Envelope 能回传上一轮语义评价;Harness 应用评价后才决定是否放行,并在调用业务 Tool 前剥离控制字段。
- [x] Prompt 明确不要求模型必须得出根因,`conclusion=null` 是合法完成结果。
- [x] Prompt 允许零次 Tool 调用;缺少必要查询条件时使用 `limitations.missing_info`,不强制执行无明确目标的查询。
- [x] Prompt 明确正确但不能推进诊断的 Tool 内容属于 `NO_GAIN`,并禁止相似关键词或相同 scope 的等价重试。
- [x] `REFERENCE`、候选文档或非空结果不会自动成为支持根因的证据。
- [x] 相同 `tool_name + normalized_scope` 的重复查询能够被 Harness 识别并阻止;第一版不承诺自然语言语义去重。
- [x] `stop-after-consecutive-no-gain` 可配置且默认值为 `2`;`GAINED` 清零连续计数。
- [x] `INFORMATION_SATURATED` 与 `BUDGET_LIMIT_REACHED` 在 Trace 和 Release 输入中保持区分。
- [x] 连续无新增信息时,Run 在资源预算耗尽前收敛为正常业务结果。
- [x] 模型主动停止和 Harness 强制停止都能输出已检查来源、查询范围、客观结果、信息缺口和下一步。
- [x] 无证据不被解释为故障不存在,通用参考资料不被解释为当前故障事实。
- [x] 真正取得新证据的多轮诊断不会被无进展策略过早终止。
- [x] 原始复现 Query 不再以 `INTERNAL_FAILURE` 结束,也不再通过大量改写查询运行到 `BUDGET_EXHAUSTED`。
- [x] Tool Loop 结束时由 Canonical Tool Result 一次性投影 `ProgressSnapshot`,并映射到现有 `SafeFallback.observed_facts`,无需新增前端协议。
- [x] `DiagnosisReleaseUseCase` 统一处理正常 Draft、信息饱和和预算终止;只有不可形成安全业务结果的技术故障发布为 `FAILED`。
- [x] `conclusion=null` 不触发 EvidenceRepair;有结论时才执行完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。
- [x] 最终 Draft 合同失败时丢弃非法内容;仅在当前 Run 有已验真 ProgressSnapshot 时发布 `INSUFFICIENT_EVIDENCE`,否则保持 `FAILED`。
- [x] 原始复现 Query 在缺少企业、时间和错误信息时返回 `FALLBACK + MISSING_REQUIRED_CONTEXT`,或在有限排查后返回 `FALLBACK + INSUFFICIENT_EVIDENCE`,并展示已经完成的检查。
- [x] exact `sessionId + runId` Trace 能解释每轮继续或停止的原因,但不泄露 Prompt、内部 thought、原始 Tool 载荷或凭据。
- [x] 每个 Provider Usage 可按模型组件和组件轮次审计,Diagnosis Agent Usage 回填 AgentStep,Run 结束显式记录 Token 是否对账。
- [x] Harness 拒绝的 Tool 请求与实际 `TOOL_INVOCATION` 分开记录,拒绝 Trace 不包含 Tool 参数或原始响应。
- [ ] 连续 `INVALID_PROGRESS_PROTOCOL` 拒绝应在硬模型/Token 预算前确定性停止;当前真实 E2E 仍出现 9 次拒绝和 13 个 Agent 轮次。
## 9. 已确认的首版边界
1. 连续 `NO_GAIN` 阈值由 `stop-after-consecutive-no-gain` 配置,默认值为 `2`,后续通过固定 E2E 和评测集校准。
2. 重复检测只比较 `tool_name + normalized_scope`,不做自然语言语义去重。
3. 模型可以在首次 Tool 调用前以 `conclusion=null + limitations.missing_info` 合法结束。
4. Harness 产生内部 `stop_reason`;Release 产生 `SafeFallback.type` 和最终 `release_outcome`。
5. Tool Schema 只通过模型原生 Tool Calling 通道注册,不拼接进 Prompt。
## 10. 相关文件
- `mvp/issues/active/ISS-015-diagnosis-runtime-quality-and-reasoning-audit.md`
- `mvp/issues/archived/ISS-002-executor-unconstrained-lookup.md`
- `mvp/issues/archived/ISS-012-executor-token-budget-and-context-growth.md`
- `mvp/issues/archived/ISS-009-negative-observation-no-evidence-reference.md`
- `mvp/architecture/agent-orchestration.md`
- `mvp/architecture/harness-quality-gates.md`
- `mvp/architecture/diagnosis-information-gain-stop-architecture.md`