Files
SuperBizAgent-java/mvp/engineering/harness/Harness异常处理-Loop内外与状态流.md

17 KiB
Raw Permalink Blame History

Harness 异常处理:Loop 内 vs Loop 外

日期:2026-07-30
范围:诊断路径(intent=DIAGNOSIS)上 ReactAgent ReAct 循环内外的异常、受控停止、降级与终态
读者:已读 E2E 全流程 / 运行控制,需要把「失败时系统到底怎么走」串成一张图

关联文档:


0. 先记住一张总图

flowchart TB
    subgraph app["Application 编排"]
        UC[ChatApplicationUseCase]
        EX[DiagnosisChatExecutor]
    end

    subgraph agent["Agent 用例"]
        UC2[DiagnosisAgentUseCase]
        CE[controlledExecution]
        RF[recoverInvalidDraft]
    end

    subgraph loop["ReactAgent ReAct Loop"]
        MI[HarnessModelInterceptor]
        LLM[ChatModel]
        TI[HarnessToolInterceptor]
        TB[ToolBoundary]
    end

    subgraph release["Release"]
        REL[DiagnosisReleaseUseCase]
        FB[SafeFallback / FALLBACK]
        OK[SUCCESS 报告]
    end

    UC --> EX
    EX --> UC2
    UC2 -->|agent.call| loop
    MI --> LLM
    LLM -->|tool_call| TI
    TI --> TB
    TB -->|observation| LLM

    loop -->|受控异常穿出| CE
    CE -->|stopped draft=null| REL
    loop -->|正常返回坏 draft| UC2
    UC2 -->|DiagnosisAgentOutputException| RF
    RF -->|有 observedFacts| REL
    REL --> FB
    REL --> OK
    CE -->|取消/超时再抛| UC
    RF -->|无 facts 再抛| UC

一句话:

区域 异常怎么处理
Loop 内 多数变成 tool/model 侧 observation 或拦截;少数 受控异常穿出 loop
Loop 边界 controlledExecution:受控停止 → stopped;不可控 → 包装/再抛
Loop 外(Executor) recoverInvalidDraft:坏 draft + 有事实 → FALLBACK;否则失败
Release 有 draft 走 Guard;无 draft / 非法 draft 走专用降级
Application 未消化异常 → ChatFailureCode + Run 终态落库

1. 状态有三层,不要混

异常处理会同时碰到三套状态,职责不同:

flowchart LR
    subgraph core["Core 执行面"]
        RS[RunState<br/>RUNNING / SUCCESS / FAILED<br/>CANCELLED / TIMED_OUT / BUDGET_EXHAUSTED]
    end
    subgraph prog["Progress 收集面"]
        CS[CollectionState<br/>COLLECTING / SATURATED]
        SR[DiagnosisStopReason]
    end
    subgraph out["对外发布面"]
        RO[ReleaseOutcome<br/>SUCCESS / FALLBACK / FAILED / CANCELLED]
        FT[FallbackType]
        CF[ChatFailureCode]
    end
    RS -.->|"预算耗尽可并存"| RO
    SR -.->|"有 facts 常映射"| FT
    RO -.->|"失败粗码"| CF
层 枚举 回答的问题
Core RunState 这次 Run 技术上还能不能继续?
Progress DiagnosisStopReason / CollectionState 证据收集为何停、是否已饱和?
发布 ReleaseOutcome / FallbackType 用户看到报告、降级还是失败?
协议失败 ChatFailureCode SSE failure 的粗粒度原因?

典型组合:

场景 RunState StopReason ReleaseOutcome
正常成功 SUCCESS — SUCCESS
信息饱和且有事实 常仍可 SUCCESS 收尾* INFORMATION_SATURATED FALLBACK / INSUFFICIENT_EVIDENCE
预算耗尽且有事实 BUDGET_EXHAUSTED BUDGET_LIMIT_REACHED FALLBACK / INSUFFICIENT_EVIDENCE
预算耗尽且无事实 BUDGET_EXHAUSTED BUDGET_LIMIT_REACHED FAILED
客户端断开 CANCELLED — CANCELLED / 可能无 done

*饱和后若 Agent 合法写完 draft 并过 Guard,也可能 SUCCESS;若 stopped 无 draft 则走 FALLBACK。


2. 架构位置:异常在哪一层被「接住」

