Skip to content

[Bug]: 流式工具调用续片带空串 id/name 时累加器丢弃参数片段,导致 "Arguments are invalid JSON" #3225

Description

@Priestess-ai-0

简述

部分按 OpenAI 流式规范分片发送工具调用参数、且续片把 id/name 写成空字符串 "" 的模型(如 glm-5.2),其工具调用会被 tool_call_accumulator 判定为 Arguments are invalid JSON. 而整轮失败重试。根因不在模型:模型实际发出了合法 JSON,但累加器只保留了每个工具调用的第一片 {,后续参数片段被丢弃。

环境

  • OpenBitFun Desktop: 1.0.1(Windows)
  • 模型: glm-5.2(provider: sensenova,OpenAI 兼容流式)
  • 出现会话: 20260923T213930、20260924T163057(前者参数解析失败 9 次,后者 8 次)

复现

让 glm-5.2 在一轮里并行发起两个工具调用(如 ExecCommand + Glob)。app.log 出现:

[ERROR][openbitfun_agent_stream::tool_call_accumulator] Tool call arguments parsing failed at boundary=finish_reason: tool_id=call_9072c7daa3af466782cee2ea, tool_name=Glob, raw_len=1, error=EOF while parsing an object at line 1 column 1, parse_error_category=eof, parse_error_is_eof=true, completion=Unknown
[ERROR][openbitfun_agent_stream] SSE Error: Has invalid tool calls
[ERROR][openbitfun_core::agentic::tools::pipeline::tool_pipeline] Tool execution failed: error=Validation error: Arguments are invalid JSON.
[WARN][openbitfun_core::agentic::execution::round_executor] Retrying stream because provider returned only invalid tool arguments: ... local_retry=1/10

关键:raw_len=1 —— 累加器里每个工具调用只剩 1 个字符 {。

根因(SSE 证据)

模型把同一工具调用的 arguments 拆成多个 SSE chunk 发送,首片带 id/name,续片把它们写成空串:

17: tool_calls[0] id="call_b45..." name="ExecCommand" arguments="{"
18: tool_calls[0] id=""            name=""            arguments="\"cmd\": "
19: tool_calls[0] id=""            name=""            arguments="\"Get-"
...(续片累积命令字符串)
28: tool_calls[0] id=""            name=""            arguments="\"}"
29: tool_calls[1] id="call_907..." name="Glob"       arguments="{"
30: tool_calls[1] id=""            name=""            arguments="\"pattern\": "
33: tool_calls[1] id=""            name=""            arguments="}"
34: finish_reason="tool_calls"

把续片 arguments 顺序拼接,得到的是合法 JSON:

  • ExecCommand → {"cmd": "Get-Date; Write-Output \"PowerShell 工具运行正常"}
  • Glob → {"pattern": "*.md"}

但累加器只保留了首片的 {,续片全部丢失 → 解析到 EOF while parsing an object at line 1 column 1。

为什么"有些模型"出问题(对比)

同一台机器上 qwen3.8-27b-uncensored 基本正常。其续片是把 id/name 字段省略(而非空串):

"tool_calls":[{"index":0,"type":"function","function":{"arguments":"{"}}]
"tool_calls":[{"index":0,"type":"function","function":{"arguments":"\"file_path\":"}}]

glm-5.2 的续片则是空串:

"tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"\"cmd\": "}}]

两种写法都符合 OpenAI 流式语义,但实现细节不同:省略字段 → serde 反序列化为 None,累加器走续片追加分支(正常);空串 "" → 累加器按 id 查归属工具调用,查不到(真 id 是 call_xxx),于是丢弃该参数片段,缓冲区永远停在 {。

(注:9/21 那次 qwen 的唯一失败是另一类:boundary=graceful_shutdown、raw_len=67、completion=Interrupted,是流被中途打断导致参数真的截断,且已累积到 67 字符,反证 qwen 的累加本身是工作的。)

期望行为

续片无论 id/name 是省略还是空串,都应视为"无 id/无 name"的续片,按 index 追加到对应工具调用的参数缓冲区。两种写法都应正确合并出完整 JSON。

修复建议(openbitfun_agent_stream::tool_call_accumulator)

  1. 续片归属应按 index 匹配,而不是按 id;或
  2. 当 id 为空串 / name 为空串时,等价于 None,走续片追加分支,而非当成新 id 去查表。

影响范围

凡按 OpenAI 规范分片发送 arguments、且续片带空串 id/name 的模型都会 100% 失败并触发整轮重试(local_retry 最多 10 次)。续片字段被省略的模型不受影响。已用本机真实 SSE 流量验证(上述日志片段为原始截取,仅脱敏用户路径)。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions