Причины остановки и резервные модели
Узнайте, что означает каждое значение stop_reason и как обрабатывать усечение ответов, использование инструментов, приостановленные ходы и отказы в вашем приложении.
Каждый ответ Messages API содержит поле stop_reason, которое сообщает, почему Claude прекратил генерацию. Проверяйте это поле, чтобы решить, что делать дальше: использовать ответ как есть, продолжить разговор, повторить запрос или выполнить «fallback» (переключение на резервную модель).
Полную схему ответа см. в справочнике по Messages API.
Краткий справочник
| Значение | Когда возникает | Что делать |
|---|---|---|
end_turn | Claude естественным образом завершил свой ответ. | Используйте ответ. |
max_tokens | Ответ достиг заданного вами лимита max_tokens. | Увеличьте max_tokens или продолжите ответ. |
stop_sequence | Claude выдал одну из ваших stop_sequences. | Прочитайте stop_sequence, чтобы узнать, какая из них сработала. |
tool_use | Claude вызывает инструмент. | Выполните инструмент и верните результат. Вызов серверного инструмента, для которого ещё нет блока результата, завершается в одном из последующих ответов. |
pause_turn | Цикл серверных инструментов достиг лимита итераций. | Отправьте содержимое ассистента обратно, чтобы продолжить. |
refusal | Claude отказался отвечать. | Прочитайте stop_details и повторите запрос на резервной модели. |
model_context_window_exceeded | Ответ заполнил «context window» (контекстное окно) модели. | Считайте ответ усечённым. |
Поле stop_reason
Поле 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,
"stop_details": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}Значения причин остановки
end_turn
Самая распространённая «stop reason» (причина остановки). Указывает, что Claude естественным образом завершил свой ответ.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# Обработать полный ответ
for block in response.content:
if block.type == "text":
print(block.text)Иногда Claude возвращает пустой ответ (ровно 2–3 токена без содержимого) с stop_reason: "end_turn". Обычно это происходит, когда Claude считает, что ход ассистента завершён, особенно после результатов инструментов.
Распространённые причины:
- Добавление текстовых блоков сразу после результатов инструментов (Claude привыкает ожидать, что пользователь всегда вставляет текст после результатов инструментов, поэтому завершает свой ход, следуя этому шаблону)
- Отправка завершённого ответа Claude обратно без каких-либо добавлений (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-5-5", 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-5-5", max_tokens=1024, messages=messages
)
return responseРекомендации:
- Никогда не добавляйте текстовые блоки сразу после результатов инструментов: это приучает Claude ожидать пользовательского ввода после каждого использования инструмента.
- Не повторяйте пустые ответы без изменений: отправка пустого ответа обратно не поможет.
- Используйте подсказки для продолжения в крайнем случае: только если перечисленные исправления не решают проблему.
max_tokens
Claude остановился, потому что достиг лимита max_tokens, указанного в вашем запросе.
client = anthropic.Anthropic()
# Запрос с ограниченным числом токенов
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# Ответ был обрезан
print("Response was cut off at token limit")
# Можно отправить ещё один запрос, чтобы продолжитьЕсли ответ Claude обрезан из-за достижения лимита max_tokens и усечённый ответ содержит незавершённый блок «tool use» (использование инструментов), вам нужно повторить запрос с большим значением 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-5-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude встретил одну из ваших пользовательских стоп-последовательностей.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-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}")tool_use
Claude вызывает инструмент и ожидает, что вы его выполните.
client = anthropic.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-opus-5-5",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
)
if response.stop_reason == "tool_use":
# Извлекаем и выполняем инструмент
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# Возвращаем результат Claude для формирования итогового ответаОтвет tool_use также может содержать блок server_tool_use, для id которого нет соответствующего блока результата. Такой вызов серверного инструмента не завершён, и этот ответ не содержит его результата. В типичном случае Claude вызывает серверный инструмент и один из ваших клиентских инструментов в одной группе параллельных вызовов инструментов: API возвращает ответ, не выполняя серверный инструмент, чтобы вы могли сначала выполнить клиентские инструменты. Другого признака этого состояния нет; определяйте его, проверяя для id каждого блока server_tool_use или mcp_tool_use наличие соответствующего блока результата.
{
"stop_reason": "tool_use",
"content": [
{
"type": "server_tool_use",
"id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
"name": "web_search",
"input": { "query": "example article" }
},
{
"type": "tool_use",
"id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"name": "run_command",
"input": { "command": "uname -a" }
}
]
}Продолжение — это пользовательское сообщение из блоков tool_result, по одному на каждый блок tool_use в ответе (см. Обработка вызовов инструментов), с двумя дополнительными правилами: это сообщение не должно содержать ничего, кроме блоков tool_result, а запрос должен сохранять тот же массив tools. Запрос на возобновление, в котором больше не определён ожидающий серверный инструмент, завершается ошибкой 400, сообщение которой заканчивается на but no `web_search` tool was provided. API присоединяет ваши результаты к всё ещё открытому ходу ассистента, выполняет отложенный серверный инструмент (а для приостановленного выполнения кода — возобновляет его) и продолжает ход. Для серверного инструмента, который Claude вызвал напрямую, content следующего ответа начинается с блока результата, соответствующего id блока server_tool_use из предыдущего ответа.
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}Добавление чего-либо после блоков tool_result в этом пользовательском сообщении, например текста, завершает ход ассистента; для серверного инструмента, который Claude вызвал напрямую, запрос в этом случае завершается ошибкой 400 invalid_request_error с указанием неразрешённого серверного инструмента:
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` blockЕсли пропустить tool_result или поместить его после другого содержимого, запрос завершится ошибкой раньше — со стандартной ошибкой tool_use ids were found without tool_result blocks immediately after. Чтобы передать Claude дополнительные входные данные, отправьте их отдельным пользовательским сообщением после завершения хода.
pause_turn
Возвращается, когда серверный цикл сэмплирования достигает лимита итераций при выполнении серверных инструментов, таких как веб-поиск. Лимит по умолчанию — 10 итераций на запрос.
В этом случае ответ может содержать блок server_tool_use без соответствующего блока результата. Чтобы Claude мог завершить обработку, продолжите разговор, отправив ответ обратно как есть. Ответ, в котором клиентский блок tool_use ожидает ваших действий, никогда не имеет stop_reason со значением pause_turn: когда Claude останавливается, чтобы вызвать ваши инструменты, stop_reason равен tool_use, и вы продолжаете его, отправляя клиентские блоки tool_result, а не сам ответ.
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
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": "Search for latest AI news"},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)refusal
Claude отказался генерировать ответ. Классификаторы безопасности возвращают эту причину остановки как обычный ответ HTTP 200, а не как ошибку.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude отказался отвечать
print("Claude was unable to process this request")
# Попробуйте переформулировать или изменить запросПри отказе объект stop_details указывает категорию политики, которая его вызвала. Категории и полная форма ответа с отказом описаны на странице Отказы и резервные модели. Для всех причин остановки, кроме refusal, stop_details равен null.
Отклонённый запрос к Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5.5 или Claude Haiku 5.5 обычно можно обслужить, повторив его на другой модели Claude. На странице Отказы и резервная обработка показано, как настроить такой повтор — на стороне сервера или в вашем клиенте. У Claude Haiku 5.5 нет серверной резервной обработки, поэтому настройте повтор для неё в вашем клиенте. Если вы реализуете повтор самостоятельно для запросов к Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5 или Claude Sonnet 5.5, на странице Кредит при резервном переключении описано, как избежать двойной оплаты кэша подсказок.
model_context_window_exceeded
Claude остановился, потому что достиг предела контекстного окна модели. Это позволяет запрашивать максимально возможное количество токенов, не зная точного размера входных данных.
# Запрос с максимальным числом токенов, чтобы получить как можно больше
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
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")
# Ответ по-прежнему корректен, но был ограничен контекстным окномРекомендации по обработке причин остановки
Всегда проверяйте stop_reason
Возьмите за правило проверять stop_reason в логике обработки ответов:
def handle_response(response):
match response.stop_reason:
case "tool_use":
return handle_tool_use(response)
case "max_tokens":
return handle_truncation(response)
case "model_context_window_exceeded":
return handle_context_limit(response)
case "pause_turn":
return handle_pause(response)
case "refusal":
return handle_refusal(response)
case _:
# Обработка end_turn и других случаев
return next(
(block.text for block in response.content if block.type == "text"),
"",
)Корректно обрабатывайте усечённые ответы
Когда ответ усечён из-за лимитов токенов или контекстного окна, добавьте уведомление, чтобы читатель знал, что вывод неполный. Чтобы вместо этого продолжить генерацию с того места, где ответ прервался, см. раздел Обеспечение полных ответов.
def handle_truncated_response(response):
text = next((block.text for block in response.content if block.type == "text"), "")
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
if response.stop_reason == "max_tokens":
note = "[Response truncated due to max_tokens limit]"
else:
note = "[Response truncated due to context window limit]"
return f"{text}\n\n{note}"
return textРеализуйте логику повтора для pause_turn
При использовании серверных инструментов API может вернуть pause_turn, если серверный цикл сэмплирования достигает лимита итераций (по умолчанию 10). Обрабатывайте это, продолжая разговор:
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-5-5",
max_tokens=4096,
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 responseПричины остановки и ошибки
Важно различать значения stop_reason и настоящие ошибки:
Причины остановки (успешные ответы)
- Являются частью тела ответа
- Указывают, почему генерация штатно остановилась
- Ответ содержит корректное содержимое
Ошибки (неудачные запросы)
- Коды состояния HTTP 4xx или 5xx
- Указывают на сбои при обработке запроса
- Ответ содержит сведения об ошибке
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5-5",
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:
# Обработка реальных ошибок
match e.status_code:
case 429:
print("Rate limit exceeded")
case 500:
print("Server error")Особенности потоковой передачи
При использовании «streaming» (потоковая передача) stop_reason:
- равен
nullв начальном событииmessage_start - передаётся в событии
message_delta - не передаётся ни в каких других событиях
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-5",
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}")Распространённые шаблоны
Обработка рабочих процессов с использованием инструментов
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-5-5",
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 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-opus-5-5", messages=messages, max_tokens=4096
)
full_response += next(
(block.text for block in response.content if block.type == "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_responseПолучение максимального количества токенов без знания размера входных данных
Благодаря причине остановки model_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.beta.messages.create(
model="claude-opus-5-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
match response.stop_reason:
case "model_context_window_exceeded":
# Получено максимально возможное число токенов с учётом размера входных данных
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
case "max_tokens":
# Получено ровно запрошенное число токенов
print(
f"Generated {response.usage.output_tokens} tokens (max_tokens reached)"
)
case _:
# Естественное завершение
print(
f"Generated {response.usage.output_tokens} tokens (natural completion)"
)
return next((block.text for block in response.content if block.type == "text"), "")Следующие шаги
Повторяйте отклонённые запросы на резервной модели — на стороне сервера или в вашем клиенте.
Позвольте SDK управлять циклом tool_use, форматированием результатов и повторными попытками за вас.
Считывайте stop_reason из события message_delta при потоковой передаче.
Обрабатывайте HTTP-ошибки 4xx и 5xx, которые отличаются от причин остановки.
Was this page helpful?