flowchart TB
    subgraph L0["L0 协议"]
        CTRL[ChatController / SSE]
    end
    subgraph L1["L1 Application"]
        APP[ChatApplicationUseCase<br/>统一 catch → ChatFailureCode]
        DEX[DiagnosisChatExecutor<br/>recoverInvalidDraft]
    end
    subgraph L2["L2 Agent 用例"]
        DAU[DiagnosisAgentUseCase<br/>controlledExecution]
    end
    subgraph L3["L3 框架 Loop"]
        RA[ReactAgent.call]
    end
    subgraph L4["L4 Interceptor / Boundary"]
        MI[ModelInterceptor]
        TI[ToolInterceptor]
        TB[ToolBoundary]
        CORE[DiagnosisHarnessCore]
    end

    CTRL --> APP --> DEX --> DAU --> RA
    RA --> MI --> CORE
    RA --> TI --> TB --> CORE

    MI -.->|BudgetExceeded / RunAborted 上抛| DAU
    TI -.->|多数 error observation 留在 loop| RA
    TI -.->|CollectionStopped 上抛| DAU
    DAU -.->|stopped| DEX
    DAU -.->|Draft 契约异常| DEX
    DEX -.->|未恢复| APP

3. Loop 内:一次 ReAct 轮次里发生什么

3.1 正常成功路径(对照)

sequenceDiagram
    participant DAU as DiagnosisAgentUseCase
    participant RA as ReactAgent
    participant MI as ModelInterceptor
    participant LLM as ChatModel
    participant TI as ToolInterceptor
    participant TB as ToolBoundary

    DAU->>RA: agent.call(input, config)
    RA->>MI: interceptModel
    MI->>MI: beforeModelCall 预算闸
    MI->>LLM: handler.call
    LLM-->>MI: tool_call
    MI-->>RA: ModelResponse
    RA->>TI: interceptToolCall
    TI->>TI: 协议/重复/饱和检查
    TI->>TB: invoke
    TB-->>TI: READY + agentResult
    TI-->>RA: 投影 observation
    RA->>MI: interceptModel 第 2 轮
    MI->>LLM: 写 Draft
    LLM-->>MI: 文本 JSON
    MI-->>RA: ModelResponse
    RA-->>DAU: AssistantMessage
    DAU->>DAU: 解析 DiagnosisDraft
    DAU-->>DAU: completed(draft, progress)

3.2 Loop 内:Model 路径异常(会穿出 loop)

触发点:HarnessModelInterceptor.interceptModel

beforeModelCall / handler.call / checkActive
  → BudgetExceededException
  → RunAbortedException(已终态、超时、取消)
  → 其它 RuntimeException(供应商错误等)
sequenceDiagram
    participant RA as ReactAgent
    participant MI as ModelInterceptor
    participant Core as DiagnosisHarnessCore
    participant DAU as DiagnosisAgentUseCase

    RA->>MI: interceptModel(第 N 次模型)
    MI->>Core: beforeModelCall
    Core-->>MI: throw BudgetExceeded / RunAborted
    Note over MI: 不吞异常,不转 observation
    MI-->>RA: 异常上抛
    RA-->>DAU: agent.call 失败
    DAU->>DAU: controlledExecution(e)
异常 Loop 内是否消化 穿出后
BudgetExceededException 否 → stopped(BUDGET_LIMIT_REACHED)
RunAbortedException(BUDGET_EXHAUSTED) 否 → 同上
RunAbortedException(CANCELLED/TIMED_OUT/…) 否 controlledExecution 再抛 → Application
其它未识别 否 → DiagnosisAgentOutputException(EXECUTION_FAILED)

3.3 Loop 内:Tool 路径(多数不穿出)

触发点:HarnessToolInterceptor.interceptToolCall + ToolBoundary

flowchart TD
    TC[收到 tool_call] --> SUP{是否证据工具?}
    SUP -->|否| H[handler.call 旁路]
    SUP -->|是| SAT{已 SATURATED 且 stop 已交付?}
    SAT -->|是| EX[throw DiagnosisCollectionStoppedException]
    SAT -->|否| PARSE[解析 Envelope / previous_observation]
    PARSE -->|协议违规| REP[可修复 error observation<br/>或连续违规后 stop]
    PARSE --> DUP{重复 scope?}
    DUP -->|是| DUPR[DUPLICATE_SCOPE observation]
    DUP -->|否| INV[ToolBoundary.invoke]
    INV -->|READY| OBS[投影 modelObservation 回注]
    INV -->|BUDGET_EXHAUSTED 等 error| ERR[error observation<br/>markBudgetLimitReached]
    EX --> OUT[穿出 ReactAgent loop]
    OBS --> LOOP[留在 loop,模型继续]
    ERR --> LOOP
    REP --> LOOP
    DUPR --> LOOP

设计选择:

