【Bug已解决】Claude/Sonnet Python API - more tokens freezes, less tokens truncates 解决方案
一、现象长什么样
你用 Python(anthropicSDK)调 Claude/Sonnet,发现一个矛盾现象:
- 把
max_tokens设得很大(如 4096、8192),请求会"卡住/冻结(freezes)"——长时间没响应,像 hang 住; - 把
max_tokens设得很小(如 64),回答被截断(truncates),内容不完整; - 你不确定该设多大;
- 冻结时程序既不报错也不返回,只能等或超时;
- 截断时输出到一半就没了,没有明显的"被截断"提示。
一句话:max_tokens是"模型单次最多生成的 token 数"——设太小,模型没说完就被强行停下(截断);设太大且未用流式时,客户端会一直等模型把额度用满或自然结束,长生成期间表现为"冻结",若再加网络/无超时配置,就像 hang 住。
二、背景
max_tokens不是"我要多少就给多少",而是"上限":模型生成到这个上限或自然结束(遇到 stop)就停。两种极端:
- 太小:模型内容还没表达完,额度耗尽被截断。Sonnet 回答本就可能较长,64/128 必然截断;
- 太大 + 非流式:模型可能真的生成很多 token 才自然结束(尤其开放性提问),客户端同步等待整个响应,期间没有任何中间输出,体感就是"冻结"。如果同时没设请求超时,且模型恰好生成很久,看起来就像程序卡死。
注意:冻结通常不是"bug",而是"你在等一个长生成"。用流式(stream)就能看到逐字输出,立刻知道还在跑,不会误以为卡住。
三、根因
根因是对max_tokens语义理解偏差 + 未用流式导致长生成期间无反馈:
# 错误 1:太小 -> 截断 client.messages.create(model="claude-3-5-sonnet-latest", max_tokens=64, messages=...) # 错误 2:太大 + 同步等 -> 冻结感 client.messages.create(model="claude-3-5-sonnet-latest", max_tokens=8192, messages=...) # 模型可能生成几千 token 才停,同步客户端一直等 -> 像 freeze修复方向:设一个"合理偏宽松但不过分"的max_tokens(如 1024~2048),并用流式让生成过程可见、可控。
四、最小可运行复现
import os from anthropic import Anthropic def call_sync(max_tokens: int): client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) # 同步等待完整响应:max_tokens 大时体感冻结 return client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=max_tokens, messages=[{"role": "user", "content": "写一篇关于秋天的 500 字散文"}], ) def call_stream(max_tokens: int): client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) # 流式:逐块可见,不会误以为冻结 with client.messages.stream(model="claude-3-5-sonnet-latest", max_tokens=max_tokens, messages=[{"role": "user", "content": "写一篇关于秋天的散文"}]) as stream: for text in stream.text_stream: print(text, end="", flush=True) # 实时输出 print() if __name__ == "__main__": # call_sync(64) # 截断 # call_stream(2048) # 流式、可见、不冻结 pass运行 streaming 版本,你能实时看到文字出现,确认"没卡住";同步大max_tokens则会等较久才有输出,体感冻结。
五、解决方案(第一层:最小直接修复)
最小修复是设合理max_tokens+ 用流式:
import os from anthropic import Anthropic client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) # 1) 合理上限:给足但不夸张(Sonnet 一般 1024~4096 够用) MAX = 2048 # 2) 用流式,生成过程实时可见 with client.messages.stream( model="claude-3-5-sonnet-latest", max_tokens=MAX, messages=[{"role": "user", "content": "详细解释一下 X"}], ) as stream: for text in stream.text_stream: print(text, end="", flush=True)要点:
max_tokens设成"你预期最长回答的 token 数 + 余量"(中文约 1.5 字/token,500 字约 750 token);- 永远用流式处理长回答,避免"等完整响应"的冻结感;
- 若必须同步,配合合理
max_tokens与客户端超时。
六、解决方案(第二层:结构化改进)
把"max_tokens取值 + 流式开关"做成策略,按任务类型自动选:
from dataclasses import dataclass, field from typing import Dict, Callable @dataclass(frozen=True) class ClaudeMaxTokensFreezePolicy: """max_tokens 策略:避免截断与冻结。 规则: - 按任务类型给默认 max_tokens(短文/长文/代码) - 长任务默认开启流式 - 提供 '估算所需 token' 防止过小 """ presets: Dict[str, int] = field(default_factory=lambda: { "chat": 1024, "long_text": 2048, "code": 4096, }) def pick(self, task: str) -> int: return self.presets.get(task, 1024) def estimate(self, chars: int) -> int: # 中文约 1.5 字/token,留 1.3 倍余量 return int(chars / 1.5 * 1.3) def should_stream(self, task: str) -> bool: return self.pick(task) >= 2048 def demo() -> None: policy = ClaudeMaxTokensFreezePolicy() print("长文 max_tokens:", policy.pick("long_text")) # 2048 print("估算 500 字需要:", policy.estimate(500)) # ~433 print("是否流式:", policy.should_stream("long_text")) # True if __name__ == "__main__": demo()七、解决方案(第三层:断言 / CI 守护)
import pytest from your_module import ClaudeMaxTokensFreezePolicy def test_preset_exists(): policy = ClaudeMaxTokensFreezePolicy() assert policy.pick("chat") == 1024 assert policy.pick("code") == 4096 def test_unknown_task_default(): policy = ClaudeMaxTokensFreezePolicy() assert policy.pick("weird") == 1024 def test_estimate_no_truncate(): policy = ClaudeMaxTokensFreezePolicy() # 500 字约需 433 token,给的估算应 >= 实际,避免截断 assert policy.estimate(500) >= 400 def test_stream_for_long(): policy = ClaudeMaxTokensFreezePolicy() assert policy.should_stream("long_text") is True def test_no_stream_for_chat(): policy = ClaudeMaxTokensFreezePolicy() assert policy.should_stream("chat") is False def test_estimate_positive(): policy = ClaudeMaxTokensFreezePolicy() assert policy.estimate(100) > 0CI 里加一条:对所有"长回答"任务断言默认开启流式、max_tokens 不低于估算值,避免截断/冻结回归。
八、排查清单
max_tokens是否设得太小(如 64/128)?那必然截断,调大到任务所需。- 是否用流式?大
max_tokens同步等会体感冻结,流式可实时看到。 - 是否合理估算回答长度?中文约 1.5 字/token,给 1.3 倍余量。
- 是否设置了客户端超时?避免"真卡死"无兜底。
- 冻结是"长生成中"还是"真 hang"?流式能区分(有逐字输出=在跑)。
- 是否把
max_tokens当成"我要多少给多少"?它是上限,不是目标值。
九、小结
Claude/Sonnet Python API"max_tokens 大了冻结、小了截断",根因是对max_tokens语义理解偏差——它是生成上限,太小被截断、太大且同步等待时长生成体感冻结。最小修复是按任务类型设合理max_tokens(如长文 2048)并改用流式,让生成过程实时可见;结构化做法是抽成ClaudeMaxTokensFreezePolicy,按任务预设上限、估算所需 token、长任务默认流式;最后用 pytest 守护"长任务流式开启、max_tokens 不低于估算",杜绝截断与冻结。