Messages API에 요청을 보낼 때, Claude의 응답에는 모델이 응답 생성을 중지한 이유를 나타내는 stop_reason 필드가 포함됩니다. 이러한 값들을 이해하는 것은 다양한 응답 유형을 적절하게 처리하는 견고한 애플리케이션을 구축하는 데 중요합니다.
API 응답의 stop_reason에 대한 자세한 내용은 Messages API 참조를 참조하세요.
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,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}가장 일반적인 중지 이유입니다. Claude가 응답을 자연스럽게 완료했음을 나타냅니다.
if response.stop_reason == "end_turn":
# Process the complete response
print(response.content[0].text)때때로 Claude는 stop_reason: "end_turn"과 함께 빈 응답(정확히 2-3개의 토큰이고 내용이 없음)을 반환합니다. 이는 일반적으로 Claude가 어시스턴트 턴이 완료되었다고 해석할 때, 특히 도구 결과 후에 발생합니다.
일반적인 원인:
빈 응답 방지 방법:
# INCORRECT: Adding text immediately 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"
},
{
"type": "text",
"text": "Here's the result" # Don't add text after tool_result
}
]}
]
# CORRECT: Send tool results directly without additional text
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
]
# If you still get empty responses after fixing the above:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
# Check if response is empty
if (response.stop_reason == "end_turn" and
not response.content):
# INCORRECT: Don't just retry with the empty response
# This won't work because Claude already decided it's done
# CORRECT: Add a continuation prompt in a NEW user message
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
return response모범 사례:
Claude가 요청에서 지정한 max_tokens 제한에 도달했기 때문에 중지했습니다.
# Request with limited 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":
# Response was truncated
print("Response was cut off at token limit")
# Consider making another request to continueClaude가 사용자 정의 중지 시퀀스 중 하나를 만났습니다.
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}")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":
# Extract and execute the tool
for content in response.content:
if content.type == "tool_use":
result = execute_tool(content.name, content.input)
# Return result to Claude for final response웹 검색과 같은 서버 도구와 함께 사용되며, 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":
# Continue the conversation
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"}]
)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 declined to respond
print("Claude was unable to process this request")
# Consider rephrasing or modifying the requestClaude Sonnet 4.5 또는 Opus 4.1을 사용하는 동안 refusal 중지 이유가 자주 발생하면, Sonnet 4 (claude-sonnet-4-20250514)를 사용하도록 API 호출을 업데이트할 수 있습니다. 이는 다른 사용 제한이 있습니다. Sonnet 4.5의 API 안전 필터 이해하기에 대해 자세히 알아보세요.
Claude Sonnet 4.5의 API 안전 필터로 인해 트리거되는 거부에 대해 자세히 알아보려면 Sonnet 4.5의 API 안전 필터 이해하기를 참조하세요.
Claude가 모델의 컨텍스트 윈도우 제한에 도달했기 때문에 중지했습니다. 이를 통해 정확한 입력 크기를 알지 못해도 최대한 많은 토큰을 요청할 수 있습니다.
# Request with maximum tokens to get as much as possible
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=64000, # Model's maximum output tokens
messages=[{"role": "user", "content": "Large input that uses most of context window..."}]
)
if response.stop_reason == "model_context_window_exceeded":
# Response hit context window limit before max_tokens
print("Response reached model's context window limit")
# The response is still valid but was limited by context window이 중지 이유는 Sonnet 4.5 이상의 모델에서 기본적으로 사용 가능합니다. 이전 모델의 경우, 베타 헤더 model-context-window-exceeded-2025-08-26을 사용하여 이 동작을 활성화하세요.
응답 처리 로직에서 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:
# Handle end_turn and other cases
return response.content[0].text토큰 제한이나 컨텍스트 윈도우로 인해 응답이 잘린 경우:
def handle_truncated_response(response):
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
# Option 1: Warn the user about the specific limit
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}"
# Option 2: Continue generation
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일시 중지될 수 있는 서버 도구의 경우:
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 responsestop_reason 값과 실제 오류를 구분하는 것이 중요합니다:
try:
response = client.messages.create(...)
# Handle successful response with stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIError as e:
# Handle actual errors
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":
# Execute tools and continue
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Final response
return responsedef 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
# Continue from where it left off
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."}
]
return full_responsemodel_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.messages.create(
model="claude-sonnet-4-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=64000 # Set to model's maximum output tokens
)
if response.stop_reason == "model_context_window_exceeded":
# Got the maximum possible tokens given input size
print(f"Generated {response.usage.output_tokens} tokens (context limit reached)")
elif response.stop_reason == "max_tokens":
# Got exactly the requested tokens
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# Natural completion
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return response.content[0].textstop_reason 값을 적절하게 처리함으로써, 다양한 응답 시나리오를 우아하게 처리하고 더 나은 사용자 경험을 제공하는 더욱 견고한 애플리케이션을 구축할 수 있습니다.