情况 策略 原因
工具执行失败、投影失败、结果过大 error observation 留在 loop 给模型一次感知/改写机会,不立刻整 run 崩
预算在 ToolBoundary 触顶 先 error observation + progress 标记预算 本轮不强制撕开 loop;下一轮 model 的 beforeModelCall 会硬闸
信息饱和后仍要工具 DiagnosisCollectionStoppedException 穿出 收集已无价值,禁止空转
协议违规(未达阈值) repairable error observation 要求模型补 previous_observation 等

ToolBoundary 对预算的处理(吞异常 → 错误码,不抛给框架):

catch (RunAbortedException | BudgetExceededException)
  → ToolBoundaryResult.error(BUDGET_EXHAUSTED | RUN_INACTIVE)

4. Loop 边界:controlledExecution

位置:DiagnosisAgentUseCase,包住 agent.call(...)。

职责:把「Harness 约定的受控停止」从异常栈(含 cause 链)捞出来,转成 DiagnosisAgentExecution.stopped;其余失败交给外层。

flowchart TD
    E[catch Exception from agent.call] --> F1{cause 含 CollectionStopped?}
    F1 -->|是| S1[stopped progress + stopReason]
    F1 -->|否| F2{cause 含 RunAborted?}
    F2 -->|是且 BUDGET_EXHAUSTED| S2[markBudgetLimitReached<br/>stopped BUDGET_LIMIT_REACHED]
    F2 -->|是且其它终态| R1[再抛 RunAborted]
    F2 -->|否| F3{BudgetExceeded 或 lifecycle 已预算耗尽?}
    F3 -->|是| S2
    F3 -->|否| N[return null]
    N --> W[包装 DiagnosisAgentOutputException EXECUTION_FAILED]
    S1 --> RET[正常 return 给 Executor]
    S2 --> RET
输入信号 输出
DiagnosisCollectionStoppedException stopped(stopReason),draft=null
预算耗尽类 stopped(BUDGET_LIMIT_REACHED)
取消 / 超时类 RunAborted 不转 stopped,再抛
未知 null → 包装执行失败异常

结果形态:

completed(draft, progress)     // 有合法草稿
stopped(progress, stopReason)  // 无草稿,仅有进度快照

5. Loop 外:坏 Draft 与 recoverInvalidDraft

时机:agent.call 已经返回(或解析阶段),最终文本 不是合法 DiagnosisDraft。

位置:DiagnosisChatExecutor catch DiagnosisAgentOutputException。

flowchart TD
    A[DiagnosisAgentOutputException] --> K{isDraftContractFailure?}
    K -->|否 EXECUTION_FAILED| P1[再抛 → Application FAILED]
    K -->|是 EMPTY/INVALID_JSON/SCHEMA| H{hasObservedFacts?}
    H --> AUD[审计 agentDraftInvalid]
    AUD --> H2{hasObservedFacts?}
    H2 -->|否| P1
    H2 -->|是| SSE[status SAFETY_VALIDATING]
    SSE --> RID[releaseInvalidDraft progress]
    RID --> FB[FALLBACK INSUFFICIENT_EVIDENCE]

要点(与常见误解对照):

误解 实际
recover 里再验 draft 完不完整 draft 合法性已在 Agent 用例判定;此处只看 异常 Kind
hasProgress = 有过 tool_call = progress.observedFacts 非空(可发布观察事实)
降级会带上坏 JSON 丢弃非法正文,只根据 progress 生成 SafeFallback

专用发布:DiagnosisReleaseUseCase.releaseInvalidDraft

  • 不再跑 Evidence/Semantic Guard(没有可校验 draft)
  • 要求 hasObservedFacts(),否则 IllegalStateException
  • 产出 FallbackType.INSUFFICIENT_EVIDENCE

6. Release:按 execution 形态分叉

flowchart TD
    EX[DiagnosisAgentExecution] --> D{draft == null?}
    D -->|是 stopped| CS[releaseControlledStop<br/>需有 observedFacts]
    D -->|否| C{conclusion == null?}
    C -->|是| NC[releaseNoConclusion<br/>部分 Evidence + progress]
    C -->|否| RC[releaseConclusion<br/>Evidence → 可选 Repair → Semantic]
    CS --> FB[FALLBACK]
    NC --> FB
    RC -->|SUPPORTED| OK[SUCCESS]
    RC -->|其它| FB
入口 典型来源
releaseControlledStop controlledExecution → stopped
releaseInvalidDraft recoverInvalidDraft(loop 结束坏 draft)
releaseConclusion 正常 completed + 有结论
releaseNoConclusion draft 合法但无 conclusion

7. Application 统一失败出口

未被转成 FALLBACK / SUCCESS 的异常,进入 ChatApplicationUseCase:

