← 所有文章

推理模型为什么返回空回答:隐藏的思考 token 吃掉了整个输出上限

推理模型为什么返回空回答:隐藏的思考 token 吃掉了整个输出上限

连续三个晚上,摘要流水线什么都没产出,可每个请求都算「成功」:返回里是 finish_reason: "length" 和空的 content 字段。问题既不在网络,也不在账户额度,而在于推理模型如何计算输出预算:那些看不见的思考 token 和可见回答共用同一个 max_tokens。对普通对话模型够用的上限,推理模型会全部烧在内部推理上,一个字都写不出来。

到底哪里坏了

流水线本身很简单:抓取原始招聘结果,让模型写一段 200 字的摘要,再把文本存起来。周中作者把普通对话模型换成了推理模型,指望摘要更聪明、更准确。结果每个回答都带着 finish_reason: "length" 和空的 content。没有报错,也没有截断警告:请求形式上通过了,答案却不存在。

罪魁祸首是 max_tokens。七百个 token 对对话模型来说写 200 字绰绰有余,但推理模型的补全预算里还包含内部推理 token:在第一个可见字符出现之前,会先生成成千上万个看不见的步骤。700 的上限大约买到了零推理加零回答——模型在思考中烧光了预算,话说到一半就被截断。而 finish_reason 诚实地报告了 length:上限确实触发了,只是不是作者以为的那个上限。

Лимит вывода и скрытые токены размышления

怎么发现这种故障

最麻烦的地方是安静。没有错误,HTTP 状态码是 200,重试也不会触发,因为从传输层看一切正常。唯一的信号是两个字段同时出现:空的 content 加上 finish_reason: "length"。如果你的监控只看 finish_reason,或者只看请求状态,它会把这种情况读成一次正常完成。

还要记住一点:同一个参数名,在不同类别的模型上含义并不相同。兼容 OpenAI 的接口把这些差异藏了起来:对对话模型,max_tokens 几乎就是整个回答;对推理模型,它是回答加上一长串推理。如果你用同一个端点把同一段提示词发给多个模型家族,最好逐个家族核对响应里每个字段到底代表什么。

留在代码里的两处修复

  • 推理模型的补全预算提到了约 4 000 token。摘要更准了,账单几乎没变:单个思考 token 很便宜,但成批出现时很容易算错。
  • 现在「空 content 加 finish_reason: "length"」被当作完整错误,而不是静默成功。这样的响应会进入重试和日志,而不是作为成品写进数据库。

还有第三个不那么明显的结论:把流水线从对话模型迁到推理模型时,要重算的不只是价格,还有输出上限。否则你会得到同样的剧本——形式上成功的请求,和数据库里一行行空白。

我们自己的数据说明了什么

静默失败在生成类服务里也不少见。根据生产库的聚合数据(文件 server/seo/benchmarkData.json,不含提示词和标识符),在 2026 年 9 月 4 日到 10 月 4 日的 30 天里,平台共完成 12 689 次生成:9 907 张图片、2 334 段视频和 448 段音乐。在 90 天窗口(2026 年 7 月 6 日—10 月 4 日)内,gpt-image-2 的 3 518 次终态生成中成功率为 92,2%,seedream-45 在 1 127 次生成中为 91,9%。视频模型 kling-3.0 在同一时期的 374 次生成中成功率为 79,7%:视频本身更难,而因输出上限被截断,只是那些仅凭请求状态看不出来的「静默」失败中的一种。

分析来源是 dev.to 博客上的文章 «max_tokens=700 on a reasoning model returned empty replies — hidden thinking tokens was the whole budget»。更多实战拆解见我们的博客。