중단 이유 처리
Messages API에 요청을 보낼 때, Claude의 응답에는 모델이 응답 생성을 중단한 이유를 나타내는 stop_reason 필드가 포함됩니다. 이러한 값들을 이해하는 것은 다양한 응답 유형을 적절히 처리하는 견고한 애플리케이션을 구축하는 데 중요합니다.
API 응답에서 stop_reason에 대한 자세한 내용은 Messages API 참조를 참조하세요.
stop_reason이란 무엇인가요?
stop_reason 필드는 모든 성공적인 Messages API 응답의 일부입니다. 요청 처리 실패를 나타내는 오류와 달리, stop_reason은 Claude가 응답 생성을 성공적으로 완료한 이유를 알려줍니다.
{
"id": "msg_01234",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "여기 질문에 대한 답변입니다..."
}
],
"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는 stop_reason: "end_turn"과 함께 빈 응답(내용 없이 정확히 2-3개 토큰)을 반환합니다. 이는 일반적으로 Claude가 어시스턴트 턴이 완료되었다고 해석할 때 발생하며, 특히 도구 결과 이후에 자주 나타납니다.
일반적인 원인:
- 도구 결과 직후에 텍스트 블록 추가 (Claude는 사용자가 항상 도구 결과 후에 텍스트를 삽입할 것으로 학습하므로, 패턴을 따르기 위해 턴을 종료합니다)
- 아무것도 추가하지 않고 Claude의 완료된 응답을 다시 보내기 (Claude는 이미 완료되었다고 결정했으므로, 완료된 상태를 유지합니다)
빈 응답을 방지하는 방법:
# 잘못된 방법: tool_result 직후에 텍스트 추가
messages = [
{"role": "user", "content": "1234와 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": "결과입니다" # tool_result 후에 텍스트를 추가하지 마세요
}
]}
]
# 올바른 방법: 추가 텍스트 없이 도구 결과만 직접 보내기
messages = [
{"role": "user", "content": "1234와 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": "계속해주세요"})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
return response모범 사례:
- 도구 결과 직후에 텍스트 블록을 절대 추가하지 마세요 - 이는 Claude에게 모든 도구 사용 후에 사용자 입력을 기대하도록 가르칩니다
- 수정 없이 빈 응답을 재시도하지 마세요 - 빈 응답을 단순히 다시 보내는 것은 도움이 되지 않습니다
- 계속 진행 프롬프트는 최후의 수단으로 사용하세요 - 위의 수정 사항으로 문제가 해결되지 않는 경우에만 사용하세요
max_tokens
Claude가 요청에서 지정된 max_tokens 제한에 도달했기 때문에 중단되었습니다.
# 제한된 토큰으로 요청
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=10,
messages=[{"role": "user", "content": "양자 물리학을 설명해주세요"}]
)
if response.stop_reason == "max_tokens":
# 응답이 잘렸습니다
print("응답이 토큰 제한에서 잘렸습니다")
# 계속하기 위해 다른 요청을 고려하세요stop_sequence
Claude가 사용자 정의 중단 시퀀스 중 하나를 만났습니다.
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
stop_sequences=["END", "STOP"],
messages=[{"role": "user", "content": "END라고 말할 때까지 텍스트를 생성해주세요"}]
)
if response.stop_reason == "stop_sequence":
print(f"시퀀스에서 중단됨: {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": "날씨가 어떤가요?"}]
)
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": "최신 AI 뉴스를 검색해주세요"}]
)
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": "[안전하지 않은 요청]"}]
)
if response.stop_reason == "refusal":
# Claude가 응답을 거부했습니다
print("Claude가 이 요청을 처리할 수 없습니다")
# 요청을 다시 표현하거나 수정하는 것을 고려하세요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": "컨텍스트 윈도우의 대부분을 사용하는 큰 입력..."}]
)
if response.stop_reason == "model_context_window_exceeded":
# 응답이 max_tokens 전에 컨텍스트 윈도우 제한에 도달했습니다
print("응답이 모델의 컨텍스트 윈도우 제한에 도달했습니다")
# 응답은 여전히 유효하지만 컨텍스트 윈도우에 의해 제한되었습니다이 중단 이유는 Sonnet 4.5 및 최신 모델에서 기본적으로 사용할 수 있습니다. 이전 모델의 경우 이 동작을 활성화하려면 베타 헤더 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].text2. 잘린 응답을 우아하게 처리하기
토큰 제한이나 컨텍스트 윈도우로 인해 응답이 잘린 경우:
def handle_truncated_response(response):
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
# 옵션 1: 특정 제한에 대해 사용자에게 경고
if response.stop_reason == "max_tokens":
message = "[max_tokens 제한으로 인해 응답이 잘렸습니다]"
else:
message = "[컨텍스트 윈도우 제한으로 인해 응답이 잘렸습니다]"
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": "계속해주세요"}]
)
return response.content[0].text + continuation.content[0].text3. 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("응답이 잘렸습니다")
except anthropic.APIError as e:
# 실제 오류 처리
if e.status_code == 429:
print("속도 제한 초과")
elif e.status_code == 500:
print("서버 오류")스트리밍 고려사항
스트리밍을 사용할 때 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"스트림이 다음으로 종료됨: {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": "중단된 지점부터 계속해주세요."}
]
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"{response.usage.output_tokens}개 토큰 생성됨 (컨텍스트 제한 도달)")
elif response.stop_reason == "max_tokens":
# 요청된 토큰을 정확히 얻었습니다
print(f"{response.usage.output_tokens}개 토큰 생성됨 (max_tokens 도달)")
else:
# 자연스러운 완료
print(f"{response.usage.output_tokens}개 토큰 생성됨 (자연스러운 완료)")
return response.content[0].textstop_reason 값을 적절히 처리함으로써 다양한 응답 시나리오를 우아하게 처리하고 더 나은 사용자 경험을 제공하는 더욱 견고한 애플리케이션을 구축할 수 있습니다.