Razones de detención y fallback
Aprende qué significa cada valor de stop_reason y cómo manejar el truncamiento, el uso de herramientas, los turnos en pausa y los rechazos en tu aplicación.
Cada respuesta de la Messages API incluye un campo stop_reasonVista previa disponible con enlaces a la referencia de la API — presiona Tab hasta que el foco entre a la tarjeta y Escape para volver. que te indica por qué Claude dejó de generar. Revisa este campo para decidir si usar la respuesta tal cual, continuar la conversación, reintentar o recurrir a otro modelo como fallback (respaldo).
Para ver el esquema completo de la respuesta, consulta la referencia de la Messages APIVista previa disponible con enlaces a la referencia de la API — presiona Tab hasta que el foco entre a la tarjeta y Escape para volver..
Referencia rápida
| Valor | Cuándo ocurre | Qué hacer |
|---|---|---|
end_turn | Claude completó su respuesta de forma natural. | Usa la respuesta. |
max_tokens | La respuesta alcanzó tu límite de max_tokens. | Aumenta max_tokens o continúa la respuesta. |
stop_sequence | Claude emitió una de tus stop_sequences. | Lee stop_sequence para ver cuál se activó. |
tool_use | Claude está llamando a una herramienta. | Ejecuta la herramienta y devuelve el resultado. Una llamada a una herramienta de servidor a la que aún le falta su bloque de resultado se completa en una respuesta posterior. |
pause_turn | Un bucle de herramientas de servidor alcanzó su límite de iteraciones. | Envía de vuelta el contenido del asistente para continuar. |
refusal | Claude se negó a responder. | Lee stop_details y reintenta en un modelo de fallback. |
model_context_window_exceeded | La respuesta llenó la ventana de contexto del modelo. | Trata la respuesta como truncada. |
El campo stop_reason
El campo stop_reason forma parte de cada respuesta exitosa de la Messages API. A diferencia de los errores, que indican fallos al procesar tu solicitud, stop_reason te indica por qué Claude completó la generación de su respuesta.
{
"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
}
}Valores del motivo de detención
end_turn
La razón de detención más común. Indica que Claude completó su respuesta de forma natural.
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":
# Procesa la respuesta completa
for block in response.content:
if block.type == "text":
print(block.text)A veces Claude devuelve una respuesta vacía (exactamente 2–3 tokens sin contenido) con stop_reason: "end_turn". Esto suele ocurrir cuando Claude determina que el turno del asistente está completo, en particular después de resultados de herramientas.
Causas comunes:
- Agregar bloques de texto inmediatamente después de los resultados de herramientas (el historial muestra entonces texto del usuario después de cada resultado de herramienta, por lo que Claude termina su turno para seguir ese patrón)
- Enviar de vuelta la respuesta completada de Claude sin agregar nada (Claude ya determinó que terminó, así que seguirá considerándose terminado)
Cómo prevenir respuestas vacías:
# INCORRECTO: Agregar texto inmediatamente después de 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
},
],
},
]
# CORRECTO: Envía los resultados de herramientas directamente sin texto adicional
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"}
],
}, # Only the tool_result, no additional text
]Si sigues obteniendo respuestas vacías después de corregir la estructura de los mensajes, agrega un prompt de continuación en un nuevo mensaje de usuario en lugar de reintentar con la respuesta vacía:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
# Comprueba si la respuesta está vacía
if response.stop_reason == "end_turn" and not response.content:
# INCORRECTO: No te limites a reintentar con la respuesta vacía
# Esto no funcionará porque Claude ya decidió que terminó
# CORRECTO: Agrega un prompt de continuación en un NUEVO mensaje de usuario
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
return responseMejores prácticas:
- Nunca agregues bloques de texto inmediatamente después de los resultados de herramientas: Esto establece un patrón de entrada del usuario después de cada uso de herramienta, y Claude sigue ese patrón terminando su turno.
- No reintentes respuestas vacías sin modificación: Enviar de vuelta la respuesta vacía no ayudará.
- Usa prompts de continuación como último recurso: Solo si estas correcciones no resuelven el problema.
max_tokens
Claude se detuvo porque alcanzó el límite de max_tokensVista previa disponible con enlaces a la referencia de la API — presiona Tab hasta que el foco entre a la tarjeta y Escape para volver. especificado en tu solicitud.
client = anthropic.Anthropic()
# Solicitud con tokens limitados
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":
# La respuesta se truncó
print("Response was cut off at token limit")
# Considera hacer otra solicitud para continuarSi la respuesta de Claude se corta porque alcanzó el límite de max_tokens, y la respuesta truncada contiene un bloque de uso de herramientas incompleto, tendrás que reintentar la solicitud con un valor de max_tokens más alto para obtener el uso de herramientas completo.
# Verifica si la respuesta se truncó durante el uso de herramientas
if response.stop_reason == "max_tokens":
# Verifica si el último bloque de contenido es un tool_use incompleto
last_block = response.content[-1]
if last_block.type == "tool_use":
# Envía la solicitud con un max_tokens más alto
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude encontró una de tus secuencias de detenciónVista previa disponible con enlaces a la referencia de la API — presiona Tab hasta que el foco entre a la tarjeta y Escape para volver. personalizadas.
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 está llamando a una herramienta que necesitas ejecutar.
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):
"""Run 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":
# Extrae y ejecuta la herramienta
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# Devuelve el resultado a Claude para la respuesta finalUna respuesta tool_use también puede contener un bloque server_tool_use cuyo id no tiene un bloque de resultado correspondiente. Esa llamada a la herramienta de servidor no está completa, y esta respuesta no incluye su resultado. En el caso común, Claude llama a una herramienta de servidor y a una de tus herramientas de cliente en el mismo grupo de llamadas a herramientas en paralelo: la API responde sin ejecutar la herramienta de servidor para que puedas ejecutar primero las herramientas de cliente. No hay ningún otro indicador de este estado; detéctalo comprobando si el id de cada bloque server_tool_use o mcp_tool_use tiene un bloque de resultado correspondiente.
{
"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" }
}
]
}La continuación es un mensaje de usuario de bloques tool_result, uno por cada bloque tool_use en la respuesta (consulta Manejar llamadas a herramientas), con dos reglas adicionales: ese mensaje no debe contener nada excepto los bloques tool_result, y la solicitud debe mantener el mismo array tools. Una solicitud de reanudación que ya no define la herramienta de servidor en espera falla con un 400 cuyo mensaje termina en but no `web_search` tool was provided. La API adjunta tus resultados al turno del asistente aún abierto, ejecuta la herramienta de servidor diferida (para la ejecución de código en pausa, la reanuda) y continúa el turno. Para una herramienta de servidor que Claude llamó directamente, el content de la siguiente respuesta comienza con el bloque de resultado que responde al id del server_tool_use de la respuesta anterior.
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}Agregar cualquier cosa después de los bloques tool_result en ese mensaje de usuario, como texto, termina el turno del asistente; para una herramienta de servidor que Claude llamó directamente, la solicitud entonces falla con un 400 invalid_request_error que nombra la herramienta de servidor sin resolver:
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` blockOmitir un tool_result, o colocar uno después de otro contenido, falla antes con el error estándar tool_use ids were found without tool_result blocks immediately after. Para darle más entrada a Claude, envíala como un mensaje de usuario separado después de que el turno se complete.
pause_turn
Se devuelve cuando el bucle de muestreo del lado del servidor alcanza su límite de iteraciones mientras ejecuta herramientas de servidor como la búsqueda web. El límite predeterminado es de 10 iteraciones por solicitud.
Cuando esto ocurre, la respuesta puede contener un bloque server_tool_use sin un bloque de resultado correspondiente. Para que Claude termine de procesar, continúa la conversación enviando la respuesta de vuelta tal cual. Una respuesta que deja un bloque tool_use de cliente esperándote nunca tiene un stop_reason de pause_turn: cuando Claude se detiene para llamar a tus herramientas, stop_reason es tool_use, y la continúas enviando los bloques tool_result de cliente en lugar de la respuesta misma.
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":
# Continúa la conversación enviando la respuesta de vuelta
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 se negó a generar una respuesta. Los clasificadores de seguridad devuelven esta razón de detención como una respuesta HTTP 200 normal, no como un error.
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 se negó a responder
print("Claude was unable to process this request")
# Considera reformular o modificar la solicitudEn un rechazo, el objeto stop_detailsVista previa disponible con enlaces a la referencia de la API — presiona Tab hasta que el foco entre a la tarjeta y Escape para volver. identifica la categoría de política que lo provocó. Las categorías y la forma completa de la respuesta de rechazo se explican en Cómo se ve un rechazo. stop_details es null para todas las razones de detención distintas de refusal.
Normalmente puedes obtener una respuesta a una solicitud rechazada reintentándola en otro modelo de Claude. Esto funciona para Claude Fable 5.2, Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5.5 y Claude Haiku 5.5. Rechazos y fallback muestra cómo configurar ese reintento, del lado del servidor o en tu cliente. Claude Haiku 5.5 no tiene fallback del lado del servidor, así que configura su reintento con fallback del lado del cliente. Si construyes el reintento tú mismo desde cualquiera de estos modelos excepto Claude Haiku 5.5, Crédito de fallback explica cómo evitar pagar dos veces el costo de la caché de prompts.
model_context_window_exceeded
Claude se detuvo porque alcanzó el límite de la ventana de contexto del modelo. Esto te permite solicitar el máximo posible de tokens sin conocer el tamaño exacto de la entrada.
client = anthropic.Anthropic()
# Solicitud con el máximo de tokens para obtener lo más posible
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":
# La respuesta alcanzó el límite de la ventana de contexto antes de max_tokens
print("Response reached model's context window limit")
# La respuesta sigue siendo válida, pero quedó limitada por la ventana de contextoMejores prácticas para manejar las razones de detención
Siempre revisa stop_reason
Acostúmbrate a revisar el stop_reason en tu lógica de manejo de respuestas:
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 _:
# Maneja end_turn y otros casos
return next(
(block.text for block in response.content if block.type == "text"),
"",
)Maneja las respuestas truncadas con elegancia
Cuando una respuesta se trunca debido a los límites de tokens o a la ventana de contexto, agrega un aviso para que el lector sepa que la salida está incompleta. Para continuar generando desde donde se quedó la respuesta, consulta Asegurar respuestas completas.
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 textImplementa lógica de reintento para pause_turn
Cuando usas herramientas de servidor, la API puede devolver pause_turn si el bucle de muestreo del lado del servidor alcanza su límite de iteraciones (10 de forma predeterminada). Manéjalo agregando la respuesta pausada a tus mensajes sin cambios y enviando la solicitud de nuevo. Si la respuesta incluye un container, envía también su ID de vuelta. Sin él, la siguiente solicitud inicia un nuevo contenedor y se pierden los archivos de la parte anterior del turno.
client = anthropic.Anthropic()
def handle_server_tool_conversation(client, user_query, tools, max_requests=5):
messages = [{"role": "user", "content": user_query}]
container_id = None
for _ in range(max_requests):
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=messages,
tools=tools,
container=container_id,
)
if response.stop_reason != "pause_turn":
# No está en pausa: devuelve la respuesta
return response
# pause_turn: agrega la respuesta pausada como mensaje del asistente y luego continúa
messages.append({"role": "assistant", "content": response.content})
# Si las herramientas se ejecutaron en un contenedor, envía su ID de vuelta para conservar sus archivos
if response.container:
container_id = response.container.id
# Sigue en pausa tras max_requests solicitudes: devuelve la última respuesta
return response
response = handle_server_tool_conversation(
client,
"Use the code execution tool to find the 1,000th prime number.",
[{"type": "code_execution_20250825", "name": "code_execution"}],
)
print(response.stop_reason)La función devuelve la última respuesta, que contiene solo la parte final del turno. Para mostrar el turno completo o para continuar la conversación, conserva también las respuestas pausadas.
Razones de detención frente a errores
Es importante distinguir entre los valores de stop_reason y los errores reales:
Razones de detención (respuestas exitosas)
- Forman parte del cuerpo de la respuesta
- Indican por qué la generación se detuvo normalmente
- La respuesta contiene contenido válido
Errores (solicitudes fallidas)
- Códigos de estado HTTP 4xx o 5xx
- Indican fallos en el procesamiento de la solicitud
- La respuesta contiene detalles del error
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# Maneja la respuesta exitosa con stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# Maneja los errores reales
match e.status_code:
case 429:
print("Rate limit exceeded")
case 500:
print("Server error")Consideraciones sobre streaming
Al usar streaming, stop_reason:
- Es
nullen el evento inicialmessage_start - Se proporciona en el evento
message_delta - No se proporciona en ningún otro evento
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}")Patrones comunes
Manejo de flujos de trabajo de uso de herramientas
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":
# Ejecuta las herramientas y continúa
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Respuesta final
return responseAsegurar respuestas completas
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
# Continúa desde donde se quedó
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_responseObtener el máximo de tokens sin conocer el tamaño de la entrada
Con la razón de detención model_context_window_exceeded, puedes solicitar el máximo posible de tokens sin calcular el tamaño de la entrada:
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":
# Se obtuvo el máximo de tokens posible según el tamaño de la entrada
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
case "max_tokens":
# Se obtuvo exactamente la cantidad de tokens solicitada
print(
f"Generated {response.usage.output_tokens} tokens (max_tokens reached)"
)
case _:
# Finalización natural
print(
f"Generated {response.usage.output_tokens} tokens (natural completion)"
)
return next((block.text for block in response.content if block.type == "text"), "")Próximos pasos
Reintenta las solicitudes rechazadas en un modelo de fallback, del lado del servidor o en tu cliente.
Deja que el SDK gestione el bucle de tool_use, el formato de los resultados y los reintentos por ti.
Lee stop_reason del evento message_delta al usar streaming.
Maneja los errores HTTP 4xx y 5xx, que son distintos de las razones de detención.
¿Te resultó útil esta página?