Cuando realizas una solicitud a la API de Mensajes, la respuesta de Claude incluye un campo stop_reason que indica por qué el modelo dejó de generar su respuesta. Comprender estos valores es crucial para construir aplicaciones robustas que manejen diferentes tipos de respuestas de manera apropiada.
Para obtener detalles sobre stop_reason en la respuesta de la API, consulta la referencia de la API de Mensajes.
El campo stop_reason es parte de cada respuesta exitosa de la API de Mensajes. A diferencia de los errores, que indican fallos en el procesamiento de tu solicitud, stop_reason te indica por qué Claude completó exitosamente 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,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}La razón de parada más común. Indica que Claude terminó su respuesta de forma natural.
if response.stop_reason == "end_turn":
# Procesa la respuesta completa
print(response.content[0].text)A veces Claude devuelve una respuesta vacía (exactamente 2-3 tokens sin contenido) con stop_reason: "end_turn". Esto típicamente ocurre cuando Claude interpreta que el turno del asistente está completo, particularmente después de resultados de herramientas.
Causas comunes:
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" # No agregues texto después de tool_result
}
]}
]
# CORRECTO: Envía 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"
}
]} # Solo el tool_result, sin texto adicional
]
# Si aún obtienes respuestas vacías después de corregir lo anterior:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
# Verifica si la respuesta está vacía
if (response.stop_reason == "end_turn" and
not response.content):
# INCORRECTO: No simplemente reintentar con la respuesta vacía
# Esto no funcionará porque Claude ya decidió que está hecho
# CORRECTO: Agrega un mensaje de continuación en un NUEVO mensaje del usuario
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
return responseMejores prácticas:
Claude se detuvo porque alcanzó el límite de max_tokens especificado en tu solicitud.
# Solicitud con tokens limitados
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}]
)
if response.stop_reason == "max_tokens":
# La respuesta fue truncada
print("Response was cut off at token limit")
# Considera hacer otra solicitud para continuarClaude encontró una de tus secuencias de parada personalizadas.
response = client.messages.create(
model="claude-sonnet-4-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}")Claude está llamando a una herramienta y espera que la ejecutes.
Para la mayoría de implementaciones de uso de herramientas, recomendamos usar el ejecutor de herramientas que maneja automáticamente la ejecución de herramientas, el formato de resultados y la gestión de conversaciones.
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather?"}]
)
if response.stop_reason == "tool_use":
# Extrae y ejecuta la herramienta
for content in response.content:
if content.type == "tool_use":
result = execute_tool(content.name, content.input)
# Devuelve el resultado a Claude para la respuesta finalSe utiliza con herramientas del servidor como búsqueda web cuando Claude necesita pausar una operación de larga duración.
response = client.messages.create(
model="claude-sonnet-4-5",
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":
# Continúa la conversación
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"}]
)Claude se negó a generar una respuesta debido a preocupaciones de seguridad.
response = client.messages.create(
model="claude-sonnet-4-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 solicitudSi encuentras razones de parada refusal frecuentemente mientras usas Claude Sonnet 4.5 u Opus 4.1, puedes intentar actualizar tus llamadas a la API para usar Sonnet 4 (claude-sonnet-4-20250514), que tiene restricciones de uso diferentes. Aprende más sobre comprender los filtros de seguridad de la API de Sonnet 4.5.
Para aprender más sobre rechazos desencadenados por filtros de seguridad de la API para Claude Sonnet 4.5, consulta Comprender los filtros de seguridad de la API de Sonnet 4.5.
Claude se detuvo porque alcanzó el límite de la ventana de contexto del modelo. Esto te permite solicitar el máximo de tokens posibles sin conocer el tamaño exacto de la entrada.
# Solicitud con tokens máximos para obtener lo máximo posible
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=64000, # Tokens de salida máximos del modelo
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 fue limitada por la ventana de contextoEsta razón de parada está disponible por defecto en Sonnet 4.5 y modelos más nuevos. Para modelos anteriores, usa el encabezado beta model-context-window-exceeded-2025-08-26 para habilitar este comportamiento.
Acostúmbrate a verificar el stop_reason en tu lógica de manejo de respuestas:
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:
# Maneja end_turn y otros casos
return response.content[0].textCuando una respuesta se trunca debido a límites de tokens o ventana de contexto:
def handle_truncated_response(response):
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
# Opción 1: Advierte al usuario sobre el límite específico
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}"
# Opción 2: Continúa la generación
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": "Please continue"}]
)
return response.content[0].text + continuation.content[0].textPara herramientas del servidor que pueden pausarse:
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 responseEs importante distinguir entre valores de stop_reason y errores reales:
try:
response = client.messages.create(...)
# Maneja respuesta exitosa con stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIError as e:
# Maneja errores reales
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")Cuando se usa transmisión, stop_reason es:
null en el evento inicial message_startmessage_deltawith 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"Stream ended with: {stop_reason}")Más simple con ejecutor de herramientas: El ejemplo a continuación muestra manejo manual de herramientas. Para la mayoría de casos de uso, el ejecutor de herramientas maneja automáticamente la ejecución de herramientas con mucho menos código.
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":
# Ejecuta 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 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-sonnet-4-5",
messages=messages,
max_tokens=4096
)
full_response += response.content[0].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_responseCon la razón de parada model_context_window_exceeded, puedes solicitar el máximo de tokens posibles sin calcular el tamaño de entrada:
def get_max_possible_tokens(client, prompt):
"""
Obtén tantos tokens como sea posible dentro de la ventana de contexto del modelo
sin necesidad de calcular el conteo de tokens de entrada
"""
response = client.messages.create(
model="claude-sonnet-4-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=64000 # Establece a los tokens de salida máximos del modelo
)
if response.stop_reason == "model_context_window_exceeded":
# Obtuviste el máximo de tokens posibles dado el tamaño de entrada
print(f"Generated {response.usage.output_tokens} tokens (context limit reached)")
elif response.stop_reason == "max_tokens":
# Obtuviste exactamente los tokens solicitados
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# Finalización natural
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return response.content[0].textAl manejar correctamente los valores de stop_reason, puedes construir aplicaciones más robustas que manejen con elegancia diferentes escenarios de respuesta y proporcionen mejores experiencias de usuario.