侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 123 篇文章
  • 累计收到 7 条评论

工具参数被 length 截断,补出对象也是错的

2026-9-25 / 0 评论 / 6 阅读

工具参数被 length 截断,补出对象也是错的

一次工具调用解析失败,报的是 Unterminated string in JSON at position 34。排查按顺序走:先看这次生成是怎么收尾的,再看拼出来的字符串缺在哪,最后试补。下面这份 chunk 序列是按流式响应的字段形状手写的桩,模型侧行为这台机器上没法实测;拼装与解析的结果都是本机跑出来的。

先看 finish_reason

== 拼完之后拿到什么 ==
finish_reason      : length
工具调用个数       : 1
工具名             : create_order
arguments 字符串   : "{\"sku\":\"A-1001\",\"qty\":12,\"addr\":\"深"
arguments 长度     : 34

finish_reason 是 length,不是 tool_calls,也不是 stop。工具名 create_order 和调用 id 都在,arguments 长度 34。这个字段说明生成是被 token 上限截断的,模型自己并没有收尾。

按这份回放看,finish_reason 只在最后一个 chunk 里出现,前面几次都是 null,所以要等流结束才能确认截断。想在流中间就止损,只能自己统计已经收到的长度,或者给这次调用单独设一个更紧的上限。

拼出来的字符串缺了什么

== 直接解析这份 arguments ==
JSON.parse 抛错 : SyntaxError: Unterminated string in JSON at position 34 (line 1 column 35)

报错位置是 34,正好是字符串末尾,含义是「引号还没闭合就到了结尾」。JSON 解析器只报第一个错误,它报的是结构还没成型,和字段校验无关。

先试补括号

== 补上收尾括号再解析(常见的兜底写法)==
补完是 : {"sku":"A-1001","qty":12,"addr":"深}
还是抛错 : SyntaxError: Unterminated string in JSON at position 35 (line 1 column 36)

只补花括号是最常见的兜底写法,这一步还是抛错,位置从 34 挪到 35,等于白补。原因在截断点落在字符串内部:花括号补齐了 qty 那一层,addr 的引号仍然开着。

连引号一起补:解析成功,值是错的

== 连开着的引号一起补 ==
补完是 : {"sku":"A-1001","qty":12,"addr":"深"}
解析结果 : {"sku":"A-1001","qty":12,"addr":"深"}
字段齐全吗 : qty = 12  addr = "深"

把缺的引号和花括号都补上,JSON.parse 通过了,拿到一个「合法」对象:sku 和 qty 都在,addr 是「深」。原本要写的是「深圳」。这一份传下去不会报错,落库也不会报错,只有在页面上看到地址少一个字的时候才会有人回头查。

顺便记一下这类修复函数的适用边界:截断点落在结构层时,补括号只补结构,值不受影响;落在字符串内部时,补出来的字符串照样「合法」,值却短了一截。判断依据是 JSON.parse 的报错消息里有没有 Unterminated string,而不是补完之后能不能解析成功。

这就是补 JSON 最危险的地方:它修改的不是结构,是内容。字段少一个会立刻报错,值被截掉一半反而一路绿灯。

两种收尾的对照

== 两种收尾的对照 ==
正常收尾 (stop)        长度  37  解析成功 → {"sku":"A-1001","qty":12,"addr":"深圳"}
被截断 (length)       长度  34  解析失败 → Unterminated string in JSON at position 34 (line 1 column 35)

正常收尾的 37 个字符解析出完整字段,被截断的 34 个字符直接被拒。三次实验的差别只在一个 finish_reason,字符串前面 34 个字符完全一样。

三种处理方式

先看 finish_reason,值是 length 的时候把这次工具调用整个丢掉,要求重新生成。这一条几乎没有代价,被截断的调用本来就没有完整参数可用。

请求侧要留余量:max_tokens 得同时装下回答和工具参数,参数长的工具尤其容易被挤。参数一长,length 出现的概率就跟着涨。

确实要兜底修复的话,按报错位置分:报错落在结构层(缺括号)可以补,报错落在字符串内部(Unterminated string)直接放弃,因为补出来的值可能是错的。我的取舍是不做静默修复,截断就失败重试,补出来的对象从字面上看不出问题,一旦落库就要靠人工核对才能发现。

重试之前还要确认这次调用有没有真的发出去。参数解析失败的情况下不会发出请求,这一层是安全的;但如果是解析成功之后请求超时,重试就有可能生成第二单,写操作的工具要带一个调用侧的幂等键来处理这种重复。

模型在什么条件下会把参数写这么长、length 出现的频率有多高,这两点这台机器上没实测,只能确认解析侧的行为:被截断的 arguments 修不好,能修的那次也会改值。

    🤞 分享