當您向 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 個 token 且沒有內容)並帶有 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 限制。
# 有限 token 的請求
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 停止是因為它達到了模型的上下文視窗限制。這允許您請求最大可能的 token 而無需知道確切的輸入大小。
# 請求最大 token 以獲得盡可能多的內容
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=64000,  # 模型的最大輸出 token
    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. 優雅地處理截斷的回應

當回應由於 token 限制或上下文視窗而被截斷時:
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

停止原因 vs. 錯誤

區分 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

在不知道輸入大小的情況下獲得最大 token

使用 model_context_window_exceeded 停止原因,您可以請求最大可能的 token 而無需計算輸入大小:
def get_max_possible_tokens(client, prompt):
    """
    在模型的上下文視窗內獲得盡可能多的 token
    而無需計算輸入 token 數量
    """
    response = client.messages.create(
        model="claude-sonnet-4-5",
        messages=[{"role": "user", "content": prompt}],
        max_tokens=64000  # 設定為模型的最大輸出 token
    )

    if response.stop_reason == "model_context_window_exceeded":
        # 在給定輸入大小的情況下獲得了最大可能的 token
        print(f"Generated {response.usage.output_tokens} tokens (context limit reached)")
    elif response.stop_reason == "max_tokens":
        # 獲得了確切請求的 token
        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 值,您可以建構更穩健的應用程式,優雅地處理不同的回應場景並提供更好的使用者體驗。