sequenceDiagram
    participant EX as DiagnosisChatExecutor
    participant APP as ChatApplicationUseCase
    participant Core as DiagnosisHarnessCore
    participant Store as ChatRunStore
    participant SSE as ChatSseSession

    EX-->>APP: 抛 ChatApplicationException 或 RuntimeException
    APP->>Core: terminalOutcome / completeFailure
    APP->>Store: finish(terminal, ...)
    APP->>APP: safeFailure → ChatFailureCode
    APP-->>SSE: fail(code, 安全文案)

常见映射直觉:

情况 ChatFailureCode 倾向
取消 RUN_CANCELLED
路由失败 ROUTING_UNAVAILABLE
落库失败 RUN_PERSISTENCE_FAILED
其它 INTERNAL_FAILURE

8. 端到端对照表(按场景)

# 场景 发生位置 关键信号 第一处理点 用户侧
1 第 N 次模型前预算没了 Loop 内 Model BudgetExceeded controlledExecution → stopped 有 facts→FALLBACK;无→FAILED
2 工具执行失败 Loop 内 Tool TOOL_EXECUTION_ERROR observation 留在 loop 模型可能改写或再试
3 工具触顶预算 Loop 内 ToolBoundary error + markBudget 常留在 loop,下轮 model 硬闸 同预算结局
4 饱和后仍要工具 Loop 内 Tool DiagnosisCollectionStopped controlledExecution → stopped 有 facts→FALLBACK
5 取消/超时 Core → 任意 checkActive RunAborted controlledExecution 再抛 CANCELLED / FAILED
6 模型返回烂 JSON Loop 外解析 INVALID_JSON 等 recoverInvalidDraft 有 facts→FALLBACK;无→FAILED
7 执行崩溃未识别 Loop 边界 EXECUTION_FAILED recover 不恢复,上抛 FAILED
8 Guard 引用失败 Loop 外 Release Evidence 违规 repair 或 FALLBACK FALLBACK EVIDENCE_*
9 语义不支持 Loop 外 Release UNSUPPORTED FALLBACK FALLBACK SEMANTIC_*

9. 两条主恢复路径对比(必记)

flowchart LR
    subgraph mid["Loop 中途打断"]
        A1[Interceptor / Core 异常] --> B1[controlledExecution]
        B1 --> C1[stopped draft=null]
        C1 --> D1[releaseControlledStop]
    end

    subgraph end["Loop 正常结束但输出坏"]
        A2[解析 Draft 失败] --> B2[DiagnosisAgentOutputException]
        B2 --> C2[recoverInvalidDraft]
        C2 --> D2[releaseInvalidDraft]
    end

    D1 --> E[INSUFFICIENT_EVIDENCE FALLBACK<br/>前提 hasObservedFacts]
    D2 --> E
controlledExecution recoverInvalidDraft
时机 loop 中途 loop 结束后
输入 Throwable cause 链 DiagnosisAgentOutputException
成功产物 Execution.stopped 直接 DiagnosisExecutionResult(FALLBACK)
发布入口 releaseControlledStop releaseInvalidDraft
共同点 都依赖 可发布 observedFacts;都 fail closed

10. 代码索引

组件 路径
Model 拦截 harness/agent/HarnessModelInterceptor.java
Tool 拦截 harness/agent/HarnessToolInterceptor.java
工具边界 harness/tool/boundary/ToolBoundary.java
受控停止转换 harness/agent/DiagnosisAgentUseCase#controlledExecution
坏 Draft 恢复 harness/application/executor/DiagnosisChatExecutor#recoverInvalidDraft
非法 draft 发布 harness/release/DiagnosisReleaseUseCase#releaseInvalidDraft
受控停止发布 DiagnosisReleaseUseCase#releaseControlledStop
应用失败出口 harness/application/ChatApplicationUseCase
错误码注释 ChatFailureCode / ReleaseOutcome / FallbackType / RunState / DiagnosisStopReason / ToolBoundaryErrorCode 等

11. 阅读检查清单

  1. 这个失败是 还在 loop 里,还是 已经穿出 agent.call?
  2. 是 资源/取消(Core),还是 收集收敛(Progress),还是 输出契约(Draft Kind)?
  3. 最终有没有 observedFacts?有才能谈 FALLBACK。
  4. RunState 与 ReleaseOutcome 是否一致理解(预算耗尽 ≠ 一定 FAILED)?
  5. Tool 错误是 observation 还是 异常穿出?不要默认「有 error 就崩 run」。

12. 一句话总结

Loop 内:能安全回注的变成 observation,必须停收集或硬预算的穿出异常。
Loop 边界:controlledExecution 把受控停止收成 stopped。
Loop 外:坏 draft 用 recoverInvalidDraft,只认 observedFacts 做降级。
全程 fail closed:没有可验证事实,就不发「看起来友好」的假成功。