Skip to content

[Bug]: 用户中断 reasoning 时会持久化未执行的工具调用,导致会话上下文无法恢复 #2470

Description

@hanydd

问题描述

当用户在 ReActAgent 的 reasoning 阶段中断执行时,框架会把当前已经累计的 reasoning message 持久化到 AgentState

如果模型此时已经开始生成 ToolUseBlock,但 acting 尚未开始,该工具调用也会被写入会话上下文。随后 handleInterrupt() 会再追加一条 GenerateReason.INTERRUPTED 的 recovery assistant 消息。

最终上下文中会留下只有 tool_use、没有对应 tool_result 的悬空工具调用。下一轮请求即使开启 enablePendingToolRecovery=true,也无法识别并修复该调用,严格遵守工具调用协议的模型可能直接拒绝这段历史。

影响版本

已确认以下版本存在该问题:

AgentScope Java v2.0.0

当前 main 分支的 2.0.1-SNAPSHOT 中也仍然存在。

相关代码

文件:

agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java
agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/ReasoningContext.java

reasoning 流检测到中断后,会构造并保存当前累计消息:

.onErrorResume(
        InterruptedException.class,
        error -> {
            Msg msg = context.buildFinalMessage();
            if (msg != null) {
                // ...
                state.contextMutable().add(msg);
            }
            return Mono.error(error);
        })

ReasoningContext.buildFinalMessage() 会无条件加入已经累计的工具调用:

List<ToolUseBlock> toolCalls = toolCallsAcc.buildAllToolCalls();
blocks.addAll(toolCalls);

之后 handleInterrupt() 追加 recovery assistant 消息:

scope.state.contextMutable().add(recoveryMsg);

而 pending tool 检测只检查最后一条 assistant 消息:

Msg lastAssistant = findLastAssistantMsg();
if (lastAssistant == null
        || !lastAssistant.hasContentBlocks(ToolUseBlock.class)) {
    return Set.of();
}

此时最后一条 assistant 已经是 recovery 消息,因此前一条 reasoning message 中的悬空工具调用不可见。

复现过程

可以使用闸门式模拟模型稳定复现:

  1. 模型先返回文本和一个 ToolUseBlock(call-1)
  2. 在后续 chunk 返回前暂停模型流;
  3. 调用 agent.interrupt(runtimeContext)
  4. 释放模型流,使框架在下一个 chunk 前检测到中断;
  5. 从同一个 AgentStateStore 重建 Agent;
  6. 开启 enablePendingToolRecovery=true 并发起下一轮请求;
  7. 检查持久化上下文以及下一轮传给模型的完整消息。

模型已经完整返回工具调用、但框架在进入 acting 前检测到中断时,也会出现相同问题。

实际结果

中断后的持久化上下文为:

USER
  用户输入

ASSISTANT
  partial text
  tool_use id=call-1

ASSISTANT GenerateReason.INTERRUPTED
  I noticed that you have interrupted me. What can I do for you?

其中不存在:

TOOL
  tool_result id=call-1

下一轮 pending recovery 因最后一条 assistant 是 recovery 消息而判断没有 pending tool call,原来的 tool_use(call-1) 会原样进入模型历史。

期望行为

reasoning 阶段被用户中断时,acting 尚未开始,工具也没有执行。因此持久化上下文不应保留本轮尚未执行的 ToolUseBlock

已经生成的文本和 thinking 内容可以继续保留,随后正常追加 interrupt recovery 消息。下一轮模型收到的历史中不应存在没有对应结果的工具调用。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions