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 同样适用。
实操步骤
- 首次请求:设置合理的
max_tokens(根据任务预估),获取响应。 - 检查 finish_reason:若为 length,提取已生成的文本(
choices[0].message.content)。 - 构造续写请求:将已生成文本作为 assistant 消息,附加用户指令要求补全。可适当增加
max_tokens以容纳剩余部分。 - 合并输出:将首次输出与续写输出拼接,注意处理边界(如避免重复开头字符)。
- 验证完整性:尝试解析 JSON,若仍不完整可重复步骤 3-4,直至 finish_reason 为 stop 或解析成功。
注意事项
- 避免无限续写:设置最大续写次数,防止模型陷入循环。
- 处理边界:续写时可能重复已输出的末尾字符,可在拼接前做简单去重。
- 模型差异:不同模型对“继续”指令的响应可能不同,建议在系统提示中明确要求“只输出缺失部分”。
- 成本控制:续写仍消耗 token,但相比整段重生成更经济。聚合站按官方价 ×1.3 扣费,合理使用续写可降低开销。
总结
通过检查 finishreason 识别截断,并采用续写策略补全 JSON,能有效避免整段重生成,提升效率并节省 token。在单一 API key 下调用多模型时,封装适配层统一处理 finishreason 字段,即可实现跨后端的截断续写。