Qwen3.7-Plus 在启用 tools 时为什么会截断输出?

作者:袖梨 2026-09-13

Qwen3.7-Plus 在启用 tools 后输出半截中断,不能只凭页面上“少了一段文字”判断是模型 Bug。应先保存原始流式事件,分别检查 HTTP 是否完整结束、最后一个数据包的 finish_reason、工具参数是否拼接完整,以及应用是否把工具调用阶段误当成最终回答。原帖报告了带 tools 时高概率截断和重复结束原因,但售后尚未给出结论,因此当前最可靠的处理方式是复现、分层定位并保留请求 ID。

先分清哪一段被截断

工具调用(Function Calling)不是一次普通的文本生成。模型可能先返回工具名和参数,应用执行工具,再把 tool 角色的结果发回模型,最后才生成面向用户的答案。任意一段提前结束,界面上都可能表现为“只输出一半”。

第一类是传输中断:服务端仍在生成,但代理、网关、SDK 超时或客户端主动取消连接。第二类是生成达到限制:输出 Token 达到上限,结束原因为 length。第三类是工具调用被误判:模型已经正常结束当前工具调用轮次,客户端却没有继续执行工具或请求下一轮。第四类才是服务端返回序列异常,例如同一选择出现互相冲突的结束事件或工具参数永久缺失。

为什么启用 tools 后更容易暴露问题

普通流式文本只需按顺序拼接 delta.content。工具调用还需要按选择序号和工具调用序号累计名称、调用 ID 与 JSON 参数。参数可能跨多个数据块返回,不能把每个片段单独解析为完整 JSON,也不能收到第一个非空 finish_reason 就立刻丢弃队列中尚未消费的数据。

阿里云百炼的官方 OpenAI 兼容接口说明,设置 tools 后,模型决定调用工具时会通过 tool_calls 返回信息。对于包含数组或对象的复杂工具参数,Qwen 系列还提供 tool_stream 控制是否流式输出参数。关闭时参数集中返回,格式通常更准确,但复杂参数可能面临等待时间;开启时复杂参数分片输出,客户端必须正确累计。

最小复现应该记录什么

复现请求要尽量小,只保留一个工具、一个必填字符串参数和一条用户消息。固定模型精确名称、地域、接入域名、SDK 版本、streamtool_stream、思考模式、输出上限和超时设置。每次测试保存以下信息:

  • 脱敏后的完整请求体和请求时间;
  • HTTP 状态、响应头与平台请求 ID;
  • 每个原始流式数据包,而不是只保存最终拼接文本;
  • 每个 choice 的索引、delta、tool call 索引和结束原因;
  • 连接由哪一层关闭,以及客户端是否触发取消或超时。

如果不能记录原始事件,至少在解析器前增加日志。只看聊天界面无法区分服务端少发、SDK 少读、代理截断和业务代码提前停止。

正确拼接流式工具参数

工具参数应按 choice.indextool_calls[].index 建立独立缓冲区。每个数据块只追加本次增量;直到该轮结束后,才把完整字符串作为 JSON 解析。工具调用 ID 和名称也可能分片或只在早期数据块出现,不能用后续空值覆盖已保存值。

for event in stream:
    save_raw_event(event)
    for delta_call in event.tool_call_deltas:
        key = (event.choice_index, delta_call.index)
        buffers[key].id = buffers[key].id or delta_call.id
        buffers[key].name += delta_call.name_fragment or ""
        buffers[key].arguments += delta_call.arguments_fragment or ""
    record_finish_reason(event.choice_index, event.finish_reason)

parse_json_only_after_stream_finishes(buffers)

这段伪代码的重点不是具体 SDK 字段名,而是保留原始顺序、按索引聚合和延迟解析。若应用使用统一 OpenAI 适配层,还要确认适配层没有把厂商扩展字段过滤掉。

如何解释 finish_reason

finish_reason 表示当前选择停止生成的原因,不等于整条业务链路已经得到最终文本。遇到 length 时先提高合理的输出上限或缩短上下文;遇到工具调用结束时,应执行工具并提交带匹配 tool_call_id 的工具结果;值为 null 的中间流式包通常不能作为终止信号。

百炼官方文档还指出,非流式请求超时后可能返回已生成的部分内容,并通过响应头标识部分响应;无法读取响应头时,可结合结束原因判断内容是否完整。因此,排查时不能只打印最终字符串,响应头和尾包同样重要。

若同一个 choice 确实收到两条非空且语义冲突的结束原因,应保留未经 SDK 二次加工的 SSE 数据。某些封装会把工具调用轮次与最终回答轮次合并展示,看起来像重复;只有原始流中同一请求、同一 choice 的事件异常,才能更有力地指向服务端或兼容层问题。

按顺序做对照测试

  1. 保持提示词不变,去掉 tools,确认普通文本流是否完整。
  2. 恢复 tools,但设置不调用工具,检查仅携带定义是否触发异常。
  3. 强制调用一个最简单的工具,比较流式与非流式响应。
  4. 分别测试 tool_stream 开关,观察复杂参数是否只在一种模式下失败。
  5. 绕过反向代理和业务适配层,使用最小客户端直连同一接入域名。
  6. 固定请求重复运行,统计失败比例并保存每次请求 ID。

若直连也能稳定复现,而且原始尾包或工具参数不完整,就把最小请求、发生时间、地域、模型名和请求 ID 提交给平台售后。若直连正常而生产链路失败,则逐层恢复网关、SDK 封装和业务解析器,最先恢复后出现异常的层就是主要排查对象。

生产环境的临时规避方式

在原因未确认前,可以让复杂工具参数采用非流式返回,或对工具调用轮次关闭流式,仅对最终回答使用流式输出。提高客户端读取超时和网关空闲超时也能排除连接层问题,但不能掩盖参数拼接错误。重试必须使用幂等设计,尤其是支付、发信和写数据库等有副作用的工具,避免截断后重复执行。

应用还应把“收到完整工具参数”“工具执行成功”“收到最终回答尾包”设计成三个独立状态。任何状态缺失都应显示可恢复错误,而不是把半段内容当作成功回答。这样即使问题来自模型服务,也能防止异常结果直接进入生产业务。

判断是否修复的标准

修复不能以一次请求成功为准。应使用同一最小样例分别测试有无 tools、流式与非流式、简单与复杂参数,并连续运行足够次数。成功标准是工具参数均可解析、调用 ID 能正确关联、每轮结束状态一致、最终回答完整,且生产网关日志中没有超时或主动取消。

因此,Qwen3.7-Plus 带 tools 截断的首要动作是抓取原始事件并建立对照,而不是直接更换模型或只调大 Token 上限。原帖现象值得提交平台排查,但在售后结论出现前,只有把传输层、流式解析、工具状态机和服务端响应逐层分开,才能判断它究竟是模型接口异常还是客户端兼容问题。

相关文章

精彩推荐