当您向 Messages API 发出请求时,Claude 的响应包含一个 stop_reason 字段,该字段指示模型为什么停止生成响应。理解这些值对于构建能够适当处理不同响应类型的健壮应用程序至关重要。 有关 API 响应中 stop_reason 的详细信息,请参阅 Messages API 参考

什么是 stop_reason?

stop_reason 字段是每个成功的 Messages API 响应的一部分。与表示处理请求失败的错误不同,stop_reason 告诉您 Claude 为什么成功完成了响应生成。
Example response
{
  "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,
  "usage": {
    "input_tokens": 100,
    "output_tokens": 50
  }
}

停止原因值

end_turn

最常见的停止原因。表示 Claude 自然地完成了其响应。
if response.stop_reason == "end_turn":
    # 处理完整响应
    print(response.content[0].text)

带有 end_turn 的空响应

有时 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"  # 不要在 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"
        }
    ]}  # 只有 tool_result,没有额外文本
]

# 如果在修复上述问题后仍然得到空响应:
def handle_empty_response(client, messages):
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        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-sonnet-4-20250514",
            max_tokens=1024,
            messages=messages
        )

    return response
最佳实践:
  1. 永远不要在工具结果后立即添加文本块 - 这会教会 Claude 在每次工具使用后期望用户输入
  2. 不要在不修改的情况下重试空响应 - 简单地将空响应发送回去不会有帮助
  3. 将继续提示作为最后手段 - 只有在上述修复无法解决问题时才使用

max_tokens

Claude 停止是因为它达到了您请求中指定的 max_tokens 限制。
# 带有有限令牌的请求
response = client.messages.create(
    model="claude-sonnet-4-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")
    # 考虑发出另一个请求以继续

stop_sequence

Claude 遇到了您的自定义停止序列之一。
response = client.messages.create(
    model="claude-sonnet-4-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 正在调用工具并期望您执行它。
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=[weather_tool],
    messages=[{"role": "user", "content": "What's the weather?"}]
)

if response.stop_reason == "tool_use":
    # 提取并执行工具
    for content in response.content:
        if content.type == "tool_use":
            result = execute_tool(content.name, content.input)
            # 将结果返回给 Claude 以获得最终响应

pause_turn

与服务器工具(如网络搜索)一起使用,当 Claude 需要暂停长时间运行的操作时。
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    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": original_query},
        {"role": "assistant", "content": response.content}
    ]
    continuation = client.messages.create(
        model="claude-sonnet-4-5",
        messages=messages,
        tools=[{"type": "web_search_20250305", "name": "web_search"}]
    )

refusal

Claude 由于安全考虑拒绝生成响应。
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "[Unsafe request]"}]
)

if response.stop_reason == "refusal":
    # Claude 拒绝响应
    print("Claude was unable to process this request")
    # 考虑重新措辞或修改请求
如果您在使用 Claude Sonnet 4.5 或 Opus 4.1 时经常遇到 refusal 停止原因,您可以尝试更新您的 API 调用以使用 Sonnet 4 (claude-sonnet-4-20250514),它具有不同的使用限制。了解更多关于理解 Sonnet 4.5 的 API 安全过滤器
要了解更多关于 Claude Sonnet 4.5 的 API 安全过滤器触发的拒绝,请参阅理解 Sonnet 4.5 的 API 安全过滤器

model_context_window_exceeded

Claude 停止是因为它达到了模型的上下文窗口限制。这允许您请求最大可能的令牌,而无需知道确切的输入大小。
# 请求最大令牌以获得尽可能多的内容
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=64000,  # 模型的最大输出令牌
    messages=[{"role": "user", "content": "Large input that uses most of context window..."}]
)

if response.stop_reason == "model_context_window_exceeded":
    # 响应在 max_tokens 之前达到了上下文窗口限制
    print("Response reached model's context window limit")
    # 响应仍然有效,但受到上下文窗口的限制
