536 lines
24 KiB
Markdown
536 lines
24 KiB
Markdown
# Harness 失败图谱:异常、停止、降级与终态如何对应
|
||
|
||
**更新日期**:2026-07-30
|
||
|
||
**主题**:失败分类、停止决策、安全降级、Run 终态与发布结果
|
||
|
||
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
|
||
|
||
在 Harness 中,“没有找到根因”“Tool 报错”“证据不够”“超时”和“客户端断开”不是同一种失败。如果把它们都压成一个 `FAILED`,用户无法知道系统是否完成了有效检查,开发者也无法判断应该重试、换 Tool、发布 Fallback,还是立即终止。
|
||
|
||
这篇文章不从状态枚举开始,而是用三个问题建立一张失败图谱:
|
||
|
||
1. 问题发生后,这次 Run 还能继续吗?
|
||
2. 已经产生的内容中,有没有可验证的安全事实?
|
||
3. 最终能公开正常报告、安全说明,还是只能失败?
|
||
|
||
第一次阅读只看第 1、2、8、10 和 13 节即可。先掌握判断方法和几个典型场景,不需要记住全部状态。
|
||
|
||
## 1. 先记住:Harness 的失败处理不是“捕获异常”
|
||
|
||
假设一次支付超时诊断中连续发生这些事情:
|
||
|
||
```text
|
||
日志 Tool 查询成功,但指定时间段没有记录
|
||
RAG Tool 第一次调用网络超时
|
||
Agent 换了一个查询范围,找到一条可验证的配置事实
|
||
Agent 据此声称“数据库连接池耗尽”
|
||
EvidenceGuard 发现报告引用的证据并不支持这个结论
|
||
```
|
||
|
||
如果只看局部,既出现了空结果、技术异常、有效事实,又出现了发布门禁失败。系统不能把其中任何一个事件直接等同于整次 Run 的终态。
|
||
|
||
Harness 真正要做的是分层决策:
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
X["某个问题发生"] --> Q1{"Run 是否仍可继续?"}
|
||
Q1 -->|"可以"| C["继续诊断或改查其他 Tool"]
|
||
Q1 -->|"不可以"| Q2{"是否已有可验证的安全进展?"}
|
||
C --> Q3{"最终报告能否通过发布门禁?"}
|
||
Q3 -->|"可以"| S["ReleaseOutcome.SUCCESS<br/>发布诊断报告"]
|
||
Q3 -->|"不可以,但有安全说明"| F["ReleaseOutcome.FALLBACK<br/>发布 SafeFallback"]
|
||
Q3 -->|"没有任何安全内容"| E["ReleaseOutcome.FAILED<br/>发布 failure"]
|
||
Q2 -->|"有"| F
|
||
Q2 -->|"无"| E
|
||
```
|
||
|
||
这张图背后的核心决策是:
|
||
|
||
> 局部事件决定下一步动作,只有 Run Lifecycle 和 Release 才能决定整次请求如何结束、什么可以公开。
|
||
|
||
因此,失败处理不是一个全局 `try/catch`,而是执行控制、安全事实和发布策略共同完成的结果。
|
||
|
||
## 2. 第一类:没有答案,但系统没有坏
|
||
|
||
最容易被误判为失败的场景,是 Tool 正常执行却没有返回证据。
|
||
|
||
当前 Tool 结果使用两个正交状态:
|
||
|
||
```text
|
||
InvocationStatus:PROJECTING / READY / ERROR
|
||
EvidenceStatus:EVIDENCE_FOUND / NO_EVIDENCE / ERROR
|
||
```
|
||
|
||
它们回答不同问题:
|
||
|
||
| 组合 | 含义 | 是否是技术失败 |
|
||
|---|---|---:|
|
||
| `READY + EVIDENCE_FOUND` | Tool 成功,当前 scope 有候选证据 | 否 |
|
||
| `READY + NO_EVIDENCE` | Tool 成功,当前 scope 没有候选证据 | 否 |
|
||
| `ERROR + ERROR` | Tool 执行、投影或 canonical 保存失败 | 是 |
|
||
|
||
`READY + NO_EVIDENCE` 只能证明“本次查询范围为空”,不能证明“整个系统不存在该问题”。例如查询 10:00 至 10:10 的支付日志为空,不代表全天没有超时,也不代表日志系统之外没有故障。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
T["执行 Tool"] --> I{"InvocationStatus"}
|
||
I -->|"ERROR"| X["技术失败路径"]
|
||
I -->|"READY"| E{"EvidenceStatus"}
|
||
E -->|"EVIDENCE_FOUND"| G["交给 Agent 判断信息增益"]
|
||
E -->|"NO_EVIDENCE"| N["记录限定 scope 的空事实<br/>累计 NO_GAIN"]
|
||
N --> Q{"还值得继续查询吗?"}
|
||
Q -->|"值得"| R["调整假设或 scope"]
|
||
Q -->|"连续无增益"| P["CollectionState.SATURATED"]
|
||
```
|
||
|
||
类似的正常无结果还包括:
|
||
|
||
- 用户没有提供企业、服务、时间范围等必要上下文;
|
||
- 多次查询都合法,但连续没有推进诊断;
|
||
- Agent 遵循协议主动输出 `conclusion=null`;
|
||
- 已完成有限检查,但证据只够描述现象,不够支持根因。
|
||
|
||
这些场景不应该伪装成系统异常。只要请求被正常处理并形成安全说明,就可以是:
|
||
|
||
```text
|
||
RunState.SUCCESS + ReleaseOutcome.FALLBACK
|
||
```
|
||
|
||
### 为什么不把 NO_EVIDENCE 设计成异常
|
||
|
||
如果空结果抛异常,系统会产生三个问题:
|
||
|
||
- Agent 无法区分“查询为空”和“查询服务不可用”;
|
||
- 监控会把正常业务分布统计成技术故障;
|
||
- Release 无法向用户解释已经检查的范围。
|
||
|
||
因此放弃了“Tool 无数据即失败”的简化方案,代价是状态维度增加,但换来了可解释的控制语义。
|
||
|
||
## 3. 第二类:技术异常可能可以重试
|
||
|
||
并非所有技术异常都应立即结束 Run。对于瞬时、明确、可能恢复的 Provider 故障,Harness 可以执行有限的显式重试。
|
||
|
||
当前重试策略遵循两个原则:
|
||
|
||
```text
|
||
只有稳定分类为可恢复的技术失败才重试
|
||
所有 attempt 都由 Harness 计数、计费并记录
|
||
```
|
||
|
||
典型策略如下:
|
||
|
||
| 组件 | 可重试场景 | 最大 attempt | 不重试场景 |
|
||
|---|---|---:|---|
|
||
| Intent Router | timeout、transport、非法输出 | 2 | 已得到合法路由结果 |
|
||
| SemanticGuard | timeout、transport、parse/schema failure | 2 | `UNSUPPORTED` |
|
||
| Diagnosis Agent | 不执行隐藏 retry | 1 个受控业务循环 | 正常下一轮 ReAct 不是 retry |
|
||
| Tool | 不执行隐藏 retry | 每个 Tool Call 一次 | Agent 可以基于结果选择其他 Tool |
|
||
| EvidenceRepair | 一次显式修复机会 | 1 | 不是无限修复循环 |
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant H as Harness
|
||
participant P as Router / Semantic Provider
|
||
participant B as Budget & Trace
|
||
|
||
H->>B: reserve attempt #1
|
||
H->>P: request #1
|
||
P-->>H: timeout / transport / invalid output
|
||
H->>B: record classified failure
|
||
H->>B: reserve attempt #2
|
||
H->>P: request #2
|
||
alt 得到合法结果
|
||
P-->>H: valid result
|
||
H->>B: record success
|
||
else 再次技术失败
|
||
P-->>H: unavailable
|
||
H->>B: record final failure
|
||
H-->>H: 进入 Fallback 或 FAILED 决策
|
||
end
|
||
```
|
||
|
||
### 为什么不使用 SDK 的默认隐藏重试
|
||
|
||
隐藏重试会让系统无法准确回答:
|
||
|
||
- 这次请求实际调用了 Provider 几次;
|
||
- Token、deadline 和 attempt 消耗在哪里;
|
||
- Trace 中的一次调用为什么延迟异常;
|
||
- 客户端取消后是否还在后台继续重试。
|
||
|
||
所以重试必须是 Harness 的控制行为,而不是各组件自行决定。代价是需要维护失败分类和 attempt 协议,但预算与审计仍然闭合。
|
||
|
||
### `UNSUPPORTED` 为什么不重试
|
||
|
||
SemanticGuard 返回 `UNSUPPORTED`,表示它成功完成了判断,只是证据不支持报告。这是业务结果,不是技术不可用。重试同一份报告只是在要求模型重新投票,既不能创造新证据,也会削弱门禁的一致性。
|
||
|
||
## 4. 第三类:一个 Tool 失败,不等于整个 Run 失败
|
||
|
||
Diagnosis Agent 通常拥有多个只读 Tool。某个 Tool 出现 `ERROR` 后,Agent 可能仍然可以:
|
||
|
||
- 改查另一个数据源;
|
||
- 缩小或调整查询范围;
|
||
- 使用已经取得的其他 canonical 事实;
|
||
- 明确说明某个数据源不可用,并结束为 Fallback。
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
E["单个 Tool ERROR"] --> A{"Run 仍 active 且预算允许?"}
|
||
A -->|"否"| T["进入终止决策"]
|
||
A -->|"是"| O{"是否还有合法替代动作?"}
|
||
O -->|"换 Tool / 换 scope"| C["Agent 继续诊断"]
|
||
O -->|"没有"| P{"已有安全进展?"}
|
||
P -->|"有"| F["ReleaseOutcome.FALLBACK"]
|
||
P -->|"无"| X["ReleaseOutcome.FAILED"]
|
||
C --> D{"最终是否形成可发布报告?"}
|
||
D -->|"是"| S["ReleaseOutcome.SUCCESS"]
|
||
D -->|"否"| P
|
||
```
|
||
|
||
这里不能建立一条简单映射:
|
||
|
||
```text
|
||
Tool ERROR -> RunState.FAILED
|
||
```
|
||
|
||
真正必须终止的情况,是错误破坏了 Harness 的安全前提,或者已经没有合法的恢复路径。例如:
|
||
|
||
- canonical store 无法保存或回读 Tool 真相;
|
||
- Tool raw 或 Agent result 超过硬 bytes 上限;
|
||
- 路由经过允许的 attempts 后仍不可用;
|
||
- Agent 输出非法 Draft,且不存在可验证的 ProgressSnapshot;
|
||
- Harness 自身出现无法分类、无法形成安全响应的内部错误。
|
||
|
||
### 为什么不让 Tool 自己决定 Run 失败
|
||
|
||
Tool 只知道一次 invocation 是否成功,不知道整个诊断还拥有多少预算、其他 Tool 是否可用、是否已有安全事实,也不知道最终发布策略。让 Tool 抛出全局终止异常,会把局部职责扩大成 Run 决策权。
|
||
|
||
当前设计的代价是 Agent 和 Application 必须处理结构化 Tool 错误,而不是依赖异常一路冒泡;收益是局部故障不会无条件摧毁整次诊断。
|
||
|
||
## 5. 第四类:执行成功,发布仍可能被拒绝
|
||
|
||
Agent 完成 Draft 并不表示用户一定能看到这份报告。发布前还要经过两类门禁:
|
||
|
||
```text
|
||
EvidenceGuard:引用是否真实、是否属于当前 Run、是否来自 READY invocation
|
||
SemanticGuard:这些真实证据是否支持用户可见结论
|
||
```
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
D["DiagnosisDraft"] --> E{"EvidenceGuard"}
|
||
E -->|"通过"| S{"SemanticGuard"}
|
||
E -->|"失败"| EF["EVIDENCE_VALIDATION_FAILED<br/>SafeFallback"]
|
||
S -->|"SUPPORTED"| R["发布 Diagnosis Report"]
|
||
S -->|"UNSUPPORTED"| SU["SEMANTIC_UNSUPPORTED<br/>SafeFallback"]
|
||
S -->|"技术不可用"| SA["SEMANTIC_UNAVAILABLE<br/>SafeFallback"]
|
||
```
|
||
|
||
对应的典型结果是:
|
||
|
||
| 发布门禁结果 | RunState | ReleaseOutcome | FallbackType |
|
||
|---|---|---|---|
|
||
| 两层门禁通过 | `SUCCESS` | `SUCCESS` | 无 |
|
||
| EvidenceGuard 最终失败 | `SUCCESS` | `FALLBACK` | `EVIDENCE_VALIDATION_FAILED` |
|
||
| SemanticGuard 判断不支持 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNSUPPORTED` |
|
||
| SemanticGuard 技术不可用 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNAVAILABLE` |
|
||
|
||
这里最反直觉的一点是:Guard 拒绝发布原报告,通常仍然是 `RunState.SUCCESS`。因为 Harness 成功执行了安全策略,并向用户发布了诚实的降级结果;失败的是“原报告获得发布资格”,不是“控制系统无法完成请求”。
|
||
|
||
### 为什么 Guard 不能直接改写结论
|
||
|
||
项目放弃了让 Verifier 或 SemanticGuard 顺手生成“更正确答案”的方案。Guard 没有业务 Tool、完整 ReAct 上下文和重新取证能力,改写报告会让审查者同时成为证据生产者。
|
||
|
||
因此 Guard 只能批准或拒绝,Release 只能发布原报告或确定性 SafeFallback。代价是部分“看起来只差一点”的报告也会降级,但发布责任保持清晰。
|
||
|
||
## 6. 第五类:停止收集,不等于 Run 已终止
|
||
|
||
当连续查询没有信息增益,或 Agent 持续违反 progress 协议时,Harness 会把证据收集状态切换为:
|
||
|
||
```text
|
||
CollectionState.SATURATED
|
||
```
|
||
|
||
可能的停止原因包括:
|
||
|
||
```text
|
||
INFORMATION_SATURATED
|
||
PROGRESS_PROTOCOL_VIOLATED
|
||
BUDGET_LIMIT_REACHED
|
||
```
|
||
|
||
`SATURATED` 只表示不再允许新的 evidence Tool,不是 Run 终态。Harness 仍然要给 Agent 一次机会输出 Draft,或者根据 canonical records 投影 `ProgressSnapshot`。
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> COLLECTING
|
||
COLLECTING --> COLLECTING: GAINED
|
||
COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
|
||
COLLECTING --> SATURATED: 连续 NO_GAIN 或协议违规
|
||
SATURATED --> DRAFTING: 交付一次 STOP_REQUIRED
|
||
DRAFTING --> RELEASE: Agent 正常结束
|
||
DRAFTING --> TERMINATED: Agent 再次请求 Tool
|
||
RELEASE --> [*]
|
||
TERMINATED --> [*]
|
||
```
|
||
|
||
这项区分解决了一个早期问题:系统过去只能依靠预算把空转“撞停”,最终把证据不足表达成 `BUDGET_EXHAUSTED` 或内部失败。引入 Collection 状态后,业务收敛可以发生在硬资源终止之前。
|
||
|
||
## 7. 第六类:超时、预算和取消是三种不同终止
|
||
|
||
Run 的终态只有:
|
||
|
||
```text
|
||
RUNNING
|
||
SUCCESS
|
||
FAILED
|
||
CANCELLED
|
||
TIMED_OUT
|
||
BUDGET_EXHAUSTED
|
||
```
|
||
|
||
`RunLifecycle` 使用 first-terminal-wins:第一个成功设置的终态不可被后来返回的 Provider、Tool 或异步回调覆盖。
|
||
|
||
### Deadline 到期
|
||
|
||
deadline 回答“这次 Run 还能否继续占用时间”。到期后终态是 `TIMED_OUT`。如果没有可发布的安全内容,Release 为 `FAILED`;当前实现不会为了美化结果在超时后继续调用模型生成说明。
|
||
|
||
### Budget 耗尽
|
||
|
||
预算可能限制模型 attempt、Tool 调用、Token 或 bytes。终态是 `BUDGET_EXHAUSTED`,但发布结果取决于是否已有安全进展:
|
||
|
||
```text
|
||
有 ProgressSnapshot -> ReleaseOutcome.FALLBACK
|
||
没有安全进展 -> ReleaseOutcome.FAILED
|
||
```
|
||
|
||
这说明 `RunState` 和 `ReleaseOutcome` 不能一一对应。
|
||
|
||
### 客户端断开
|
||
|
||
客户端断开意味着公开通道已经不存在,Run 转为 `CANCELLED`。取消是协作式的:已经发出的同步 Provider 调用可能无法被物理中止,但返回后必须经过 active check 和 SSE state 检查,迟到结果不能再发布。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as Client
|
||
participant S as ChatSseSession
|
||
participant H as Harness Core
|
||
participant P as Provider / Tool
|
||
|
||
H->>P: 已发出的同步调用
|
||
C--xS: 断开连接
|
||
S->>H: cancel Run
|
||
H->>H: first terminal = CANCELLED
|
||
P-->>H: 迟到结果
|
||
H->>H: checkActive 拒绝继续
|
||
H-->>S: 不发送 content / done
|
||
S->>S: DISCONNECTED 拒绝迟到事件
|
||
```
|
||
|
||
客户端断开时不可靠地发送 `done(CANCELLED)`,因为连接已经不可用。取消事实应由服务端 Trace 和 Run 状态观察,而不是假设客户端还能收到终止事件。
|
||
|
||
## 8. “安全进展”决定 FALLBACK 还是 FAILED
|
||
|
||
停止之后,系统不能把 Agent 的未验证草稿直接当作降级内容。所谓安全进展,必须来自当前 Run 的 canonical READY records,并经过有界投影。
|
||
|
||
`ProgressSnapshot` 可以包含:
|
||
|
||
- 已实际执行的查询 scope;
|
||
- 可验证的 observed facts;
|
||
- 限定范围内的 `NO_EVIDENCE`;
|
||
- 数据源不可用、结果截断或投影失败等 limitations;
|
||
- 推荐补充的上下文或下一步检查。
|
||
|
||
它不能包含未经支持的根因结论。
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
T["Run 无法继续或 Agent 无结论"] --> P["从 canonical records<br/>构建 ProgressSnapshot"]
|
||
P --> V{"存在可验证的 observed facts?"}
|
||
V -->|"有"| F["FALLBACK<br/>说明已检查内容、限制和下一步"]
|
||
V -->|"没有"| M{"是否明确缺少必要上下文?"}
|
||
M -->|"是"| C["FALLBACK<br/>MISSING_REQUIRED_CONTEXT"]
|
||
M -->|"否"| E["FAILED<br/>不公开未验证内容"]
|
||
```
|
||
|
||
因此,下面两次预算耗尽可以有不同结果:
|
||
|
||
| 场景 | RunState | ReleaseOutcome | 用户看到什么 |
|
||
|---|---|---|---|
|
||
| 已验证支付服务在目标时段无日志,随后预算耗尽 | `BUDGET_EXHAUSTED` | `FALLBACK` | 已检查范围、空结果限制和下一步 |
|
||
| 第一次模型调用前预算检查失败,没有任何安全事实 | `BUDGET_EXHAUSTED` | `FAILED` | failure |
|
||
|
||
### 为什么不为所有失败生成一段“友好回答”
|
||
|
||
如果失败后再调用模型组织解释,会继续消耗已经耗尽的预算,也可能根据异常文本编造业务结论。确定性模板虽然表达能力有限,但不会把未知包装成答案。
|
||
|
||
这项设计选择了 fail closed:有安全事实才降级,没有就失败。代价是用户体验不总是“自然语言很完整”,但不会为了完整感牺牲可信度。
|
||
|
||
## 9. 同一个结果,要从四个视图理解
|
||
|
||
Harness 没有一条能容纳全部语义的总状态机。一次请求结束后,至少要区分四个观察面:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
R["RunState<br/>执行为什么停止"] --> X["一次请求"]
|
||
O["ReleaseOutcome<br/>最终公开什么"] --> X
|
||
D["diagnosis_run.status<br/>请求是否被安全处理"] --> X
|
||
S["SSE 事件<br/>客户端实际收到什么"] --> X
|
||
```
|
||
|
||
### RunState:执行为什么停止
|
||
|
||
它由 Harness Core 拥有,表达正常完成、内部失败、取消、超时或预算耗尽。
|
||
|
||
### ReleaseOutcome:最终公开什么
|
||
|
||
```text
|
||
SUCCESS / FALLBACK / FAILED / CANCELLED
|
||
```
|
||
|
||
它由 Release 决定,表达正常诊断报告、安全降级、失败或取消。
|
||
|
||
### 数据库 status:请求是否被安全处理
|
||
|
||
`JpaChatRunStore` 当前映射为:
|
||
|
||
| ReleaseOutcome | diagnosis_run.status |
|
||
|---|---|
|
||
| `SUCCESS` | `SUCCESS` |
|
||
| `FALLBACK` | `SUCCESS` |
|
||
| `FAILED` | `FAILED` |
|
||
| `CANCELLED` | `CANCELLED` |
|
||
|
||
因此:
|
||
|
||
```text
|
||
status=SUCCESS + release_outcome=FALLBACK
|
||
```
|
||
|
||
表示请求被正常、安全地处理并发布了降级内容,不表示系统找到了根因。
|
||
|
||
只有 `DIAGNOSIS + ReleaseOutcome.SUCCESS + published_result` 才能进入下一轮 `PreviousTurn`。Fallback 不进入后续上下文,避免把“证据不足”当作已确认结论继续传播。
|
||
|
||
### SSE:客户端实际收到什么
|
||
|
||
公开事件顺序是:
|
||
|
||
```text
|
||
metadata -> status* -> content | failure -> done
|
||
```
|
||
|
||
- `content` 最多一次;
|
||
- `content` 与 `failure` 互斥;
|
||
- `TERMINAL / DISCONNECTED` 后拒绝迟到结果;
|
||
- 当前 `done` 使用 `ReleaseOutcome`,不是另一套遗留终态。
|
||
|
||
四个视图回答不同问题,排障时不能拿数据库 `SUCCESS` 推断用户收到了一份成功诊断报告。
|
||
|
||
## 10. 典型场景总表
|
||
|
||
| 场景 | RunState | ReleaseOutcome | 公开内容或 FallbackType |
|
||
|---|---|---|---|
|
||
| EvidenceGuard、SemanticGuard 全部通过 | `SUCCESS` | `SUCCESS` | Diagnosis report |
|
||
| 缺少企业、服务、时间等必要上下文 | `SUCCESS` | `FALLBACK` | `MISSING_REQUIRED_CONTEXT` |
|
||
| 有限检查后仍然证据不足 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
|
||
| 信息饱和且有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
|
||
| Progress 协议停止且有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
|
||
| EvidenceGuard 最终失败 | `SUCCESS` | `FALLBACK` | `EVIDENCE_VALIDATION_FAILED` |
|
||
| SemanticGuard 判断不支持 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNSUPPORTED` |
|
||
| SemanticGuard attempts 后不可用 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNAVAILABLE` |
|
||
| 预算耗尽但有安全进展 | `BUDGET_EXHAUSTED` | `FALLBACK` | 当前发布为 `INSUFFICIENT_EVIDENCE` |
|
||
| 超时且没有安全内容 | `TIMED_OUT` | `FAILED` | failure |
|
||
| 预算耗尽且没有安全进展 | `BUDGET_EXHAUSTED` | `FAILED` | failure |
|
||
| 不可恢复内部失败 | `FAILED` | `FAILED` | failure |
|
||
| 客户端断开 | `CANCELLED` | `CANCELLED` | 不可靠发送 `done` |
|
||
|
||
这张表不是一个双向转换规则。例如看到 `ReleaseOutcome.FALLBACK`,不能单独推断 Run 是正常完成还是预算耗尽;仍要结合 Run termination 和 Trace。
|
||
|
||
## 11. 排障时按什么顺序看
|
||
|
||
面对“用户为什么没有看到诊断结论”,不要先搜索所有异常日志。按发布结果向前追踪更容易定位:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
U["用户实际收到的 SSE"] --> O["release_outcome<br/>fallback_type"]
|
||
O --> R["Run termination<br/>deadline / budget / cancel"]
|
||
O --> G["Evidence / Semantic<br/>Release Trace"]
|
||
R --> T["Tool Invocation<br/>Progress / canonical record"]
|
||
G --> T
|
||
```
|
||
|
||
推荐顺序:
|
||
|
||
1. 看 SSE 是 `content`、`failure`,还是连接提前断开。
|
||
2. 看 `release_outcome` 和 `FallbackType`,确认是正常报告、降级还是失败。
|
||
3. 看 Run termination,区分正常完成、超时、预算耗尽和取消。
|
||
4. 看 Release、EvidenceGuard、SemanticGuard Trace,确认原报告为什么不能发布。
|
||
5. 看 Tool 的 `InvocationStatus + EvidenceStatus`,不要只看一个 `success`。
|
||
6. 最后查看 canonical record、scope、bytes、budget usage 和 progress stop reason。
|
||
|
||
这个顺序从“用户看到什么”追到“内部为什么这样决定”,比从第一条异常开始阅读整条 Timeline 更容易建立因果关系。
|
||
|
||
## 12. 这套设计放弃了哪些更简单的方案
|
||
|
||
### 一个 `status` 表示一切
|
||
|
||
放弃原因:`SUCCESS` 无法同时表达 Tool 执行、Run 终止、报告发布和数据库处理结果。单状态简单,但必然丢失原因。
|
||
|
||
### 任意异常直接终止整个 Run
|
||
|
||
放弃原因:局部 Tool 故障仍可能有替代路径;空结果也不是异常。这样做会降低系统可用性并掩盖有限检查的价值。
|
||
|
||
### 所有错误都自动重试
|
||
|
||
放弃原因:业务拒绝、容量上限、非法输入和证据不支持不会因为重试自动恢复;隐藏重试还会破坏预算和审计。
|
||
|
||
### Agent 自己决定何时降级
|
||
|
||
放弃原因:Agent 无法读取 canonical truth,也不能验证自己的引用。让它同时生成结论和批准发布,会形成自证循环。
|
||
|
||
### 失败后继续调用模型润色 Fallback
|
||
|
||
放弃原因:终止后继续消耗资源,且无法保证新文本只包含已验证事实。当前使用确定性 Release Policy 和安全模板。
|
||
|
||
### 取消时强制等待所有底层调用结束
|
||
|
||
放弃原因:同步 Provider 未必支持真正中断,等待会延长资源占用。当前采用协作式取消、active check、first-terminal-wins 和 SSE 状态拒绝迟到发布。
|
||
|
||
## 13. 面试时如何讲这套失败设计
|
||
|
||
可以用下面这段话概括:
|
||
|
||
> 我们没有把 Harness 的失败处理设计成一个全局异常捕获器,因为 Agent 系统里“没有证据、单个 Tool 失败、证据不支持结论、预算耗尽和客户端取消”代表完全不同的控制语义。系统先判断 Run 是否还能继续,再从当前 Run 的 canonical records 判断是否已有可验证进展,最后由唯一的 Release Policy 决定发布正常报告、SafeFallback 还是 failure。Tool 使用 InvocationStatus 和 EvidenceStatus 区分技术失败与正常空结果;RunState 解释执行为什么停止,ReleaseOutcome 解释用户最终看到什么,两者不做一一映射。可恢复的 Provider 故障只允许 Harness 做有限、可审计的显式重试,Guard 拒绝发布通常降级而不是把整次请求标成失败,超时、预算和取消则通过 first-terminal-wins 与 SSE 状态阻止迟到结果。这样做的目标不是让每次诊断都成功,而是保证任何结束方式都可解释、可审计,并且不会把未经验证的内容发布给用户。
|
||
|
||
这段回答要表达的不是“系统定义了很多状态”,而是:**每个状态都对应不同的决策权和失败责任。**
|
||
|
||
## 14. 当前边界与代价
|
||
|
||
1. 多套正交状态提高了准确性,也增加了学习成本,必须通过 Context、映射表和 Trace 查询规范维持统一口径。
|
||
2. 内存中的 `RunTermination.state/reason` 当前没有完整独立持久化;精确判断 `TIMED_OUT / BUDGET_EXHAUSTED` 仍需结合 Trace、异常路径和预算记录。
|
||
3. 协作式取消能阻止迟到发布,但不保证立即终止已经发出的同步 Provider 计算。
|
||
4. Tool ERROR 后是否继续由 Agent 在 Harness 门禁内选择,因此模型可能做出次优恢复动作;硬预算和停止协议负责限制损失。
|
||
5. 有安全进展才能 Fallback 的策略会让部分请求直接失败,但这是避免发布未验证内容的主动取舍。
|
||
6. `FallbackType` 枚举仍保留 `BUDGET_EXHAUSTED`,但当前预算受控停止实际统一发布 `INSUFFICIENT_EVIDENCE`;这是已知命名债务。未来若要区分资源不足与业务证据不足,需要先明确对外语义和兼容策略,不能只替换当前映射。
|
||
|
||
## 15. 事实来源与延伸阅读
|
||
|
||
本文对应的主要实现边界:
|
||
|
||
- `ChatApplicationUseCase`:Application 生命周期、Router、Run 终止和 Release 协作;
|
||
- `DiagnosisHarnessCore`:Run active check、预算、终态与控制边界;
|
||
- `DiagnosisAgentUseCase`:Tool loop、受控停止和 Draft 形成;
|
||
- `DiagnosisReleaseUseCase`:EvidenceGuard、SemanticGuard 与唯一发布策略;
|
||
- `JpaChatRunStore`:ReleaseOutcome 到数据库 status 的映射;
|
||
- `ChatSseSession`:SSE 单内容、终态和断开规则。
|
||
|
||
继续阅读:
|
||
|
||
- [Harness 入门](README.md)
|
||
- [支付超时诊断案例](案例-从一次支付超时诊断看Harness如何控制Agent.md)
|
||
- [Harness 生命周期与状态](Harness生命周期与状态.md)
|
||
- [信息增益停止](Harness信息增益停止-让无证据诊断正常收敛.md)
|
||
- [证据安全链](Harness证据安全链-从引用真实到结论可发布.md)
|
||
- [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)
|