Files
SuperBizAgent-java/mvp/engineering/harness/Harness信息增益停止-让无证据诊断正常收敛.md

303 lines
16 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.
# Harness 信息增益停止:让无证据诊断正常收敛
**更新日期**:2026-07-29
**主题**:Information Gain、Progress Tracker、STOP_REQUIRED 与过程型 Fallback
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
**上位设计**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md)
## 1. 预算能限制损失,不能判断何时完成
在知识库只有通用说明、日志持续为空或查询条件不足时,ReAct Agent 容易出现一种看似合理的行为:不断修改关键词、时间范围或查询表达,再调用一次 Tool。
每一次调用单独看都可能合法,但整个 Run 没有获得新信息。旧系统只能等到模型次数、Tool 次数、Token 或 deadline 耗尽,再以 `BUDGET_EXHAUSTED` 或 `INTERNAL_FAILURE` 结束。
这暴露了两个被混淆的问题:
```text
资源预算:这次 Run 最多允许消耗多少?
业务收敛:继续查询是否仍可能推进当前诊断?
```
预算是最后一道资源保护,不能承担正常停止策略。否则“当前证据不足”会被错误表达成“系统执行失败”。
信息增益停止契约的目标是:在硬预算之前识别连续无进展,使 Agent 停止调用 Tool,并把已经完成的有限检查发布为诚实、可验证的业务结果。
## 2. 先划清三方判断权
设计停止机制时最危险的做法,是让某一层承担它无法可靠完成的判断。
| 参与者 | 能可靠判断什么 | 不能判断什么 |
|---|---|---|
| Tool / Projector | 是否执行成功、结果是否为空、返回数量、实际 scope、是否截断 | 内容是否支持当前诊断假设 |
| Diagnosis Agent | 非空内容是否确认、排除或缩小当前假设 | 是否还能绕过预算、重复和饱和门禁 |
| Harness | scope 是否重复、连续 NO_GAIN、协议是否合规、是否允许继续 | 业务根因是什么 |
| Release | 已有进展可以发布成哪种安全结果 | 是否应该重新调用 Tool |
最终原则是:
> Tool 提供客观结果,模型判断语义价值,Harness 拥有最终停止权。
模型可以选择主动结束,但不能通过继续发 Tool Call 绕过 Harness 已经确定的饱和或预算状态。
## 3. 为什么信息增益只有两个值
当前契约只保留:
```text
information_gain = GAINED | NO_GAIN
```
没有 `HIGH / MEDIUM / LOW`,也没有置信分数。停止控制只需要知道结果是否推进了当前诊断;引入更多等级会带来阈值解释、跨 Tool 标定和模型输出不稳定,却不一定改变最终动作。
判定标准是:
| 结果 | Information Gain | 生产者 |
|---|---|---|
| Tool 执行失败 | 不产生 | 进入技术失败流程 |
| `evidence_status=NO_EVIDENCE` | `NO_GAIN` | Harness |
| 相同 `tool_name + normalized_scope` | `NO_GAIN` | Harness,并拒绝重复执行 |
| 成功非空,确认/排除/缩小假设 | `GAINED` | Diagnosis Agent |
| 成功非空,但只是通用知识、重复内容或无关事实 | `NO_GAIN` | Diagnosis Agent |
RAG 的 `REFERENCE` 不能自动映射为 `NO_GAIN`。它只表示检索相关度一般;一段一般相关的资料可能仍排除一个假设,也可能完全无用,需要模型结合诊断上下文判断。
## 4. 为什么模型在下一次 Tool Call 中评价上一轮
模型只有在读到 Tool Observation 后,才能判断它是否有语义增益。但如果要求模型单独输出一条 progress 消息,就必须增加新的协议轮次或 Progress Judge。
当前设计利用模型已经要做的下一步行为:
- 输出 DiagnosisDraft,表示主动结束;
- 发起下一次 Tool Call,表示希望继续。
当模型选择继续时,下一次 Tool Call Envelope 必须携带对上一轮的评价:
```json
{
"previous_observation": {
"tool_call_id": "call-123",
"information_gain": "NO_GAIN"
},
"input": {
"query": "新的业务查询参数"
}
}
```
Interceptor 在调用业务 Tool 之前完成三件事:
1. 校验 `previous_observation.tool_call_id` 是否正好是当前 pending 调用;
2. 应用 `GAINED / NO_GAIN`,更新连续计数和收集状态;
3. 剥离 `previous_observation`,只把 `input` 传给原业务 Tool。
这是 Agent-facing Tool Schema 的协议变化,但 RAG、日志和 MySQL 的业务 request 并没有被控制字段污染。
```mermaid
sequenceDiagram
participant M as Diagnosis Agent
participant I as Tool Interceptor
participant P as Progress Tracker
participant T as Business Tool
M->>I: Tool Call #1 + input
I->>P: no pending observation
I->>T: business input
T-->>I: successful non-empty observation
I->>P: mark call #1 pending evaluation
I-->>M: bounded observation
M->>I: Tool Call #2 + previous_observation(#1, NO_GAIN)
I->>P: validate ID and apply NO_GAIN
alt 仍为 COLLECTING
I->>T: strip control fields, execute input #2
else 达到 SATURATED
I-->>M: STOP_REQUIRED
end
```
如果模型读完 observation 后直接输出 Draft,就不需要额外评价最后一轮。因为它已经通过实际行为表达“停止”,Harness 也不需要为收集一个统计字段强迫模型再调用 Tool。
## 5. ProgressTracker 保存什么,不保存什么
`DiagnosisProgressTracker` 是 `RunContext` 中的线程安全状态句柄,保存:
- 已完成的 `tool_name + normalized_scope`;
- 已完成 Tool Call identity;
- 当前等待模型评价的 `pendingToolCallId`;
- 连续 `NO_GAIN` 次数;
- 连续 progress protocol violation 次数;
- `COLLECTING / SATURATED` 状态;
- stop reason;
- STOP_REQUIRED 是否已经交付。
它不保存 request、raw response、agent result 或模型 thought。完整 Tool 事实仍属于 Canonical Store。Tracker 只保存作出停止决策需要的最小 identity 和计数,避免出现第二份 Tool 真相。
结束时,`DiagnosisProgressProjector` 根据 completed identity 回读 canonical READY records,投影成有界 `ProgressSnapshot`。无法验证、不可读取或格式非法的记录不会被发布,只会形成安全 limitation。
## 6. 重复检测为什么只做参数级
Harness 使用 `ToolScopeNormalizer` 将业务输入转换成稳定 scope,再比较:
```text
tool_name + normalized_scope
```
这可以识别字段顺序、格式差异下的完全相同查询,并在调用 backend 前拒绝重复执行。
首版没有做自然语言语义去重,例如以下两条 query 可能语义相同,但不会被代码证明为同一 scope:
```text
“查询支付超时日志”
“查找支付请求 timeout 记录”
```
原因是语义去重需要 embedding、模型判断或跨 Tool 指纹,会引入新的不确定性和误杀风险。当前边界选择了可以确定性证明的参数重复;语义近似由模型的 `NO_GAIN` 义务约束。
## 7. 收集状态机
连续无增益阈值由配置控制,当前默认值为 2。`GAINED` 会清零连续计数,避免一次早期空查使后续有效取证被过早停止。
```mermaid
stateDiagram-v2
[*] --> COLLECTING
COLLECTING --> COLLECTING: GAINED / consecutiveNoGain=0
COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
COLLECTING --> SATURATED: NO_GAIN / 达到阈值
SATURATED --> STOP_CHANCE: 交付一次 STOP_REQUIRED
STOP_CHANCE --> RELEASE: 模型输出 Draft
STOP_CHANCE --> TERMINATED: 模型再次请求 Tool
```
当某次 READY 结果是 NO_EVIDENCE 时,Harness 可以立即累计 `NO_GAIN`。当成功非空结果需要模型评价时,Tracker 设置 pending ID;上一轮未评价前,新的 Tool Call 不会被执行。
达到 `SATURATED` 后,Harness 给模型一次合法完成机会,而不是在 Tool response 中立刻抛出通用错误。STOP_REQUIRED 只交付一次;模型仍继续请求 Tool 时,`DiagnosisCollectionStoppedException` 将受控停止穿出框架 ReAct loop。
## 8. 协议错误为什么不能计为 NO_GAIN
真实 E2E 曾出现 9 次 `INVALID_PROGRESS_PROTOCOL` Tool 请求拒绝和 13 轮 Diagnosis Agent 模型调用。模型没有正确回传上一轮评价,但这些拒绝也没有增加 NO_GAIN,最终持续空转。
一种看似简单的修复是把协议错误也计为 NO_GAIN。但这会混淆两个事实:
- `NO_GAIN` 表示 Tool 结果没有推进诊断;
- 协议错误表示模型没有遵守控制契约,Tool 根本没有执行。
因此引入独立的协议错误计数和 `PROGRESS_PROTOCOL_VIOLATED`:
1. 第一次错误返回可修正 observation,包含 violation type、缺失字段、期望的上一轮 ID 和允许值;
2. 连续错误达到配置阈值后进入 SATURATED;
3. 交付一次 `STOP_REQUIRED / PROGRESS_PROTOCOL_VIOLATED`;
4. 再次请求 Tool 时受控停止。
协议错误 Trace 使用 `TOOL_REQUEST_REJECTED`,不能记录成 `TOOL_INVOCATION`,因为 backend 从未被调用,Tool 预算和实际执行数也不应被污染。
## 9. 三种停止原因必须分开
| Stop Reason | 含义 | 是否等于技术失败 |
|---|---|---:|
| `INFORMATION_SATURATED` | 连续结果没有推进诊断 | 否 |
| `BUDGET_LIMIT_REACHED` | 达到模型、Tool、Token 或 bytes 预算边界 | 不一定;有安全进展时可发布过程 Fallback |
| `PROGRESS_PROTOCOL_VIOLATED` | 模型连续违反 progress Envelope | 协议失败;有安全进展时仍可保留过程价值 |
信息饱和不能伪装成预算耗尽,否则无法判断阈值是否合理;预算耗尽也不能伪装成信息饱和,因为可能是在持续获得有效证据时资源不足。
这些 stop reason 是 Harness 内部控制语义,不直接作为第二套公开生命周期。最终仍由 Release 映射为 `SUCCESS / FALLBACK / FAILED / CANCELLED`。
## 10. 停止以后如何形成用户结果
停止本身不是答案。系统需要把已经完成的检查转换成可发布内容,又不能依赖预算耗尽后额外调用模型。
`DiagnosisProgressProjector` 从 canonical READY results 生成:
- verified sources;
- observed facts,包括限定范围的空结果;
- 实际查询 scope;
- 投影失败、截断或不可读取形成的 limitations;
- stop reason。
Release 根据现有进展决定:
```mermaid
flowchart TD
S["Agent 主动无结论<br/>或 Harness 受控停止"] --> P["ProgressSnapshot"]
P --> Q{"存在已验真 observed facts?"}
Q -->|"是"| I["INSUFFICIENT_EVIDENCE<br/>展示已检查内容和下一步"]
Q -->|"否"| M{"Draft 声明 missing_info?"}
M -->|"是"| C["MISSING_REQUIRED_CONTEXT"]
M -->|"否"| F["FAILED / fail closed"]
```
`conclusion=null` 是合法 Draft。它允许 Agent 在缺少企业、时间、服务或错误信息时零次调用 Tool,直接报告 `missing_info`,避免为了表现“已经排查”而执行无明确范围的查询。
## 11. 为什么没有引入更多控制字段
### 11.1 不使用 `new_count`
“新记录数量”需要跨 RAG 文档、日志事件和数据库行建立稳定指纹,而且新数据不等于对当前假设有用。它增加了复杂度,却不能替代语义增益判断。
### 11.2 不使用 `next_action`
模型发起 Tool Call已经表示继续,输出 Draft 已经表示结束。再要求 `CONTINUE / STOP` 只会形成一套可能与实际行为冲突的声明状态。
### 11.3 不增加 Progress Judge
独立 Judge 会为每轮 Tool 结果增加模型调用、延迟和失败面。空结果和完全重复 scope 本可由代码判断;其他内容由正在做诊断的 Agent 评价即可。
### 11.4 不向模型公开剩余预算和阈值
模型只需要知道是否必须停止,不需要围绕“还剩几次”规划消耗。阈值、计数和预算属于 Harness Control View,只有 STOP_REQUIRED 等必要指令进入模型上下文。
## 12. Prompt 与硬门禁如何分工
Prompt 仍然需要告诉模型:
- 不必须得出根因;
- `conclusion=null` 是合法完成;
- 缺少必要上下文时可以零 Tool 结束;
- 正确但不能推进假设的内容也是 NO_GAIN;
- 不要通过改写相似关键词重复查询;
- 收到 STOP_REQUIRED 后必须停止。
但 Prompt 只是帮助模型做出正确选择,不构成系统保证。重复 scope、pending evaluation、饱和状态、预算和一次性 STOP_REQUIRED 都由代码门禁执行。
## 13. 真实问题如何改变设计
| 真实现象 | 被证伪的假设 | 设计修正 |
|---|---|---|
| 空日志、通用知识仍不断改写查询 | 模型会自然意识到没有进展 | 明确信息增益义务和 Harness 饱和状态 |
| 最终以 BUDGET_EXHAUSTED 结束 | 硬预算可以充当正常停止 | 预算与信息饱和分离 |
| `REFERENCE` 非空结果持续触发查询 | 非空候选就是有价值证据 | 检索相关度与诊断增益分离 |
| 相同 scope 被反复执行 | Prompt 足以禁止重复 | Harness 参数级去重并在 backend 前拒绝 |
| 9 次协议拒绝仍消耗 13 轮模型 | 错误 observation 会让模型自修复 | 可修正反馈 + 独立协议阈值 + STOP_REQUIRED |
| 非法 Draft 使已完成检查丢失 | 只有合法最终 Draft 才有用户价值 | 已验真 ProgressSnapshot 可形成过程型 Fallback |
| 缺少企业/时间仍被迫调用 Tool | Tool 调用次数大于零才算诊断 | 允许零 Tool、missing context 合法结束 |
## 14. 代价与当前边界
1. 模型侧 Tool Schema 增加了 `previous_observation + input`,这是明确的 Agent-facing 协议变化。
2. 首版只能确定性识别参数相同的重复 scope,不能阻止所有自然语言近义改写。
3. 默认连续 NO_GAIN 阈值 2 是工程起点,需要依靠固定评测集校准;太小会过早停止,太大会增加空转。
4. 最后一轮非空 Tool 结果如果模型直接输出 Draft,Tracker 不强制收集其 information gain;这是减少无意义协议轮次的主动取舍。
5. ProgressSnapshot 依赖 canonical record 仍在 TTL 内且可解析,无法验真的进展不会被发布。
6. 受控预算停止只有在已有安全进展时才能转为 Fallback;没有可验证内容仍然 fail closed。
## 15. 如何验证
| 需要证明 | 代表性测试或 E2E |
|---|---|
| NO_EVIDENCE 自动累计 NO_GAIN,GAINED 清零 | `DiagnosisProgressTrackerTest` |
| 重复 scope 在 backend 前被拒绝 | `HarnessToolInterceptorTest`、`ToolScopeNormalizerTest` |
| pending evaluation 的缺失、乱序和意外回传被拒绝 | Interceptor protocol focused cases |
| 连续协议错误达到阈值并只交付一次 STOP_REQUIRED | Tracker + Interceptor tests |
| 模型主动停止、饱和停止和预算停止都能进入 Release | `DiagnosisAgentUseCaseTest`、`DiagnosisReleaseUseCaseTest` |
| 非法 Draft 只有在存在安全进展时降级 | `DiagnosisChatExecutorTest` |
| Tool 拒绝与实际 Tool 执行分开审计 | exact-run Trace |
| 未知 Query 不再以通用内部错误结束 | ISS-016 named SSE E2E |
其中一条 live E2E 曾准确暴露“协议拒绝不累计 NO_GAIN”的盲区。这说明停止机制不能只验证最终 SSE,还要核对模型轮次、Tool 实际执行数、Tool 拒绝数、Token 和 Timeline 序列。
## 16. 与另外两项设计的关系
信息增益依赖 [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)提供客观 evidence status、scope 和有界 observation;停止后的 ProgressSnapshot 和最终 Fallback 依赖[证据安全链](Harness证据安全链-从引用真实到结论可发布.md)确保只发布当前 Run 可验证的事实。
三者组合后,Harness 才能完整回答:模型看到了什么、为什么继续或停止、最终哪些内容可以发布。