故障排查 / 输出质量怪
模型“回”了但内容是空的 / 只蹦出几个字就断(思考模型)
本站未复现 改写 影响:llama.cpp / Ollama / 任何把思考与正文共用一份输出预算的推理栈 全版本(思考型模型通用行为) · Windows / Linux / macOS
报错原文
{"choices":[{"message":{"content":""},"finish_reason":"length"}]}
{"response":"","thinking":"Okay, the user is asking for...","done_reason":"length"}这段字符串可以直接粘到 站内搜索 或外部搜索引擎里找。
现象
调用没报错、状态码正常,但返回的正文是空的,或只蹦出「Based」这种一两个字就断了。尤其发生在思考/推理型模型上(Qwen3 的 -thinking 变体、DeepSeek-R1 类、任何会先写思考块再答的模型):reasoning / thinking / reasoning_content 字段里一大段思考,正文却是空的。重试时好时坏——短问题正常、一换长一点的题就空。
原因
思考模型在给出可见答案前,会先生成一段隐藏的推理(reasoning)。关键在于:这段推理和正文**共用同一份输出 token 预算**(llama.cpp 的 n_predict、Ollama 的 num_predict、OpenAI 兼容端点的 max_tokens),而模型不会给正文留余量。推理一旦把预算吃光,生成就在第一个可见字之前停了。响应本身格式完好、也算“成功”(HTTP 200、无 error),所以很容易被当成“模型坏了 / 不肯回答”。其实有三种状态:① 空(推理吃掉 100% 预算,一个字都没剩下)② 截断(答案开了头被切半)③ 完整(finish_reason=stop)——只有②看起来像“正常但不对劲”,①最容易被误判为故障。
按这个顺序处理
- 先把输出预算抬到思考模型“想完再说话”够用:思考模型 max_tokens / num_predict 至少给到 2048,复杂推理 4096 起。参照系:llama.cpp 官方讨论里一台本地 RTX 3000 实测,问「2+2」光内部思考就烧掉约 420 token,可见答案只有 “four” 一个词——预算卡在 50 必然空。
- 不需要深度思考的场景,直接关思考(省 token、省时间):llama.cpp 用 --reasoning-budget 给思考单独封顶(用完强制收尾、正文照常出,不会把正文截断);Ollama 用**原生 /api/chat 顶层参数** `think:false` 关闭。
- 注意 Ollama 的 OpenAI 兼容端点 /v1 会**忽略** think 参数(0.30 起 `reasoning_effort:"none"` 才生效)——这是最坑的一个不对称:同一个模型走 /api/chat 能关思考,走 /v1 关不掉、照样烧预算出空回复。你的客户端如果强制走 /v1,就发 `reasoning_effort:"none"`。
- 别把空回复当成“模型的回答”:代码里判 `finish_reason=="length" && content 为空` 时,去读 reasoning_content / thinking 字段。预算被思考吃光时,思考是满的、答案是空的——读那个字段一眼就能看出这是预算问题、不是配置或权重坏了,也就不会去瞎改量化/模板。
- 别用一个全局 max_tokens 默认值同时伺候思考模型和普通模型:一个够思考模型想完还答的预算,喂普通模型是浪费;一个贴普通模型的预算,留思考模型一句都说不出来。按模型分开设默认值。
怎么确认解决了
同一条“问 2+2”,把 max_tokens 从触发空回复的小值抬到 2048/4096(或用上面方法关掉思考)后重跑:正文有内容、finish_reason 变成 stop,且「思考字段长度 + 正文长度」不再逼近预算上限。
来源与可信度
- https://github.com/ggml-org/llama.cpp/discussions/20931 — llama.cpp 官方讨论:Qwen3-Thinking 在 max_tokens 偏小时 content 为空、finish_reason=length(预期行为非 bug);Perry Machine 在本地 RTX 3000 实测「2+2」用了 426 token、可见答案仅 “four”,并给出按用途的预算下限(简单 512 / 代码 1024 / 复杂 2048+)
- https://github.com/ollama/ollama/issues/14793 — Ollama 官方 issue:generate 端点忽略 options 里的 think:false、思考烧光 num_predict 出空 response;改用 chat 端点顶层 think=false 后 3 token 正常作答——印证「/v1 与原生端点不对称」
- https://www.lftw.dev/guides/llama-cpp-reasoning-budget/ — llama.cpp --reasoning-budget 机制:它是独立采样器,封顶的是思考而非正文,用完强制收尾再继续答、正文不被截断,未触顶则零成本——支撑「封顶思考」这条修法
- https://slice-code.com/post/the-ollama-http-api-answer-is-empty-and-it-is-one-field-over — 本地 Ollama 实测:qwen3:0.6b 开思考 + num_predict=60,60 token 全烧在 thinking、response 为空、done_reason=length——思考与正文共用 num_predict 预算的直接复现