【Bug已解决】Claude/Sonnet Python API - more tokens freezes, less tokens truncates 解决方案.md
一、现象长什么样
你用 PythonanthropicSDK 调 Claude/Sonnet,观察到这个矛盾(与 939 同源但本篇从"诊断冻结真因"切入):
max_tokens调大后,代码长时间无输出,像冻结(freezes),疑似 hang;- 调小后回答被截断(truncates);
- 你加了超时,冻结变成"超时报错",但不知道到底是模型慢还是配置错;
- 你用
print调试,发现冻结期间 CPU/网络其实有活动,只是没 token 回传; - 你怀疑是 SDK bug,但其实是"在等一个长生成 + 没流式 + 没合理超时"。
一句话:本篇把"冻结"进一步拆穿——它通常不是 SDK 故障,而是:① 未用流式导致同步等待整段响应;② 没有设客户端超时,长生成期间无任何反馈;③ 偶尔真的是请求构造有问题(如max_tokens超大 + 模型被要求生成到上限)。诊断清楚"冻结 vs 真 hang"才能对症下药。
二、背景
max_tokens是生成上限(见 939)。本篇补充两个诊断维度:
- 流式缺失的代价:同步
client.messages.create要等模型完整生成完才返回。若回答本就长(如 2000 token),等待数秒到数十秒,期间没有任何回调,体感冻结。流式则每 token 立即可见。 - 超时缺失的代价:若因网络慢或模型异常迟迟不返回,且没有
timeout配置,进程会无限等——这就是"真 hang"错觉。设了timeout至少能在 N 秒后拿到明确错误,区分"慢"与"死"。
所以诊断清单:流式输出在动 = 在生成(慢但活);流式也无输出且超时才报错 = 真问题(网络/鉴权/请求构造)。
三、根因
根因是同步等待 + 无超时 +max_tokens不当,使"长生成"被误判为"冻结":
# 既无流式、又无超时 -> 长生成期间完全黑屏 client = Anthropic(api_key=KEY) # 默认无 timeout client.messages.create(model="claude-3-5-sonnet-latest", max_tokens=8192, # 上限很高 messages=[{...}]) # 同步等完整响应 -> 冻结感修复方向:开流式 + 设timeout+ 给合理max_tokens,三者缺一不可。
四、最小可运行复现
import os, time from anthropic import Anthropic KEY = os.environ["ANTHROPIC_API_KEY"] def diagnose(max_tokens, use_stream, timeout): client = Anthropic(api_key=KEY, timeout=timeout) # 显式超时 t0 = time.time() if use_stream: # 流式:可见逐字,能判断"在生成" with client.messages.stream(model="claude-3-5-sonnet-latest", max_tokens=max_tokens, messages=[{"role": "user", "content": "写一首关于大海的诗"}]) as s: for _ in s.text_stream: pass print(f"stream 完成, 用时 {time.time()-t0:.1f}s") else: client.messages.create(model="claude-3-5-sonnet-latest", max_tokens=max_tokens, messages=[{"role": "user", "content": "写一首关于大海的诗"}]) print(f"sync 完成, 用时 {time.time()-t0:.1f}s") # diagnose(max_tokens=64, use_stream=False, timeout=30) # 截断且黑屏等 # diagnose(max_tokens=2048, use_stream=True, timeout=60) # 流式可见,不冻结运行流式版本你能看到文字逐渐出现,确认"在生成";同步大max_tokens则等较久才有输出。
五、解决方案(第一层:最小直接修复)
最小修复是流式 + 超时 + 合理上限,三者并用:
import os from anthropic import Anthropic client = Anthropic( api_key=os.environ["ANTHROPIC_API_KEY"], timeout=60, # 关键:避免无限等待(区分慢与死) ) # 合理上限(诗约 300 字 -> ~200 token,给 512 余量,不必 8192) with client.messages.stream( model="claude-3-5-sonnet-latest", max_tokens=512, messages=[{"role": "user", "content": "写一首关于大海的诗"}], ) as stream: for text in stream.text_stream: print(text, end="", flush=True)这样:超时能在 60s 后给出明确错误(不再是无限冻结);流式让生成过程可见;合理max_tokens既不截断也不逼迫模型生成到上限。
六、解决方案(第二层:结构化改进)
把"流式 + 超时 + 上限"做成诊断策略,集中管理并能在冻结时自动判定:
from dataclasses import dataclass, field from typing import Callable, Optional @dataclass(frozen=True) class ClaudeMaxTokensFreezeV2Policy: """冻结诊断策略:流式 + 超时 + 上限,区分慢与死。 规则: - 默认开启流式(可见生成) - 设默认超时(避免无限冻结) - max_tokens 按任务估算,不过分大 """ default_timeout: float = 60.0 default_max_tokens: int = 1024 def build_client_kwargs(self) -> dict: return {"timeout": self.default_timeout} def classify(self, *, streamed: bool, elapsed: float, timeout: float) -> str: if not streamed and elapsed >= timeout: return "可能真 hang:同步且无流式且超时被触发" if streamed and elapsed < timeout: return "正常:流式可见,模型在生成" return "观察中" def demo() -> None: policy = ClaudeMaxTokensFreezeV2Policy() print("client kwargs:", policy.build_client_kwargs()) print(policy.classify(streamed=True, elapsed=3.0, timeout=60)) print(policy.classify(streamed=False, elapsed=61, timeout=60)) if __name__ == "__main__": demo()七、解决方案(第三层:断言 / CI 守护)
import pytest from your_module import ClaudeMaxTokensFreezeV2Policy def test_client_kwargs_has_timeout(): policy = ClaudeMaxTokensFreezeV2Policy() assert policy.build_client_kwargs()["timeout"] == 60.0 def test_classify_streaming_ok(): policy = ClaudeMaxTokensFreezeV2Policy() assert "正常" in policy.classify(streamed=True, elapsed=3, timeout=60) def test_classify_hang(): policy = ClaudeMaxTokensFreezeV2Policy() assert "hang" in policy.classify(streamed=False, elapsed=61, timeout=60) def test_default_max_tokens_reasonable(): policy = ClaudeMaxTokensFreezeV2Policy() assert 0 < policy.default_max_tokens <= 4096 def test_classify_observing(): policy = ClaudeMaxTokensFreezeV2Policy() assert policy.classify(streamed=True, elapsed=61, timeout=60) == "观察中" def test_timeout_configurable(): policy = ClaudeMaxTokensFreezeV2Policy(default_timeout=30) assert policy.build_client_kwargs()["timeout"] == 30.0CI 里加一条:对长回答任务断言默认开启流式、设有超时、max_tokens 合理,避免"冻结/截断"回归。
八、排查清单
- 是否开了流式?没流式同步等整段,体感必冻结。
- 是否设了
timeout?没超时则无法区分"慢"与"真死"。 - 冻结时流式有输出吗?有=在生成(慢),无+超时错=真问题。
max_tokens是否过大?逼迫模型生成到上限会拖长。- 是否把"超时报错"当 bug?它其实在帮你定位(网络/鉴权/请求构造)。
- 是否用
print/日志确认生成在动?先诊断再改配置。
九、小结
Claude/Sonnet Python API"max_tokens 大冻结、小截断",本篇从诊断角度拆穿:冻结几乎都是"未流式 + 无超时 + 上限不当"导致的长生成等待,而非 SDK 故障。最小修复是流式 + 显式timeout+ 合理max_tokens三者并用;结构化做法是抽成ClaudeMaxTokensFreezeV2Policy,集中管理并提供"慢 vs 死"的分类诊断;最后用 pytest 守护"默认流式、有超时、上限合理",让冻结现象可诊断、可消除。