Was this page helpful?
Когда вы отправляете запрос к 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": "Here's the answer to your question..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}Наиболее распространённая причина остановки. Указывает, что Claude завершил свой ответ естественным образом.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
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": "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-4-8", 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-4-8", max_tokens=1024, messages=messages
)
return responseРекомендации:
Claude остановился, потому что достиг лимита max_tokens, указанного в вашем запросе.
# Запрос с ограниченным числом токенов
response = client.messages.create(
model="claude-opus-4-8",
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, и усечённый ответ содержит неполный блок использования инструментов, вам потребуется повторить запрос с более высоким значением max_tokens, чтобы получить полный блок использования инструментов.
Claude встретил одну из ваших пользовательских последовательностей остановки.
response = client.messages.create(
model="claude-opus-4-8",
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}")Claude вызывает инструмент и ожидает, что вы его выполните.
Для большинства реализаций использования инструментов применяйте tool runner, который автоматически обрабатывает выполнение инструментов, форматирование результатов и управление диалогом.
from anthropic import Anthropic
client = 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-sonnet-4-20250514",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather?"}],
)
if response.stop_reason == "tool_use":
# Извлечь и выполнить инструмент
for content in response.content:
if content.type == "tool_use":
result = execute_tool(content.name, content.input)
# Вернуть результат Claude для окончательного ответаВозвращается, когда серверный цикл сэмплирования достигает лимита итераций при выполнении серверных инструментов, таких как веб-поиск или веб-загрузка. Лимит по умолчанию — 10 итераций на запрос.
Когда это происходит, ответ может содержать блок server_tool_use без соответствующего server_tool_result. Чтобы позволить Claude завершить обработку, продолжите диалог, отправив ответ обратно как есть.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
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": original_query},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)Ваше приложение должно обрабатывать pause_turn в любом агентном цикле, использующем серверные инструменты. Просто добавьте ответ ассистента в ваш массив сообщений и выполните ещё один запрос к API, чтобы позволить Claude продолжить.
Claude отказался генерировать ответ. В Claude Fable 5 классификаторы безопасности возвращают эту причину остановки как обычный ответ HTTP 200, а не как ошибку.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude отказался отвечать
print("Claude was unable to process this request")
# Рассмотрите возможность переформулировать или изменить запросЕсли вы часто сталкиваетесь с причиной остановки refusal при использовании Claude Sonnet 4.5 или Opus 4.1 (устаревшая модель), вы можете попробовать обновить ваши вызовы API для использования Haiku 4.5 (claude-haiku-4-5-20251001), у которой другие ограничения использования. Узнайте больше о понимании фильтров безопасности API Sonnet 4.5.
При отказе объект stop_details указывает категорию политики, которая его вызвала. Категории и полная структура ответа при отказе описаны на странице Отказы и резервный вариант. stop_details равен null для всех причин остановки, кроме refusal.
Отклонённый запрос в Claude Fable 5 обычно можно обслужить, повторив его на другой модели Claude, и страница Отказы и резервный вариант показывает, как настроить такой повтор — на стороне сервера или в вашем клиенте. Страница Кредит за резервный вариант описывает, как избежать двойной оплаты стоимости кэша подсказок, когда вы реализуете повтор самостоятельно.
Claude остановился, потому что достиг лимита контекстного окна модели. Это позволяет вам запрашивать максимально возможное количество токенов, не зная точного размера входных данных.
# Запрос с максимальным числом токенов, чтобы получить как можно больше
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k (Opus 4.8 supports 128k with streaming)
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")
# Ответ всё ещё корректен, но был ограничен контекстным окномЭта причина остановки доступна по умолчанию в 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 = "[Response truncated due to max_tokens limit]"
else:
message = "[Response truncated due to context window limit]"
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-opus-4-8",
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Please continue"}],
)
return response.content[0].text + continuation.content[0].textПри использовании серверных инструментов 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-4-8", max_tokens=1024, 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 и фактические ошибки:
import anthropic
from anthropic import Anthropic
client = Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-20250514",
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:
# Обработка фактических ошибок
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")При использовании потоковой передачи stop_reason:
null в начальном событии message_startmessage_deltafrom anthropic import Anthropic
client = Anthropic()
with client.messages.stream(
model="claude-sonnet-4-20250514",
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}")Проще с tool runner: Следующий пример показывает ручную обработку инструментов. Для большинства сценариев использования tool runner автоматически обрабатывает выполнение инструментов с гораздо меньшим объёмом кода.
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-4-8", 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 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-opus-4-8", 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": "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.messages.create(
model="claude-opus-4-8",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
if response.stop_reason == "model_context_window_exceeded":
# Получено максимально возможное число токенов при данном размере входных данных
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
elif response.stop_reason == "max_tokens":
# Получено ровно запрошенное число токенов
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# Естественное завершение
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return response.content[0].textПравильно обрабатывая значения stop_reason, вы можете создавать более надёжные приложения, которые корректно справляются с различными сценариями ответов и обеспечивают лучший пользовательский опыт.
# Проверяем, был ли ответ усечён во время использования инструментов
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-4-8",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)