loading

Loading

探索与发现

首页 AI 开发实战

推理模型输出被截断后怎么接回去

字数: (3139)
阅读: (4)
0
摘要:推理模型遇到输出截断,重试整条请求代价高;续写也有隐藏门槛。从识别边界、提示回灌到 prefill 与成本判断,聊聊工程上怎么做才不算亏。

截断不是异常,是默认结果

推理模型用多了,写应用的人迟早会碰到输出只停在一句话中间的情况。比如调 Qwen3-30B-A3B-Thinking-2507 或 Gemini 系列模型干活,账户收到的大段 Markdown 里,think 块写了一半就断了,或者 JSON 里只剩下 `{"score": 1, "reason": "the answer` 这种状态。多数人第一反应是重试整条请求,代价直接翻倍,而且前后两次推理链路不一样,下游还要合并结果。

这类问题在 2025 到 2026 年变得更普遍,因为模型输出变长了。跑一次扣子 Agent,一个复杂任务可能产生几千个 token 的中间推理。Gemini 2.5 Deep Think 的公开说明显示,订阅 250 美元/月 Ultra 套餐的用户每天调用次数有限,每次推理算力也更贵。这种量级下,整条重试并不像过去那样随便加。

我最近做成本核算的时候,遇到推理连续性出问题,花了些时间梳理截断的几种真实来源。这篇把工程现场的做法整理一下,尤其是恢复截断输出时那些隐藏门槛。

硬件截断、主动截断和响应切断

推理模型被截断,一般不是三种之外的异常:

  • 生成侧到长度上限自动停。模型本身没有结束,进入停止状态只是因为我们把 max_tokens 设成了固定值,托管接口返回的 finish_reason 通常是 length。
  • 服务端主动停。部分厂商追求确定性时会在达到特定步数后终止长输出;也有一些内部安全过滤在中间截断。
  • 传输中断。流式接口因为 TCP 断连、流式连接异常关闭或超时,已经在路上的 output delta 就断了。

这三种情况的恢复方式不同。第一种其实只要补充生成参数。第二种本身就带有指令性质,不能碰运气硬接。第三种才适合做我们常说的“续接”。麻烦在于下游拼接时经常分不清当前是哪一类,工程上要有信息足够澄清。

怎么识别“续接边界”

如果模型内部还在自我推理中,当前 token 序列只是一个中间状态。模型自己当然不知道“上一段的最后半句”是何意,但应用侧可以做到:

  1. 首先管理请求体里的 special_token,决定续接的时候把隐藏性的 think 块从头重排进 status 结构,用一个特定标识 token 围堵旧 prompt。
  2. 把 troncated(以及带在末尾的无边扩展信息块)转成单独的 user prompt 传到下一次请求,显式要求从 current_text 继续。

这些做法真正有效需要接口允许把原来的输出作为 role=assistant 的内容回灌。如果平台把旧推理内容当成助理继续(从会话历史回放中记忆旧部分),那第二次请求能参考一部分断点信息,但它要么重新消耗之前的 prompt token,要么不进入同一个思维块(对 DeepSeek 等内容,思维块和答案文本分开)。

不少国内实现的推理模型,例如上个月公布的 Qwen3 新模型的流式接口,是 Qwen Chat 原生的 thinking case。远端通常提供 segmented thought 和 final message 两项,在断开处会有思维未结束的特征,开发者并不大可能用一条普通对话 prompt 去恢复原来的思考网络。

第二个途径是 prefill:部分接口本身支持 prefll 部分回答的开头(它来自助手信息,作为最后一条参与补全的前缀),这个续接模型生成的结尾效果最好。模型可以重新接回半截思路,需要的 hidden context 放在 input prefix 填充段里。我们没法保证 GPT / Gemini 全部提供这个选项,客户端侧不能死等。但在同一台云服务器上将推理流程放入可控的自托管或轻量验证基础设施的情况下,可以把这个实现得干净。

一个封装补丁:如何记录断点与恢复

更靠谱的办法是维护一个连接管理器,用一个负责纯文本格式的特殊字节重建到主 API 的连接。每次请求末尾检查 finish_reason。如果状态是 length,构造下一次:

role: system -> assistant:
       保持角色一致性(不要重新发表态度、总结、提出问题)
       目标已输出的最后 800 字符 = ...
       目标累计文本长度不少于 4200 字符

除了最后的 800 到 1000 token 携带格式边界,另外分配 attention budget 给实际输入填充信息中的头。“复制最后完整句子结尾的开头几字符”让模型不迷惑。思考模式继续写在 messages 中、标有 thinking 前缀,如果 SDK 有 think blocks 可见的策略,也可以用 marker 合并。

一个真实情况是计费都偏向报应。你续一次就需要为输入负责,用 assistant prefill 时更需要重复传输以前所有 answer tokens,这个成本甚至会占大头,前提是原来的计算仍然留在没有改变的进程里。一般每覆盖 1000 token 的大模型返回,大约是 0.5 到 2 次输入的单位成本。如果整个继续操作多发生 5 次以上,总开销反而不如重试一次完整生成(单并发对话)。经验处理方式大概是:单次续写可以接受;超过 2 次续写,下一轮直接重新打开新对话并分片处理摘要(带已有拼接点的重启比串接拼贴更可靠)。

被忽略的坑

续写以后最大的问题发生在 verifier 和下游 JSON 解析层。模型常以为“自己没写完是因为有点急着下结论”,续接第一段往往同时输出总结和收尾语,但实际代码期望后面还要继续写论据段。更糟的情况是模型续了一小段重复字符后按训练语料重新开头自我介绍或礼貌回答。办法是在第二段用户输入里放“你已经写到的一部分”,同时要求“如果要重新进入主论点就写,先输出一次结束”,而不是靠追加个“continue”。对强上下文连贯的任务可以开两段式,先简短 think 输出确定的 plan 属性,再续续写。这种做法在类似 Gemini 多 Agent parallel thinking 的推理层中已经靠配置支持,客户端很难替换推理引擎内部逻辑,就需要花点工时打磨局部输入模板。

另一个少人明说的风险是 JSON 修复。为了少重试,日志通常允许继续截断 output 里的部分 JSON 再进行 reparative extraction。拼接时点经常错位,比如一条数组对象的尾巴丢了逗号,补回来成了两条连续字符串。大部分现代 SDK 都支持 stream_json 的 error recovery(同时移除不完整的尾块,确保后续 append 的代码写得符合原来的 JSON schema)。如果找不到完整位置,就直接结束而不重建 JSON blob,交给重试逻辑翻更大切片。对需要回答可审计、偏保守训练数据的人来说,更好的权衡是避免报格式错但返回不连贯的结果。

工程形态的结论

这类问题的分工,不适合当作最后的补救,要提前写进 pipeline。一个完整的检测层会做四件事:每次调用记录收尾的 finish_reason、完整的 span 引用、实际的 token 累计量,同时标记格式边界(segment/cancel 状态)和三选一(重试/继续生成/切回归档)。某些场景(例如 Agent 的长期计划,日历类的聊天浏览会话同时附带若干写操作前后逻辑),续接一旦可能生成重复的副作用动作触发写二次调用,运维要明确禁止这个模式,而另选短任务 atomic 处理。过去一年大家主要关心参数怎么调、速度怎么控制。截断恢复本属于“万一”,但在长推理 agent 上线后它每天都在发生。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/ai-dev/620.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图