Причины остановки и резервные модели
Узнайте, что означает каждое значение 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. На странице Отказы и резервные модели показано, как настроить такую повторную попытку — на стороне сервера или в вашем клиенте. Если вы сами реализуете повторную попытку для запросов к 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?