此停止原因在 Sonnet 4.5 和更新的模型中默认可用。对于较早的模型,使用 beta 头部 model-context-window-exceeded-2025-08-26 来启用此行为。

处理停止原因的最佳实践

1. 始终检查 stop_reason

养成在响应处理逻辑中检查 stop_reason 的习惯:
def handle_response(response):
    if response.stop_reason == "tool_use":
        return handle_tool_use(response)
    elif response.stop_reason == "max_tokens":
        return handle_truncation(response)
    elif response.stop_reason == "model_context_window_exceeded":
        return handle_context_limit(response)
    elif response.stop_reason == "pause_turn":
        return handle_pause(response)
    elif response.stop_reason == "refusal":
        return handle_refusal(response)
    else:
        # 处理 end_turn 和其他情况
        return response.content[0].text

2. 优雅地处理截断的响应

当响应由于令牌限制或上下文窗口而被截断时:
def handle_truncated_response(response):
    if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
        # 选项 1:警告用户特定限制
        if response.stop_reason == "max_tokens":
            message = "[Response truncated due to max_tokens limit]"
        else:
            message = "[Response truncated due to context window limit]"
        return f"{response.content[0].text}\n\n{message}"

        # 选项 2:继续生成
        messages = [
            {"role": "user", "content": original_prompt},
            {"role": "assistant", "content": response.content[0].text}
        ]
        continuation = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=1024,
            messages=messages + [{"role": "user", "content": "Please continue"}]
        )
        return response.content[0].text + continuation.content[0].text

3. 为 pause_turn 实现重试逻辑

对于可能暂停的服务器工具:
def handle_paused_conversation(initial_response, max_retries=3):
    response = initial_response
    messages = [{"role": "user", "content": original_query}]
    
    for attempt in range(max_retries):
        if response.stop_reason != "pause_turn":
            break
            
        messages.append({"role": "assistant", "content": response.content})
        response = client.messages.create(
            model="claude-sonnet-4-5",
            messages=messages,
            tools=original_tools
        )
    
    return response

停止原因与错误

区分 stop_reason 值和实际错误很重要:

停止原因(成功响应)

  • 响应体的一部分
  • 指示生成为什么正常停止
  • 响应包含有效内容

错误(失败请求)

  • HTTP 状态码 4xx 或 5xx
  • 指示请求处理失败
  • 响应包含错误详细信息
try:
    response = client.messages.create(...)
    
    # 处理带有 stop_reason 的成功响应
    if response.stop_reason == "max_tokens":
        print("Response was truncated")
    
except anthropic.APIError as e:
    # 处理实际错误
    if e.status_code == 429:
        print("Rate limit exceeded")
    elif e.status_code == 500:
        print("Server error")

流式传输考虑事项

使用流式传输时,stop_reason 是:
  • 在初始 message_start 事件中为 null
  • message_delta 事件中提供
  • 在任何其他事件中不提供
with client.messages.stream(...) 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-sonnet-4-5",
            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-sonnet-4-5",
            messages=messages,
            max_tokens=4096
        )

        full_response += response.content[0].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):
    """
    在模型的上下文窗口内获得尽可能多的令牌
    而无需计算输入令牌数量
    """
    response = client.messages.create(
        model="claude-sonnet-4-5",
        messages=[{"role": "user", "content": prompt}],
        max_tokens=64000  # 设置为模型的最大输出令牌
    )

    if response.stop_reason == "model_context_window_exceeded":
        # 在给定输入大小的情况下获得了最大可能的令牌
        print(f"Generated {response.usage.output_tokens} tokens (context limit reached)")
    elif response.stop_reason == "max_tokens":
        # 获得了确切请求的令牌
        print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
    else:
        # 自然完成
        print(f"Generated {response.usage.output_tokens} tokens (natural completion)")

    return response.content[0].text
通过正确处理 stop_reason 值,您可以构建更健壮的应用程序,优雅地处理不同的响应场景并提供更好的用户体验。