Skip to content

fix(core): persist state on call cancellation - #2820

Open
hanydd wants to merge 1 commit into
agentscope-ai:mainfrom
hanydd:fix/cancelled-call-state
Open

fix(core): persist state on call cancellation#2820
hanydd wants to merge 1 commit into
agentscope-ai:mainfrom
hanydd:fix/cancelled-call-state

Conversation

@hanydd

@hanydd hanydd commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

AgentScope-Java Version

2.0.3-SNAPSHOT

Description

背景

#2799 已在模型调用报错时保存安全的对话状态,但 Reactor cancellation 不会进入异常处理,因此取消普通或 structured-output 调用时,本轮用户输入仍可能丢失。

另外,如果 reasoning 已经提交了 tool call,而 acting 或 middleware 在写入 tool result 前报错,直接保存会留下没有结果的 PENDING tool call。

本 PR 是 #2713 基于 #2799 的精简后续,只补充上述两个场景。

改动

  • 普通、native structured-output 和 fallback structured-output 调用被取消时,先向上游传播 cancel,再尽力保存当前安全状态。
  • 复用 fix(agent): 模型调用失败时持久化当前轮次上下文 #2799 的报错保存流程;异常保存前删除本轮没有结果的 PENDING/ALLOWED tool call,保留可恢复的 ASKING
  • 不保存未完成的 assistant 流式片段,不修改 public API 或 Session schema。

测试

  • mvn -pl agentscope-core -Dtest=ReActAgentCallFailurePersistenceTest test
  • mvn -pl agentscope-core test

2302 tests passed,8 skipped。

Checklist

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (no documentation change required)
  • Code is ready for review

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.11111% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...e/src/main/java/io/agentscope/core/ReActAgent.java 71.11% 7 Missing and 6 partials ⚠️

📢 Thoughts on this report? Let us know!

@guslegend0510 guslegend0510 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查发现了两个会影响取消状态正确性的真实问题,不适合直接批准

Comment on lines +2167 to +2170
!(block instanceof ToolUseBlock toolUse)
|| toolUse.getState()
!= ToolCallState.PENDING
|| !pendingIds.contains(toolUse.getId()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] ALLOWED 工具调用未被清理

acting 在执行工具前会把调用改为 ALLOWED,因此工具执行中取消或 acting 后置失败时,这里的 PENDING 条件会保留一个没有 tool_result 的 tool_use。默认 pending recovery 关闭,下一轮普通调用会失败,空消息恢复还可能重复执行工具。应清理本轮无结果的 PENDING/ALLOWED,并保留 ASKING。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 21df397 修复:异常保存时会清理本轮无结果的 PENDING 和 ALLOWED tool call,并保留可恢复的 ASKING。新增测试分别覆盖 pre-acting PENDING、工具执行中取消后的 ALLOWED、post-acting failure 后的 ALLOWED,以及 ASKING 保留。

.ifPresent(ae -> scope.externalEventEmitter = ae);
}
return scope.doCallInner(msgs)
.doOnCancel(() -> saveStateAfterCancellation(scope))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 取消前同步保存会阻塞并竞态双写

doOnCancel 回调先同步持久化、随后才向上游传播 cancel,阻塞式状态库会延迟模型和工具停止,并允许 live state 在保存时继续变化;取消发生在正常异步保存期间时还会启动第二次 persistState,造成 loadedVersion/CAS 竞态。应统一为单一的取消终止保存流程,在上游取消后保存稳定快照。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 21df397 修复:三条执行路径统一通过 doFinally(CANCEL) 处理,cancel 会先传播给上游模型/工具,之后才修复并保存状态。hook 只包裹 doCallInner;正常异步保存开始后 inner 已结束,因此取消不会启动第二次保存。新增测试验证工具先收到 cancel、StateStore 后保存,以及正常保存期间取消不会双写。

@hanydd
hanydd force-pushed the fix/cancelled-call-state branch from e1ca130 to 21df397 Compare August 26, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants