📌 内容摘要
- max_tokens 是 Claude API 唯一必填的生成参数——不传直接报错,这和 OpenAI 不同,是迁移时最高频的坑。
- 设太小导致输出截断、设太大浪费成本,本文给出 8 类任务的推荐区间和选值逻辑。
- 如何判断输出是被截断还是自然结束?stop_reason 字段是唯一可靠的判断依据。
- 附动态 max_tokens 计算策略:根据输入长度自动调整,在上下文窗口限制内最大化输出空间。
一、max_tokens 是什么,为什么 Claude 必须传它?
max_tokens 控制 Claude 在这次请求中最多生成多少个 token。它是一个”安全上限”而不是”期望长度”——设成 4096 不代表 Claude 一定会生成 4096 个 token,它会在认为回答完整时自然停止;但如果正常回答需要 5000 token 而你只设了 4096,就会在中途被硬性截断。
Claude API 把 max_tokens 设计为必填参数,没有默认值。这和 OpenAI 的 API 不同——OpenAI 不传 max_tokens 会用一个内部默认值。从 OpenAI 迁移到 Claude 时,漏掉这个参数是最常见的报错原因之一:
# 漏掉 max_tokens 的报错
anthropic.BadRequestError: Error code: 400
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens: field required"
}
}
二、Token 基础:一个 token 等于多少字?
在设 max_tokens 之前,先理解 token 的实际大小——不同语言差异很大:
| 内容类型 | Token / 字符比例 | 1000 token 约等于 |
|---|---|---|
| 英文文本 | 约 1 token / 4 字符 | 约 750 个英文单词 |
| 中文文本 | 约 1.5–2 token / 汉字 | 约 500–650 个汉字 |
| Python 代码 | 约 1 token / 3–4 字符 | 约 40–60 行代码 |
| JSON / 结构化数据 | 约 1 token / 3 字符 | 取决于嵌套深度和键名长度 |
一个实用的心智模型:中文输出每 1000 字约需 1500–2000 token,英文输出每 1000 词约需 1300 token。
三、stop_reason:判断输出是否被截断的唯一方法
很多开发者设完 max_tokens 就不管了,其实应该每次都检查 stop_reason 字段:
| stop_reason 值 | 含义 | 处理建议 |
|---|---|---|
end_turn |
Claude 自然结束,输出完整 | 正常,直接使用 |
max_tokens |
达到 max_tokens 上限,输出被硬截断 | ⚠️ 需要处理截断! |
stop_sequence |
命中了自定义停止序列 | 符合预期,检查停止序列是否正确 |
tool_use |
Claude 需要调用工具 | 执行工具后继续对话 |
import anthropic
client = anthropic.Anthropic()
def safe_complete(
messages: list[dict],
system: str = "",
max_tokens: int = 1024,
model: str = "claude-sonnet-4-6",
) -> dict:
"""
带截断检测的完整调用封装
返回包含内容、停止原因和是否截断的字典
"""
response = client.messages.create(
model = model,
max_tokens = max_tokens,
system = system,
messages = messages,
)
content = response.content[0].text
stop_reason = response.stop_reason
truncated = stop_reason == "max_tokens"
if truncated:
# 生产环境这里应该记录日志、告警或触发续写逻辑
print(f"⚠️ 输出被截断!已生成 {response.usage.output_tokens} tokens,"
f"请考虑增大 max_tokens(当前设置:{max_tokens})")
return {
"content": content,
"stop_reason": stop_reason,
"truncated": truncated,
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens,
}
# 使用示例
result = safe_complete(
messages = [{"role": "user", "content": "写一篇2000字的文章"}],
max_tokens = 500, # 故意设太小,演示截断
)
if result["truncated"]:
print("输出不完整,建议增大 max_tokens 或启用续写")
print(f"内容:{result['content'][:100]}...")
print(f"停止原因:{result['stop_reason']}")
四、截断后的续写策略
当 stop_reason == "max_tokens" 时,输出被强制截断了。处理方式取决于业务场景:
async def complete_with_continuation(
messages: list[dict],
system: str = "",
max_tokens: int = 2048,
max_continuations: int = 3,
) -> str:
"""
自动续写:输出截断时自动继续生成
最多续写 max_continuations 次
适用场景:
- 长文章生成
- 代码文件生成
- 报告撰写
"""
full_content = []
current_msgs = list(messages)
for attempt in range(max_continuations + 1):
response = client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = max_tokens,
system = system,
messages = current_msgs,
)
chunk = response.content[0].text
full_content.append(chunk)
if response.stop_reason != "max_tokens":
# 自然结束,不需要继续
break
if attempt < max_continuations:
# 把已生成内容加入对话历史,让 Claude 继续
current_msgs.append({"role": "assistant", "content": chunk})
current_msgs.append({
"role": "user",
"content": "请继续,从你停下的地方接着写,不要重复已有内容。"
})
else:
print(f"已达到最大续写次数({max_continuations}),停止")
return "".join(full_content)
# 测试续写
content = await complete_with_continuation(
messages=[{"role": "user", "content": "写一篇详细的Python异步编程教程"}],
max_tokens=1024, # 每次最多生成 1024 token
max_continuations=3, # 最多续写 3 次,共约 4096 token
)
print(f"总字数:{len(content)}")
五、上下文窗口与 max_tokens 的关系
max_tokens 不是孤立的参数,它受限于模型的上下文窗口大小:
# 上下文窗口 = 输入 token + 输出 token
# max_tokens 不能超过:上下文窗口 - 实际输入 token 数
MODEL_CONTEXT_WINDOWS = {
"claude-haiku-4-5-20251001": 200_000,
"claude-sonnet-4-6": 1_000_000,
"claude-opus-4-6": 1_000_000,
}
def calculate_safe_max_tokens(
messages: list[dict],
system: str = "",
model: str = "claude-sonnet-4-6",
safety_buffer:int = 100, # 安全边距,防止边界计算偏差
) -> int:
"""
根据输入内容自动计算安全的 max_tokens 上限
避免因为 input_tokens + max_tokens > context_window 导致的报错
"""
# 精确计算输入 token 数(使用 count_tokens API)
count_response = client.messages.count_tokens(
model = model,
system = system,
messages = messages,
)
input_tokens = count_response.input_tokens
context_window = MODEL_CONTEXT_WINDOWS.get(model, 200_000)
available = context_window - input_tokens - safety_buffer
if available <= 0:
raise ValueError(
f"输入已经用了 {input_tokens} tokens,"
f"超过上下文窗口 {context_window},无法生成输出"
)
return available
# 动态计算示例
long_messages = [
{"role": "user", "content": "非常长的文档内容..." * 1000}
]
safe_max = calculate_safe_max_tokens(long_messages, model="claude-sonnet-4-6")
print(f"安全的 max_tokens 上限:{safe_max:,}")
response = client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = min(safe_max, 4096), # 取安全上限和业务需求的较小值
messages = long_messages,
)
当你传入一个非常长的文档(比如10万token),再设 max_tokens=100000,两者相加可能超过模型的上下文窗口,Claude 会返回 400 错误。用
count_tokens API 提前计算、动态设置 max_tokens 是最稳健的方案。六、8类任务的推荐 max_tokens 值
| 任务类型 | 推荐值 | 选值逻辑 |
|---|---|---|
| 分类 / 情感分析 | 16 – 64 | 输出只是一个词或短句,64 已经绰绰有余 |
| 结构化提取(JSON) | 256 – 1024 | 取决于 schema 的字段数量和值的长度 |
| 简短问答 / FAQ | 256 – 512 | 一般问答不超过300字,512 有足够余量 |
| 对话聊天机器人 | 512 – 1024 | 每轮回复不宜过长,防止输出冗长 |
| 文档摘要 | 512 – 2048 | 摘要通常是原文的 10–20%,按需调整 |
| 代码生成(函数级) | 1024 – 4096 | 一个功能完整的函数通常在 50–200 行 |
| 长文章 / 报告撰写 | 4096 – 8192 | 超过 8192 建议启用续写机制分段生成 |
| 复杂推理 / 分析报告 | 4096 – 16384 | 思维链推理过程长,需要较大空间展开 |
七、成本视角:max_tokens 设太大会多花钱吗?
这是一个常见误解,需要澄清:
"""
成本计算的关键:你只为实际生成的 token 付费
不是为 max_tokens 付费
例子:
- 设 max_tokens=8192,Claude 自然回答了 500 tokens
- 实际计费:500 output tokens(不是 8192)
所以从成本角度,把 max_tokens 设大一点是安全的。
但有两个间接影响需要注意:
1. max_tokens 太大但输出真的很长 → 成本确实增加(这是因为内容多,不是参数大)
2. max_tokens 影响 Claude 的"生成倾向":
- 设得很小(如64):Claude 会更倾向于简洁、提前结束
- 设得很大(如8192):Claude 更倾向于详细展开
这不是成本问题,但会影响输出长度和质量
"""
# 验证:max_tokens 不影响实际计费(只有 output_tokens 计费)
response = client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = 8192, # 设得很大
messages = [{"role": "user", "content": "用一句话说你好"}],
)
print(f"max_tokens 设置:8192")
print(f"实际生成:{response.usage.output_tokens} tokens")
print(f"内容:{response.content[0].text}")
# 输出类似:实际生成:6 tokens,内容:你好!
八、常见报错与解决方案
报错一:max_tokens 超过模型限制
# 错误信息
anthropic.BadRequestError: max_tokens: must be less than or equal to 200000
# 原因:Haiku 4.5 的上下文窗口是 20万 token,不能设超过
# 解决:检查模型对应的上下文窗口上限
MAX_OUTPUT_LIMITS = {
"claude-haiku-4-5-20251001": 8_192, # Haiku 的最大单次输出
"claude-sonnet-4-6": 64_000, # Sonnet 的最大单次输出
"claude-opus-4-6": 32_000, # Opus 的最大单次输出
}
# 注意:上下文窗口(输入+输出总量)和单次最大输出是两个不同的限制
报错二:input + max_tokens 超过上下文窗口
# 错误信息
anthropic.BadRequestError: prompt is too long: 950000 tokens > 900000 maximum
# 原因:输入 token 太多,加上 max_tokens 超过上下文窗口
# 解决:用 count_tokens 检查,或者截短输入,或者换更大窗口的模型
count = client.messages.count_tokens(
model = "claude-sonnet-4-6",
messages = your_messages,
)
print(f"输入已用:{count.input_tokens:,} tokens")
报错三:从 OpenAI 迁移漏掉 max_tokens
# 报错
anthropic.BadRequestError: max_tokens: field required
# OpenAI 代码(有默认值,不传没问题):
openai.chat.completions.create(model="gpt-4", messages=[...]) # 不传 max_tokens 也行
# Claude 代码(必须传):
client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = 1024, # 必须!
messages = [...],
)
# 迁移检查清单:grep -r "messages.create" . | grep -v "max_tokens"
九、实用工具函数集
import anthropic
from enum import Enum
client = anthropic.Anthropic()
class TaskType(str, Enum):
CLASSIFY = "classify"
EXTRACT = "extract"
QA = "qa"
CHAT = "chat"
SUMMARIZE = "summarize"
CODE = "code"
ARTICLE = "article"
ANALYSIS = "analysis"
# 各任务类型的推荐 max_tokens
TASK_MAX_TOKENS = {
TaskType.CLASSIFY: 64,
TaskType.EXTRACT: 512,
TaskType.QA: 512,
TaskType.CHAT: 1024,
TaskType.SUMMARIZE: 2048,
TaskType.CODE: 4096,
TaskType.ARTICLE: 8192,
TaskType.ANALYSIS: 8192,
}
def recommend_max_tokens(task_type: TaskType, output_lang: str = "zh") -> int:
"""根据任务类型推荐 max_tokens,中文输出适当增大"""
base = TASK_MAX_TOKENS[task_type]
# 中文每个字约 1.5–2 token,相同内容需要更多 token
multiplier = 1.5 if output_lang == "zh" else 1.0
return int(base * multiplier)
def estimate_output_tokens(expected_chars: int, lang: str = "zh") -> int:
"""根据预期输出字数估算需要的 token 数"""
if lang == "zh":
return int(expected_chars * 1.7) # 中文约 1.7 token/字
else:
return int(expected_chars / 4) # 英文约 4 字符/token
def smart_complete(
messages: list[dict],
task_type: TaskType,
system: str = "",
model: str = "claude-sonnet-4-6",
output_lang:str = "zh",
) -> dict:
"""
智能完成:自动选择合适的 max_tokens 并检测截断
Returns:
{content, stop_reason, truncated, tokens_used, recommended_increase}
"""
max_tokens = recommend_max_tokens(task_type, output_lang)
response = client.messages.create(
model = model,
max_tokens = max_tokens,
system = system,
messages = messages,
)
truncated = response.stop_reason == "max_tokens"
result = {
"content": response.content[0].text,
"stop_reason":response.stop_reason,
"truncated": truncated,
"tokens_used":{
"input": response.usage.input_tokens,
"output": response.usage.output_tokens,
},
"max_tokens_used": max_tokens,
}
if truncated:
# 建议增大到实际用量的 1.5 倍
result["recommended_increase"] = int(response.usage.output_tokens * 1.5)
return result
# 使用示例
# 分类任务
result = smart_complete(
messages = [{"role": "user", "content": "判断这条评论的情感:'商品不错但发货慢'"}],
task_type = TaskType.CLASSIFY,
)
print(f"分类结果:{result['content']}(max_tokens={result['max_tokens_used']})")
# 文章生成
result = smart_complete(
messages = [{"role": "user", "content": "写一篇关于AI发展的文章"}],
task_type = TaskType.ARTICLE,
)
if result["truncated"]:
print(f"输出被截断,建议将 max_tokens 增加到 {result['recommended_increase']}")
十、批量请求中的 max_tokens 优化
"""
批量处理中,不同任务有不同的输出需求
用统一的最大值会导致成本浪费(虽然不直接影响计费,
但会影响 Claude 的输出倾向,可能生成过长的回复)
更好的做法:按任务类型分组,每组用对应的 max_tokens
"""
from anthropic import Anthropic
import anthropic
# Batch API 示例(打5折)
def batch_with_smart_max_tokens(tasks: list[dict]) -> list:
"""
批量处理,每个任务独立设置 max_tokens
tasks 格式:
[
{"messages": [...], "task_type": "classify"},
{"messages": [...], "task_type": "summarize"},
...
]
"""
requests = []
for i, task in enumerate(tasks):
task_type = TaskType(task.get("task_type", "qa"))
max_tokens = recommend_max_tokens(task_type)
requests.append(
anthropic.types.MessageCreateParamsNonStreaming(
model = "claude-haiku-4-5-20251001", # 批量用 Haiku 省成本
max_tokens = max_tokens,
messages = task["messages"],
)
)
# 发起批量请求
batch = client.messages.batches.create(requests=requests)
print(f"批量任务已提交:{batch.id}")
return batch
# 统计批量结果中的截断情况
def analyze_batch_results(batch_id: str):
truncated_count = 0
total_count = 0
for result in client.messages.batches.results(batch_id):
total_count += 1
if result.result.type == "succeeded":
msg = result.result.message
if msg.stop_reason == "max_tokens":
truncated_count += 1
print(f"任务 {result.custom_id} 被截断,已用 {msg.usage.output_tokens} tokens")
print(f"\n截断率:{truncated_count}/{total_count} = {truncated_count/total_count:.1%}")
常见问题
Q:设 max_tokens=1 有意义吗?
有意义,但用途很窄——只在你想测试模型能否启动、或者只需要一个字符的输出时有用。设成 1 时 Claude 会只输出第一个 token 就停止,对大多数业务场景没有用,但在调试连通性时很方便。
Q:为什么我设了 max_tokens=4096,但 Claude 经常在 1000 token 左右就停了?
这是正常的——Claude 认为回答已经完整时会自然结束(stop_reason="end_turn"),不会为了”用满” max_tokens 而强行拖长回复。如果你需要更详细的输出,应该在 Prompt 里要求,比如”请详细说明,不少于 2000 字”,而不是依赖调大 max_tokens 来增加输出长度。
Q:流式输出时怎么知道有没有被截断?
流式输出的 stop_reason 在流结束后才能获取。用 Python SDK 的 stream.get_final_message(),或 Node.js 的 stream.finalMessage(),流结束后检查 final_message.stop_reason 即可。本文流式输出章节的代码已经演示了这个模式。
总结
max_tokens 的正确使用可以概括为四条原则:一、必须传,不传直接报错(Claude 的必填参数,不同于 OpenAI);二、按任务设,分类用64、聊天用1024、长文用8192,不要无脑用一个大值;三、检查截断,每次调用后看 stop_reason,max_tokens 说明输出不完整需要处理;四、用动态计算,长输入时先 count_tokens,再计算安全上限,避免超出上下文窗口的报错。把 safe_complete 和 smart_complete 这两个工具函数加入你的工具库,可以覆盖绝大多数场景的 max_tokens 管理需求。