调用大模型API时,一个经常被忽视却又影响巨大的参数就是Max Tokens。不少开发者把它当成一个随便填的数字,结果模型好不容易推理到关键一步,输出却戛然而止,finish_reason返回length,用户看到的是半句话甚至半个JSON。这篇文章就来系统梳理Max Tokens的常见陷阱,并给出避免推理链被截断的工程实践。

一、先搞清楚:Max Tokens到底限制的是什么
很多开发者对Max Tokens存在一个根本性的误解:以为它限制的是输入加输出的总量。实际上,在大多数主流API(如OpenAI、Anthropic、DeepSeek等)中,Max Tokens只限制生成的Token数量,也就是模型这一轮要写出来的内容长度,输入部分的Token是单独计算的,受模型上下文窗口总长度约束。
这里有一个容易被忽略的细节:随着推理模型(如o1、DeepSeek-R1、Claude的thinking模式)的流行,模型的思考过程本身也会消耗输出Token。也就是说,如果你设置了Max Tokens等于4096,模型可能花了3500个Token在内部推理链上,真正呈现给用户的答案只剩下不到600个Token,答案自然容易被截断。这是目前推理链被截断最常见的原因,没有之一。
另外一个坑是Token不等于字符。中文大约1个汉字对应1到2个Token,英文单词可能是1个甚至半个Token。如果你按照字符数去估算Token上限,误差可能达到一倍以上。正确的做法是调用API之前,先用对应的分词器或者count_tokens类的接口精确计算。
二、推理链被截断的四种典型场景
场景一:思考型模型的隐藏消耗。开了推理模式后,模型的reasoning内容虽然可能不在最终响应中展示,但它确实占用输出配额。以DeepSeek-R1为例,复杂问题的推理链动辄几千Token,如果Max Tokens设得和普通对话一样,截断几乎是必然的。判断方法很简单:查看响应中的reasoning字段是否完整,以及finish_reason是否为length。
response = client.chat.completions.create(
model="deepseek-reasoner",
messages=[{"role": "user", "content": "证明根号2是无理数"}],
max_tokens=2048 # 推理链就可能吃掉1800,答案必然被截断
)
print(response.choices[0].finish_reason) # 输出 length 表示被截断
场景二:长结构化输出超限。要求模型生成大段JSON、长报告或者批量代码时,内容长度往往难以预估。一个生成100条商品描述的任务,实际输出很容易突破8000 Token,固定的小上限会导致JSON在中间断裂,下游解析直接报错。
场景三:系统提示词与上下文挤占。虽然Max Tokens不直接约束输入,但输入加上输出不能超过模型的上下文窗口。如果你的系统提示词和历史消息已经占用了大部分窗口,留给输出的空间就非常有限,即使Max Tokens设置了很大的值,实际可用额度也会被压缩。
场景四:流式输出下的误判。流式模式下,客户端收到一堆chunk后就拼接展示,开发者往往没意识到末尾的finish_reason是length,用户还以为是网络卡了。务必在流结束时检查最后一个chunk的状态字段。
三、避免截断的最佳实践
第一,根据模型类型分层设置上限。普通对话模型可以设置较为宽松的上限,而推理模型要预留出思考Token的空间。Anthropic的做法值得参考:其thinking模式允许单独指定thinking的预算,让开发者显式规划思考与回答的Token分配。如果使用的模型不支持分离,经验值是给推理模型设置普通模型3到5倍的输出上限。
# 推理模型:给思考过程留足空间
response = client.chat.completions.create(
model="deepseek-reasoner",
messages=messages,
max_tokens=16384,
stream=True
)
for chunk in response:
if chunk.choices[0].finish_reason == "length":
# 触发兜底逻辑:继续请求或提示用户精简问题
handle_truncation(chunk)
第二,把finish_reason检查做成标配。无论同步还是流式调用,都应该在代码里显式判断结束原因。常见的取值有stop(正常结束)、length(达到Token上限)、content_filter(被安全策略拦截)等。一旦检测到length,可以自动发起续写请求,把已生成内容拼接后让模型继续,这在长文生成场景非常实用。
第三,控制输出长度的根本手段是约束输入。与其事后补救,不如在提示词中明确要求输出格式,比如限定要点数量、要求分条简答、限制代码只给出核心函数。同时精简系统提示词,把长上下文做摘要压缩,给输出留出足够的窗口空间。任务太大就拆分,比如把100条商品描述拆成每批10条,循环调用,每批的输出长度都可控。
第四,建立Token监控体系。记录每次请求的usage字段,统计输入Token、输出Token和思考Token的分布,绘制趋势图。当发现输出经常逼近Max Tokens时,说明上限设置不合理或者任务粒度过大,需要及时调整。这个监控数据还能帮助你优化成本,因为输出Token的单价通常是输入的好几倍。
四、被截断之后的补救措施
即使做了充分预防,截断仍可能发生,这时候需要有兜底方案。最通用的是续写机制:检测到finish_reason为length后,把已生成的内容追加到messages中,用一条类似请从中断处继续的指令发起第二次请求,再把两段结果拼接。注意续写时要保持上下文完整,否则模型可能重复或跑偏。
def generate_with_continue(client, messages, max_rounds=3):
full_text = ""
for i in range(max_rounds):
resp = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
max_tokens=4096
)
choice = resp.choices[0]
full_text += choice.message.content
if choice.finish_reason == "stop":
break
# 被截断,把已生成内容回填后继续
messages = messages + [
{"role": "assistant", "content": full_text},
{"role": "user", "content": "继续,从中断处接着输出,不要重复"}
]
return full_text
对于JSON输出的场景,还可以在客户端做容错解析:截断的JSON可以用补全右括号的方式尝试修复,或者在提示词中要求模型先输出总条数,客户端据此校验完整性。更稳妥的方案是改用结构化输出功能,让API保证返回合法JSON,再配合足够的Token上限。
最后提醒一点:不要为了保险把Max Tokens无脑拉到模型允许的最大值。上限越大,异常情况下的成本失控风险越高,而且部分模型对最大输出有硬性限制,超出会直接报错。合理的策略是监控加动态调整:根据实际业务输出的分布设定上限,留出百分之三十左右的余量,配合finish_reason检测和续写兜底,就能在体验、成本和稳定性之间找到平衡点。
Max Tokens推理链截断大模型输出优化修改时间:2026-08-31 18:59:08