Skip to content

[Bug]: 1.0.11 流式模式下 ToolUseBlock name 为 null #1237

@chickenlj

Description

@chickenlj

问题描述

Java 使用 AgentScope 1.0.11 + 非 OpenAI 模型(如 Qwen3-14b)开启流式响应(<font style="color:rgb(0, 0, 0);">stream=true</font>)时,ReAct Agent 执行 MCP 工具调用后,日志中持续输出以下警告且agent无法调用mcp且无法正常返回响应给客户端, 设置 <font style="color:rgb(0, 0, 0);">agent.stream=false</font>后问题消失。

WARN  i.a.c.f.o.OpenAIMessageConverter - ToolUseBlock has null id or name, skipping

影响范围

  • 功能影响:agent无法调用mcp且无法正常返回响应给客户端
  • 日志噪音:每轮 ReAct 迭代重复打印 WARN
  • 潜在风险:被丢弃的 ToolUseBlock 不会出现在发给模型的对话历史中,可能影响模型对上下文的理解

根因定位

缺陷位置<font style="color:rgb(0, 0, 0);">io.agentscope.core.agent.accumulator.ToolCallsAccumulator.ToolCallBuilder#build()</font>

调用链路

  1. LLM(Qwen3, stream)返回流式 chunk,部分 chunk 不携带 <font style="color:rgb(0, 0, 0);">function.name</font>
  2. <font style="color:rgb(0, 0, 0);">OpenAIResponseParser.parseChunkResponse()</font> 将 name 为 null 的 chunk 设为空串 <font style="color:rgb(0, 0, 0);">""</font>,创建 fragment ToolUseBlock(<font style="color:rgb(0, 0, 0);">name="__fragment__"</font>
  3. <font style="color:rgb(0, 0, 0);">ToolCallsAccumulator.ToolCallBuilder.merge()</font> 判断 <font style="color:rgb(0, 0, 0);">"__fragment__"</font> 为 placeholder,跳过赋值
  4. <font style="color:rgb(0, 0, 0);">ToolCallBuilder.name</font> 始终为初始值 <font style="color:rgb(0, 0, 0);">null</font>
  5. <font style="color:rgb(0, 0, 0);">ToolCallBuilder.build()</font> 生成 <font style="color:rgb(0, 0, 0);">ToolUseBlock(id=generated, name=null)</font>
  6. <font style="color:rgb(0, 0, 0);">ReasoningContext.buildFinalMessage()</font> 将该 ToolUseBlock 存入 Memory 的 assistant Msg
  7. 下一轮迭代 <font style="color:rgb(0, 0, 0);">OpenAIMessageConverter.convertAssistantMessage()</font> 检测到 <font style="color:rgb(0, 0, 0);">name==null</font>,打印 WARN 并跳过

代码缺陷

<font style="color:rgb(0, 0, 0);">ToolCallBuilder.build()</font><font style="color:rgb(0, 0, 0);">toolId</font> 做了 null 兜底(生成合成 ID),但对 <font style="color:rgb(0, 0, 0);">name</font> 没有做任何兜底处理,直接透传了可能为 null 的字段:

// ToolCallBuilder.build() 反编译还原
ToolUseBlock.builder()
    .id(toolId != null ? toolId : generateId())  // ✅ 有兜底
    .name(name)                                   // ❌ 无兜底,可为 null
    .input(mergedArgs)
    .build();

临时规避

agent:
  stream: false

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions