response-truncation-recovery · ZH · 2026-10-25

max_tokens 截在 JSON 中间:一把 key 上怎么识别截断并把输出接着补完

本文介绍如何通过 finish_reason 识别 max_tokens 导致的截断,并给出在单一 API key 下续写不完整 JSON 输出的实操方法,避免整段重生成,节省 token 与时间。

为什么需要关注截断

调用大模型 API 生成结构化输出(如 JSON)时,若输出超过 maxtokens,响应会在中途被截断,导致 JSON 不完整。不同后端(Claude、GPT、DeepSeek、Qwen、GLM、Kimi)在截断时的 finishreason 字段可能不同,但核心思路一致:先识别截断,再续写补全,而非重新生成整个响应。

识别截断:检查 finish_reason

主流后端的响应中,choices[0].finish_reason(或类似字段)会标明停止原因:

  • length:达到 max_tokens 限制,输出被截断。
  • stop:遇到自然停止符或完成。
  • 其他值(如 content_filter)表示内容过滤。

在聚合站中,不同模型返回的字段名可能略有差异,但通常兼容 OpenAI 格式。建议在代码中统一检查 finishreason == 'length' 或对应后端的等价标识(如 Claude 的 stopreason: 'max_tokens')。若使用多模型,可封装一个函数适配各后端的字段。

注意:仅当 finish_reason 为 length 时,才需要续写;否则可能已完成或遇到其他停止条件。

续写策略:从截断处继续生成

一旦确认截断,不应重新发送完整请求,而是利用已有部分输出,让模型接着生成剩余内容。常见方法有两种:

方法一:追加提示词续写

将已生成的不完整输出作为上下文,附加指令要求模型补全。例如:

{"messages": [
  {"role": "user", "content": "生成一个包含10个字段的JSON对象。"},
  {"role": "assistant", "content": "{\"field1\": \"value1\", \"field2\": ..."},
  {"role": "user", "content": "上面的JSON被截断了,请从截断处继续补全,不要重复已有内容。"}
]}

此方法简单,但需确保模型理解“继续”而非“重写”。可在系统提示中强调“输出应无缝衔接”。

方法二:利用函数调用或结构化输出模式

部分后端支持 JSON 模式或函数调用,可要求模型以流式方式输出,并在截断后从最后一个完整 token 处恢复。但需注意,流式响应中 finish_reason 同样适用。

实操步骤

  1. 首次请求:设置合理的 max_tokens(根据任务预估),获取响应。
  2. 检查 finish_reason:若为 length,提取已生成的文本(choices[0].message.content)。
  3. 构造续写请求:将已生成文本作为 assistant 消息,附加用户指令要求补全。可适当增加 max_tokens 以容纳剩余部分。
  4. 合并输出:将首次输出与续写输出拼接,注意处理边界(如避免重复开头字符)。
  5. 验证完整性:尝试解析 JSON,若仍不完整可重复步骤 3-4,直至 finish_reason 为 stop 或解析成功。

注意事项

  • 避免无限续写:设置最大续写次数,防止模型陷入循环。
  • 处理边界:续写时可能重复已输出的末尾字符,可在拼接前做简单去重。
  • 模型差异:不同模型对“继续”指令的响应可能不同,建议在系统提示中明确要求“只输出缺失部分”。
  • 成本控制:续写仍消耗 token,但相比整段重生成更经济。聚合站按官方价 ×1.3 扣费,合理使用续写可降低开销。

总结

通过检查 finishreason 识别截断,并采用续写策略补全 JSON,能有效避免整段重生成,提升效率并节省 token。在单一 API key 下调用多模型时,封装适配层统一处理 finishreason 字段,即可实现跨后端的截断续写。