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가 응답을 자연스럽게 완료했음을 나타냅니다.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# 완전한 응답 처리
print(response.content[0].text)때때로 Claude는 stop_reason: "end_turn"과 함께 빈 응답(콘텐츠 없이 정확히 2-3개의 토큰)을 반환합니다. 이는 일반적으로 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-4-8", 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-4-8", max_tokens=1024, messages=messages
)
return response모범 사례:
Claude가 요청에 지정된 max_tokens 제한에 도달하여 중지했습니다.
# 제한된 토큰으로 요청
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# 응답이 잘렸습니다
print("Response was cut off at token limit")
# 계속하려면 추가 요청을 고려하세요max_tokens 제한에 도달하여 Claude의 응답이 잘리고, 잘린 응답에 불완전한 도구 사용 블록이 포함된 경우, 전체 도구 사용을 얻으려면 더 높은 max_tokens 값으로 요청을 재시도해야 합니다.
# 도구 사용 중 응답이 잘렸는지 확인
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-4-8",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)Claude가 사용자 지정 중지 시퀀스 중 하나를 만났습니다.
response = client.messages.create(
model="claude-opus-4-8",
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가 도구를 호출하고 있으며 사용자가 이를 실행하기를 기대합니다.
대부분의 도구 사용 구현에는 도구 실행, 결과 형식 지정 및 대화 관리를 자동으로 처리하는 tool runner를 사용하세요.
from anthropic import Anthropic
client = 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-sonnet-4-20250514",
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에 반환합니다웹 검색이나 웹 가져오기와 같은 서버 도구를 실행하는 동안 서버 측 샘플링 루프가 반복 제한에 도달하면 반환됩니다. 기본 제한은 요청당 10회 반복입니다.
이 경우 응답에 해당하는 server_tool_result 없이 server_tool_use 블록이 포함될 수 있습니다. Claude가 처리를 완료하도록 하려면 응답을 그대로 다시 보내 대화를 계속하세요.
response = client.messages.create(
model="claude-opus-4-8",
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-opus-4-8",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)애플리케이션은 서버 도구를 사용하는 모든 에이전트 루프에서 pause_turn을 처리해야 합니다. 어시스턴트의 응답을 메시지 배열에 추가하고 또 다른 API 요청을 보내 Claude가 계속 진행하도록 하면 됩니다.
Claude가 응답 생성을 거부했습니다. Claude Fable 5에서는 안전 분류기가 이 중지 이유를 오류가 아닌 일반 HTTP 200 응답으로 반환합니다.
response = client.messages.create(
model="claude-opus-4-8",
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 중지 이유가 자주 발생하는 경우, 다른 사용 제한이 적용되는 Haiku 4.5(claude-haiku-4-5-20251001)를 사용하도록 API 호출을 업데이트해 볼 수 있습니다. Sonnet 4.5의 API 안전 필터 이해하기에서 자세히 알아보세요.
거부 시 stop_details 객체는 거부를 트리거한 정책 카테고리를 식별합니다. 카테고리와 전체 거부 응답 형태는 거부 및 폴백에서 다룹니다. stop_details는 refusal 이외의 모든 중지 이유에 대해 null입니다.
Claude Fable 5에서 거부된 요청은 일반적으로 다른 Claude 모델에서 재시도하여 처리할 수 있으며, 거부 및 폴백에서 서버 측 또는 클라이언트에서 해당 재시도를 설정하는 방법을 보여줍니다. 폴백 크레딧에서는 재시도를 직접 구축할 때 프롬프트 캐시 비용을 두 번 지불하지 않는 방법을 다룹니다.
Claude가 모델의 컨텍스트 윈도우 제한에 도달하여 중지했습니다. 이를 통해 정확한 입력 크기를 알지 못해도 가능한 최대 토큰을 요청할 수 있습니다.
# 가능한 한 많이 얻기 위해 최대 토큰으로 요청
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k (Opus 4.8 supports 128k with streaming)
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 및 이후 모델에서 기본적으로 사용할 수 있습니다. 이전 모델의 경우 베타 헤더 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:
# end_turn 및 기타 경우 처리
return response.content[0].text토큰 제한 또는 컨텍스트 윈도우로 인해 응답이 잘린 경우:
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-opus-4-8",
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Please continue"}],
)
return response.content[0].text + continuation.content[0].text서버 도구를 사용할 때 서버 측 샘플링 루프가 반복 제한(기본값 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-4-8", max_tokens=1024, 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 responsestop_reason 값과 실제 오류를 구분하는 것이 중요합니다:
import anthropic
from anthropic import Anthropic
client = Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-20250514",
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:
# 실제 오류 처리
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")스트리밍을 사용할 때 stop_reason은:
message_start 이벤트에서 nullmessage_delta 이벤트에서 제공됨from anthropic import Anthropic
client = Anthropic()
with client.messages.stream(
model="claude-sonnet-4-20250514",
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}")tool runner로 더 간단하게: 다음 예제는 수동 도구 처리를 보여줍니다. 대부분의 사용 사례에서 tool runner는 훨씬 적은 코드로 도구 실행을 자동으로 처리합니다.
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-4-8", 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 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-opus-4-8", 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_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-opus-4-8",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
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].textstop_reason 값을 적절하게 처리함으로써 다양한 응답 시나리오를 원활하게 처리하고 더 나은 사용자 경험을 제공하는 더욱 견고한 애플리케이션을 구축할 수 있습니다.
Was this page helpful?