agentscope-ai / agentscope-ai/agentscope-java

[Bug]: onError 路径(超时/LLM 异常)下 AgentState 也不持久化,#2461 是否可一并覆盖?

Đang mở
#2,702 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
area/core/agent bug
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

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.