对接大模型 API 时,你是否也遇到过这种诡异现象:返回结果半截就断了,JSON 残缺、段落缺失,
finish_reason还时而length时而stop?很多人只知道max_tokens会截断,却忽略了 stop sequence 停止词 以及 推理模型特殊的 token 抢占问题。本文梳理大模型输出突然中断的 3 类真实场景,讲清原理、坑点与代码处理思路。
前言
在对接大模型 API 的过程中,"输出被截断" 是一个非常高频但极易被误判的问题。多数开发者第一反应是 max_tokens 设置太小,但实际上,导致输出中断的原因至少有三类:
- Stop Sequence(停止词)命中
- Max Tokens 达到上限
- 推理模型推理过程抢占 Token 预算
三者现象相似、根因不同,排查方式也完全不同。下面逐一拆解。
📌 前提认知:默认情况下大模型 没有配置 stop sequence,输出由模型自身判断 EOS 自然结束。
一、Stop Sequence(停止词):看似正常,实则被强制截断
Stop sequence 是开发者传入的一组字符串列表,语义非常明确:
一旦生成内容命中该字符串,立刻终止生成。
1.1 情况一:不设置 stop(绝大多数业务场景)
模型自由生成,自我判断内容完成,输出 EOS 结束标记后自然停止。
此时无论输出多少换行、空行,都不会触发中断,停止逻辑完全由模型自身控制。
类比:交给写手写文章,写手觉得写完了就主动停笔。
1.2 情况二:显式配置 stop = "\\n\\n"
只要生成流中遇到 \n\n 双换行,强制立刻终止后续所有内容。
类比:告诉写手,只要看到句号就停下,哪怕句子还没写完。
1.3 坑点:隐蔽的误截断
业务中极容易踩坑:文本内容内部本身就包含停止词字符串。
例如模型输出一个地址:
北京市朝阳区
某某路
文本内部自带双换行,恰好命中 stop = ["\n\n"] 规则,直接截断,地址残缺。
此时 API 返回:
{
"finish_reason": "stop"
}
从返回字段看属于正常结束,但业务内容已经被拦腰截断。这种问题极难排查,因为日志上看不出任何异常。
1.4 Stop Sequence 适合哪些场景
仅适合下面窄场景,普通问答、写文章、通用业务接口 不要配置 stop:
| 场景 | 说明 |
|---|---|
| 单词/单句输出 | 禁止模型额外解释 |
| 多轮对话 | 防止模型模拟用户发言 |
| 结构化输出 | 需要严格切割输出边界 |
二、Max Tokens:达到最大长度上限被截断
每个大模型请求都存在 max_tokens 最大输出 token 配额。当生成 token 数量耗尽,不管当前语句、JSON 是否完整,直接强行切断输出。
2.1 现象示例:请求返回残缺 JSON
{
"name": "张三",
"age": 25,
"address": "北京市朝阳区某某路某某号",
"hobbies":
["读书", "跑步", "编
API 返回:
{
"finish_reason": "length"
}
明确告知:输出因为长度上限被截断。
2.2 正确代码处理思路
拿到响应后 优先判断结束原因 ,length 代表内容不完整,不能直接解析使用:
if ("length".equals(response.getFinishReason())) {
// 输出被截断,结果不可信
// 方案:调高 max_tokens 重试 / 标记本次调用失败
throw new TruncatedOutputException("大模型输出token达到上限,内容截断");
}
2.3 错误写法
不校验 finish_reason,直接对残缺字符串执行 JSON.parse():
// ❌ 危险写法
Object obj = JSON.parseObject(response.getContent()); // 直接抛解析异常
✅ 该问题特征明显,残缺内容肉眼可识别,相比 stop 误截断 更容易发现。
三、推理模型特殊坑:推理过程抢占 Token 预算
OpenAI o1 / o3 这类推理型模型,输出分为两部分:
- thinking 推理过程 :内部思考推演,用户不可见,但消耗 token
- content 最终回答:对外返回给业务方的内容
⚠️ 关键点:推理内容 + 最终回答 共用同一套 max_tokens 预算。
3.1 场景对比
设置 max_tokens = 5000:
| 场景 | 推理消耗 | 回答可用 | 结果 |
|---|---|---|---|
| A | 1000 token | 4000 token | 输出完整 ✅ |
| B | 4500 token | 500 token | 回答被截断 ❌ |
此时 API 返回依旧是:
{
"finish_reason": "length"
}
3.2 排查要点
排查时 不能只看最终回答文本长短 ,必须同时关注 推理过程占用的 token 消耗。
如果仅凭回答内容判断,容易误以为 "回答才几百 token 怎么就截断了",从而错过真正原因。
四、汇总对比表
| 截断场景 | finish_reason | 风险等级 | 现象说明 |
|---|---|---|---|
| Max Tokens 达到上限 | length |
中等 | 内容明显残缺,容易发现 |
| Stop 停止词命中 | stop |
⭐ 高风险 | 返回标记显示正常,业务内容悄悄残缺,隐蔽难查 |
| 推理模型 token 预算耗尽 | length |
中等 | 回答被截断,根源是推理思考占用大量配额 |
🎯 核心结论:不能默认 API 返回的内容一定完整,三种场景都要在代码层面做判断处理。
五、开发最佳实践总结
- 慎用 stop sequence
绝大多数业务场景,不要自定义stop sequence,交给模型自然 EOS 结束;只有需要严格切割输出边界时才启用停止词。 - 必须校验 finish_reason
每次 API 响应必须校验finish_reason,length代表内容残缺,禁止直接解析 JSON、执行业务逻辑。 - 监控推理 token 消耗
使用推理类模型,监控thinking推理 token 消耗,预留足够 token 给最终输出。 - 警惕停止词误命中
stop 模式下,警惕业务文本本身包含停止词字符串,避免发生看不见的截断 bug。
六、健壮响应处理模板
public String safeInvoke(LLMRequest request) {
LLMResponse response = llmClient.chat(request);
String finishReason = response.getFinishReason();
// 1. stop 命中:可能被自定义停止词误截断,需要业务侧再校验
if ("stop".equals(finishReason)) {
if (hasCustomStop(request)) {
log.warn("检测到自定义 stop 命中,请确认业务内容是否完整");
}
return response.getContent();
}
// 2. length 命中:内容残缺,不可直接使用
if ("length".equals(finishReason)) {
// 推理模型场景:打印 thinking token 消耗,辅助定位
if (response.getUsage() != null) {
log.warn("输出被截断,reasoning_tokens={}, completion_tokens={}",
response.getUsage().getReasoningTokens(),
response.getUsage().getCompletionTokens());
}
throw new TruncatedOutputException("大模型输出被截断,请调高 max_tokens 或重试");
}
// 3. 其他原因:按业务兜底
return response.getContent();
}
结语
大模型 API 的 "截断" 问题,本质上是 输出边界控制权在谁手里 的问题:
- 交给模型 → 自然 EOS,最安全
- 交给 stop sequence → 人为切割,易误伤
- 交给 max_tokens → 硬性上限,必然残缺
- 交给推理模型 → 预算共享,回答被推理挤占
永远不要假设 API 返回的内容是完整的 。做好 finish_reason 校验 + 业务侧兜底,才是稳定的工程实践。