停止原因与回退
了解每个 stop_reason 值的含义,以及如何在您的应用程序中处理截断、工具使用、暂停的轮次和拒绝。
每个 Messages API 响应都包含一个 stop_reason 字段,用于告诉您 Claude 停止生成的原因。检查此字段以决定是按原样使用响应、继续对话、重试,还是回退到另一个模型。
有关完整的响应模式,请参阅 Messages API 参考。
快速参考
| 值 | 何时出现 | 应对措施 |
|---|---|---|
end_turn | Claude 自然地完成了响应。 | 使用该响应。 |
max_tokens | 响应达到了您的 max_tokens 限制。 | 提高 max_tokens 或继续生成响应。 |
stop_sequence | Claude 输出了您的某个 stop_sequences。 | 读取 stop_sequence 以查看触发的是哪一个。 |
tool_use | Claude 正在调用工具。 | 运行该工具并返回结果。仍缺少结果块的服务器工具调用会在后续响应中完成。 |
pause_turn | 服务器工具循环达到了其迭代上限。 | 将助手内容发回以继续。 |
refusal | Claude 拒绝响应。 | 读取 stop_details 并在回退模型上重试。 |
model_context_window_exceeded | 响应填满了模型的上下文窗口。 | 将响应视为已截断。 |
stop_reason 字段
stop_reason 字段是每个成功的 Messages API 响应的一部分。与表示请求处理失败的错误不同,stop_reason 告诉您 Claude 为何完成了响应生成。
{
"id": "msg_01234",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's the answer to your question..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"stop_details": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}停止原因值
end_turn
最常见的停止原因。表示 Claude 自然地完成了响应。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# 处理完整的响应
for block in response.content:
if block.type == "text":
print(block.text)有时 Claude 会返回一个空响应(恰好 2–3 个令牌且没有内容),其 stop_reason: "end_turn"。这通常发生在 Claude 认为助手轮次已经完成时,尤其是在工具结果之后。
常见原因:
- 在工具结果之后立即添加文本块(Claude 会学习到用户总是在工具结果之后插入文本,因此它会结束自己的轮次以遵循该模式)
- 将 Claude 已完成的响应原样发回而不添加任何内容(Claude 已经判定自己完成了,因此它会保持完成状态)
如何防止空响应:
# 错误:在 tool_result 之后立即添加文本
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"},
{
"type": "text",
"text": "Here's the result", # Don't add text after tool_result
},
],
},
]
# 正确:直接发送工具结果,不附加额外文本
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"}
],
}, # Just the tool_result, no additional text
]如果在修正消息结构后仍然得到空响应,请在新的用户消息中添加一个继续提示,而不是用空响应重试:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
# 检查响应是否为空
if response.stop_reason == "end_turn" and not response.content:
# 错误做法:不要直接用空响应重试
# 这样行不通,因为 Claude 已经判定自己完成了
# 正确做法:在一条新的用户消息中添加继续提示
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
return response最佳实践:
- 切勿在工具结果之后立即添加文本块: 这会让 Claude 学会在每次工具使用后都期待用户输入。
- 不要不加修改地重试空响应: 将空响应发回不会有帮助。
- 将继续提示作为最后手段: 仅在上述修正无法解决问题时使用。
max_tokens
Claude 因达到您请求中指定的 max_tokens 限制而停止。
client = anthropic.Anthropic()
# 限制令牌数的请求
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# 响应已被截断
print("Response was cut off at token limit")
# 可考虑再发送一次请求以继续如果 Claude 的响应因达到 max_tokens 限制而被截断,并且截断的响应包含一个不完整的工具使用块,您需要使用更高的 max_tokens 值重试请求,以获取完整的工具使用。
# 检查响应是否在 tool use(工具使用)过程中被截断
if response.stop_reason == "max_tokens":
# 检查最后一个内容块是否为不完整的 tool_use
last_block = response.content[-1]
if last_block.type == "tool_use":
# 使用更高的 max_tokens 重新发送请求
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude 遇到了您的某个自定义停止序列。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
stop_sequences=["END", "STOP"],
messages=[{"role": "user", "content": "Generate text until you say END"}],
)
if response.stop_reason == "stop_sequence":
print(f"Stopped at sequence: {response.stop_sequence}")tool_use
Claude 正在调用工具,并期望您运行它。
client = anthropic.Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state"},
},
"required": ["location"],
},
}
def execute_tool(name, tool_input):
"""Execute a tool and return the result."""
return f"Weather in {tool_input.get('location', 'unknown')}: 72°F"
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
)
if response.stop_reason == "tool_use":
# 提取并执行工具
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# 将结果返回给 Claude 以生成最终响应tool_use 响应还可能包含一个 server_tool_use 块,其 id 没有匹配的结果块。该服务器工具调用尚未完成,且此响应不携带其结果。在常见情况下,Claude 在同一组并行工具调用中同时调用了一个服务器工具和您的某个客户端工具:API 在不运行服务器工具的情况下返回,以便您可以先运行客户端工具。该状态没有其他标记;请通过检查每个 server_tool_use 或 mcp_tool_use 块的 id 是否有匹配的结果块来检测它。
{
"stop_reason": "tool_use",
"content": [
{
"type": "server_tool_use",
"id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
"name": "web_search",
"input": { "query": "example article" }
},
{
"type": "tool_use",
"id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"name": "run_command",
"input": { "command": "uname -a" }
}
]
}继续的方式是发送一条由 tool_result 块组成的用户消息,响应中的每个 tool_use 块对应一个(参见处理工具调用),并附加两条规则:该消息除 tool_result 块外不得包含任何其他内容,且请求必须保持相同的 tools 数组。如果恢复请求不再定义正在等待的服务器工具,则会以 400 失败,其消息以 but no `web_search` tool was provided 结尾。API 会将您的结果附加到仍处于打开状态的助手轮次,运行被延迟的服务器工具(对于暂停的代码执行,则恢复它),并继续该轮次。对于 Claude 直接调用的服务器工具,下一个响应的 content 以回应上一个响应中 server_tool_use id 的结果块开头。
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}在该用户消息的 tool_result 块之后添加任何内容(例如文本)都会结束助手轮次;对于 Claude 直接调用的服务器工具,请求随后会以 400 invalid_request_error 失败,并指明未解决的服务器工具:
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` block遗漏某个 tool_result,或将其放在其他内容之后,则会更早地以标准的 tool_use ids were found without tool_result blocks immediately after 错误失败。若要向 Claude 提供更多输入,请在轮次完成后将其作为单独的用户消息发送。
pause_turn
当服务器端采样循环在执行服务器工具(例如网页搜索)时达到其迭代上限时返回。默认上限为每个请求 10 次迭代。
发生这种情况时,响应可能包含一个没有对应结果块的 server_tool_use 块。要让 Claude 完成处理,请将响应按原样发回以继续对话。留有客户端 tool_use 块等待您处理的响应,其 stop_reason 永远不会是 pause_turn:当 Claude 停下来调用您的工具时,stop_reason 为 tool_use,您应通过发送客户端 tool_result 块而不是响应本身来继续。
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
messages=[{"role": "user", "content": "Search for latest AI news"}],
)
if response.stop_reason == "pause_turn":
# 将响应发送回去以继续对话
messages = [
{"role": "user", "content": "Search for latest AI news"},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)refusal
Claude 拒绝生成响应。安全分类器以正常的 HTTP 200 响应而非错误的形式返回此停止原因。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude 拒绝了回复
print("Claude was unable to process this request")
# 请考虑重新表述或修改请求发生拒绝时,stop_details 对象会标识触发拒绝的策略类别。这些类别以及完整的拒绝响应形态在拒绝与回退中有介绍。对于 refusal 以外的所有停止原因,stop_details 均为 null。
在 Claude Fable 5.1、Claude Fable 5、Claude Opus 5.5、Claude Opus 5 上被拒绝的请求,通常可以通过在另一个 Claude 模型上重试来处理。拒绝与回退介绍了如何在服务器端或您的客户端中设置这种重试。如果您自行构建从 Claude Fable 5.1、Claude Fable 5、Claude Opus 5.5、Claude Opus 5 发起的重试,回退抵扣介绍了如何避免为提示缓存支付两次费用。
model_context_window_exceeded
Claude 因达到模型的上下文窗口限制而停止。这使您可以在不知道确切输入大小的情况下请求尽可能多的令牌。
# 请求时设置最大令牌数,以尽可能获取更多内容
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
messages=[
{"role": "user", "content": "Large input that uses most of context window..."}
],
)
if response.stop_reason == "model_context_window_exceeded":
# 响应在达到 max_tokens 之前已触及 context window(上下文窗口)限制
print("Response reached model's context window limit")
# 响应仍然有效,但受到了上下文窗口的限制处理停止原因的最佳实践
始终检查 stop_reason
养成在响应处理逻辑中检查 stop_reason 的习惯:
def handle_response(response):
match response.stop_reason:
case "tool_use":
return handle_tool_use(response)
case "max_tokens":
return handle_truncation(response)
case "model_context_window_exceeded":
return handle_context_limit(response)
case "pause_turn":
return handle_pause(response)
case "refusal":
return handle_refusal(response)
case _:
# 处理 end_turn 及其他情况
return next(
(block.text for block in response.content if block.type == "text"),
"",
)妥善处理被截断的响应
当响应因令牌限制或上下文窗口而被截断时,请附加一条提示,让读者知道输出不完整。若要改为从响应中断处继续生成,请参阅确保响应完整。
def handle_truncated_response(response):
text = next((block.text for block in response.content if block.type == "text"), "")
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
if response.stop_reason == "max_tokens":
note = "[Response truncated due to max_tokens limit]"
else:
note = "[Response truncated due to context window limit]"
return f"{text}\n\n{note}"
return text为 pause_turn 实现重试逻辑
使用服务器工具时,如果服务器端采样循环达到其迭代上限(默认为 10),API 可能会返回 pause_turn。通过继续对话来处理这种情况:
def handle_server_tool_conversation(client, user_query, tools, max_continuations=5):
"""
Handle server tool conversations that may require multiple continuations.
The server runs a sampling loop when executing server tools. If the loop
reaches its iteration limit, the API returns pause_turn. Continue the
conversation by sending the response back to let Claude finish.
"""
messages = [{"role": "user", "content": user_query}]
for _ in range(max_continuations):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=4096, messages=messages, tools=tools
)
if response.stop_reason != "pause_turn":
# Claude 已完成处理 - 返回最终响应
return response
# pause_turn:替换完整的消息列表,以保持角色交替
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# 已达到最大续接次数 - 返回最后一次响应
return response停止原因与错误的区别
区分 stop_reason 值和实际错误非常重要:
停止原因(成功的响应)
- 是响应正文的一部分
- 表示生成为何正常停止
- 响应包含有效内容
错误(失败的请求)
- HTTP 状态码为 4xx 或 5xx
- 表示请求处理失败
- 响应包含错误详情
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# 处理带有 stop_reason 的成功响应
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# 处理实际错误
match e.status_code:
case 429:
print("Rate limit exceeded")
case 500:
print("Server error")流式传输注意事项
使用 streaming(流式传输)时,stop_reason:
- 在初始的
message_start事件中为null - 在
message_delta事件中提供 - 不在任何其他事件中提供
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
) as stream:
for event in stream:
if event.type == "message_delta":
stop_reason = event.delta.stop_reason
if stop_reason:
print(f"Stream ended with: {stop_reason}")常见模式
处理工具使用工作流
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason == "tool_use":
# 执行工具并继续
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# 最终响应
return response确保响应完整
def get_complete_response(client, prompt, max_attempts=3):
messages = [{"role": "user", "content": prompt}]
full_response = ""
for _ in range(max_attempts):
response = client.messages.create(
model="claude-opus-5-5", messages=messages, max_tokens=4096
)
full_response += next(
(block.text for block in response.content if block.type == "text"), ""
)
if response.stop_reason != "max_tokens":
break
# 从中断处继续
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_response在不知道输入大小的情况下获取最大令牌数
借助 model_context_window_exceeded 停止原因,您可以在不计算输入大小的情况下请求尽可能多的令牌:
def get_max_possible_tokens(client, prompt):
"""
Get as many tokens as possible within the model's context window
without needing to calculate input token count
"""
response = client.beta.messages.create(
model="claude-opus-5-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
match response.stop_reason:
case "model_context_window_exceeded":
# 已获得输入大小所允许的最大令牌数
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
case "max_tokens":
# 恰好获得了请求的令牌数
print(
f"Generated {response.usage.output_tokens} tokens (max_tokens reached)"
)
case _:
# 自然完成
print(
f"Generated {response.usage.output_tokens} tokens (natural completion)"
)
return next((block.text for block in response.content if block.type == "text"), "")后续步骤
在服务器端或您的客户端中,在回退模型上重试被拒绝的请求。
让 SDK 为您管理 tool_use 循环、结果格式化和重试。
在流式传输时从 message_delta 事件中读取 stop_reason。
处理 4xx 和 5xx HTTP 错误,它们与停止原因不同。
Was this page helpful?