Когда вы делаете запрос к Messages API, ответ Claude включает поле stop_reason, которое указывает, почему модель прекратила генерацию своего ответа. Понимание этих значений имеет решающее значение для создания надежных приложений, которые соответствующим образом обрабатывают различные типы ответов.
Подробности о stop_reason в ответе API см. в справочнике Messages API.
Поле 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
}
}Наиболее распространенная причина остановки. Указывает, что Claude естественным образом завершил свой ответ.
if response.stop_reason == "end_turn":
# Обработать полный ответ
print(response.content[0].text)Иногда Claude возвращает пустой ответ (ровно 2-3 токена без содержимого) с stop_reason: "end_turn". Это обычно происходит, когда 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, указанного в вашем запросе.
# Запрос с ограниченными токенами
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=10,
messages=[{"role": "user", "content": "Объясните квантовую физику"}]
)
if response.stop_reason == "max_tokens":
# Ответ был обрезан
print("Ответ был обрезан по лимиту токенов")
# Рассмотрите возможность сделать еще один запрос для продолжения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}")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 для финального ответаИспользуется с серверными инструментами, такими как веб-поиск, когда 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": "Найдите последние новости об ИИ"}]
)
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"}]
)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 не смог обработать этот запрос")
# Рассмотрите возможность переформулировать или изменить запросЕсли вы часто сталкиваетесь с причинами остановки refusal при использовании Claude Sonnet 4.5 или Opus 4.1, вы можете попробовать обновить свои вызовы API для использования Sonnet 4 (claude-sonnet-4-20250514), который имеет другие ограничения использования. Узнайте больше о понимании фильтров безопасности API Sonnet 4.5.
Чтобы узнать больше об отказах, вызванных фильтрами безопасности API для Claude Sonnet 4.5, см. Понимание фильтров безопасности API Sonnet 4.5.
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, чтобы включить это поведение.
Сделайте привычкой проверять 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 = "[Ответ обрезан из-за лимита 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].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 responseВажно различать значения stop_reason и фактические ошибки:
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:
null в начальном событии message_startmessage_deltawith 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 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
# Продолжить с того места, где остановились
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].textПравильно обрабатывая значения stop_reason, вы можете создавать более надежные приложения, которые изящно обрабатывают различные сценарии ответов и обеспечивают лучший пользовательский опыт.