agentscope-ai / agentscope-ai/agentscope-java
[Bug]: onError 路径(超时/LLM 异常)下 AgentState 也不持久化,#2461 是否可一并覆盖?
- Ngôn ngữ chính
- Java
- Star
- 5.6k
- Fork
- 1.3k
- Merge trung bình
- 4 ngày 12 giờ
- Pull request đã merge (30 ngày)
- 77
Mô tả
## 描述
感谢 #2008 修复 USER interrupt 路径下的 state 持久化,以及 #2461 正在处理 cancellation 路径。
我在阅读当前 main 分支(commit `c49dc199`)时发现,**除 `InterruptedException` 和下游 cancellation 之外的其他 mid-stream 错误路径**,似乎仍然会丢失 in-memory `AgentState`(包括 user input、partial reasoning、tool state 等)。这与 #1997 报告的本质相同,只是触发条件不同。
想请教一下:#2461 的 hook 机制是否考虑过扩展到 `onError` 路径?还是希望我把这个问题作为单独的 issue 跟进?
## 受影响的触发场景
1. `TimeoutException`(显式 `.timeout(...)` 或 LLM 调用超时)
2. `RetryExhaustedException` / LLM provider 抛出的异常
3. `ModelError` / 网络异常
4. `HarnessAgent.wrappedStreamEvents` 路径下的任意错误
## 复现步骤
1. 构建 `HarnessAgent`(或 `ReActAgent`),配置 `JsonFileAgentStateStore`
2. 通过 `streamEvents(...)` 启动一个长任务
3. 让 mock model 在发出部分 `TextBlockDeltaEvent` 之后抛出 `RuntimeException("boom")`
4. 等待流 error 终止
5. 从 store 重新加载 `AgentState`
6. 观察:本次流期间 `addToContext(msgs)` 添加的 user input + 任何 partial state 都不在持久化结果中
```java
Model erroringModel = mock(Model.class);
when(erroringModel.stream(anyList(), any(), any()))
.thenReturn(Flux.concat(
Flux.just(textChunk),
Flux.error(new RuntimeException("boom")))); // 任何非 InterruptedException
HarnessAgent agent = HarnessAgent.builder()
.name("t")
.model(erroringModel)
.workspace(workspace)
.abstractFilesystem(new LocalFilesystem(workspace))
.stateStore(new JsonFileAgentStateStore(...)) // 配置了持久化
.build();
agent.streamEvents(messages, ctx).blockLast();
// 此时 store 中应包含 partial state(user msgs + 任何 emit 过的内容)
// 实际:从 store 读回,state.context 为空,user msg 丢失
```
## 期望行为
`createErrorHandler`(或类似的错误处理点)应在 rethrow error 之前 best-effort 持久化当前 `AgentState`,与 #2461 处理 cancellation 的策略一致。
## 根因分析
`AgentBase.createErrorHandler`(`agentscope-core/src/main/java/io/agentscope/core/agent/AgentBase.java:495-503`):
```java
return error -> {
if (error instanceof InterruptedException
|| (error.getCause() instanceof InterruptedException)) {
return handleInterrupt(createInterruptContext(), originalArgs);
}
return notifyError(error).then(Mono.error(error)); // ← 其他错误绕过 saveStateToSession
};
```
只有 `InterruptedException` 走 `handleInterrupt` 路径(#2008 修过),其他错误直接 rethrow,不调 `saveStateToSession`。
`HarnessAgent.wrappedStreamEvents`(`agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java:877-892`)与 `wrappedCall`(line 828-854)不对称——前者没有任何错误恢复或 state 保存。
## 可能的方案(仅供参考)
如果 maintainer 认为这个范围值得合并到 #2461,可以考虑:
- **方案 A**:扩展 `onAgentExecutionCancelled(callScope)` 为更通用的 `onAgentExecutionTerminated(callScope, signal)`,`SignalType.onError` 也触发 state 保存
- **方案 B**:在 `createErrorHandler` 的 `notifyError(error).then(...)` 之前 best-effort 调一次 `saveStateToSession`(需要把 `saveStateToSession` 提升到 `AgentBase` 或用 protected hook)
- **方案 C**:在 `ReActAgent.buildAgentStream` 的 `doFinally` 中判断 `SignalType.onError` 并触发保存
每个方案都有权衡(错误路径上 IO 的延迟 vs 数据丢失的成本),欢迎指正。
## 环境
- AgentScope-Java 版本:2.0.0 / main (`c49dc199`)
- Model:任意(mock 或真实 LLM provider)
- StateStore:`JsonFileAgentStateStore`(其他 store 同理)
## 关联
- #1997 — 原始 cancellation 路径 bug
- #2461 — 修复 PR(已批准,待 merge)
- #2007 / #2008 — 已修复的 USER interrupt 路径
非常感谢各位 maintainer 的辛勤工作 🙏
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.