问题描述
当用户在 ReActAgent 的 reasoning 阶段中断执行时,框架会把当前已经累计的 reasoning message 持久化到 AgentState。
如果模型此时已经开始生成 ToolUseBlock,但 acting 尚未开始,该工具调用也会被写入会话上下文。随后 handleInterrupt() 会再追加一条 GenerateReason.INTERRUPTED 的 recovery assistant 消息。
最终上下文中会留下只有 tool_use、没有对应 tool_result 的悬空工具调用。下一轮请求即使开启 enablePendingToolRecovery=true,也无法识别并修复该调用,严格遵守工具调用协议的模型可能直接拒绝这段历史。
影响版本
已确认以下版本存在该问题:
当前 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 中的悬空工具调用不可见。
复现过程
可以使用闸门式模拟模型稳定复现:
- 模型先返回文本和一个
ToolUseBlock(call-1);
- 在后续 chunk 返回前暂停模型流;
- 调用
agent.interrupt(runtimeContext);
- 释放模型流,使框架在下一个 chunk 前检测到中断;
- 从同一个
AgentStateStore 重建 Agent;
- 开启
enablePendingToolRecovery=true 并发起下一轮请求;
- 检查持久化上下文以及下一轮传给模型的完整消息。
模型已经完整返回工具调用、但框架在进入 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 消息。下一轮模型收到的历史中不应存在没有对应结果的工具调用。
问题描述
当用户在
ReActAgent的 reasoning 阶段中断执行时,框架会把当前已经累计的 reasoning message 持久化到AgentState。如果模型此时已经开始生成
ToolUseBlock,但 acting 尚未开始,该工具调用也会被写入会话上下文。随后handleInterrupt()会再追加一条GenerateReason.INTERRUPTED的 recovery assistant 消息。最终上下文中会留下只有
tool_use、没有对应tool_result的悬空工具调用。下一轮请求即使开启enablePendingToolRecovery=true,也无法识别并修复该调用,严格遵守工具调用协议的模型可能直接拒绝这段历史。影响版本
已确认以下版本存在该问题:
当前
main分支的2.0.1-SNAPSHOT中也仍然存在。相关代码
文件:
reasoning 流检测到中断后,会构造并保存当前累计消息:
ReasoningContext.buildFinalMessage()会无条件加入已经累计的工具调用:之后
handleInterrupt()追加 recovery assistant 消息:而 pending tool 检测只检查最后一条 assistant 消息:
此时最后一条 assistant 已经是 recovery 消息,因此前一条 reasoning message 中的悬空工具调用不可见。
复现过程
可以使用闸门式模拟模型稳定复现:
ToolUseBlock(call-1);agent.interrupt(runtimeContext);AgentStateStore重建 Agent;enablePendingToolRecovery=true并发起下一轮请求;模型已经完整返回工具调用、但框架在进入 acting 前检测到中断时,也会出现相同问题。
实际结果
中断后的持久化上下文为:
其中不存在:
下一轮 pending recovery 因最后一条 assistant 是 recovery 消息而判断没有 pending tool call,原来的
tool_use(call-1)会原样进入模型历史。期望行为
reasoning 阶段被用户中断时,acting 尚未开始,工具也没有执行。因此持久化上下文不应保留本轮尚未执行的
ToolUseBlock。已经生成的文本和 thinking 内容可以继续保留,随后正常追加 interrupt recovery 消息。下一轮模型收到的历史中不应存在没有对应结果的工具调用。