Когда вы делаете запрос к Messages API, ответ Claude включает поле stop_reason, которое указывает, почему модель прекратила генерацию своего ответа. Понимание этих значений имеет решающее значение для создания надежных приложений, которые соответствующим образом обрабатывают различные типы ответов.
Подробности о stop_reason в ответе API см. в справочнике 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 возвращает пустой ответ (ровно 2-3 токена без содержимого) с stop_reason: "end_turn". Это обычно происходит, когда 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}")
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": "Найдите последние новости об ИИ"}]
)
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 не смог обработать этот запрос")
# Рассмотрите возможность переформулировать или изменить запрос
Если вы часто сталкиваетесь с причинами остановки refusal при использовании Claude Sonnet 4.5 или Opus 4.1, вы можете попробовать обновить свои вызовы API для использования Sonnet 4 (claude-sonnet-4-20250514), который имеет другие ограничения использования. Узнайте больше о понимании фильтров безопасности API Sonnet 4.5.
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].text
2. Изящно обрабатывайте обрезанные ответы
Когда ответ обрезан из-за лимитов токенов или контекстного окна:
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
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
Причины остановки против ошибок
Важно различать значения 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:
null в начальном событии message_start
- Предоставляется в событии
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].text
Правильно обрабатывая значения stop_reason, вы можете создавать более надежные приложения, которые изящно обрабатывают различные сценарии ответов и обеспечивают лучший пользовательский опыт.