Cuando realizas una solicitud a la API de Messages, la respuesta de Claude incluye un campo stop_reason que indica por qué el modelo dejó de generar su respuesta. Entender estos valores es crucial para construir aplicaciones robustas que manejen diferentes tipos de respuesta de manera apropiada. Para detalles sobre stop_reason en la respuesta de la API, consulta la referencia de la API de Messages.

¿Qué es stop_reason?

El campo stop_reason es parte de cada respuesta exitosa de la API de Messages. A diferencia de los errores, que indican fallas en el procesamiento de tu solicitud, stop_reason te dice por qué Claude completó exitosamente la generación de su respuesta.
Example response
{
  "id": "msg_01234",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "Aquí está la respuesta a tu pregunta..."
    }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 100,
    "output_tokens": 50
  }
}

Valores de razón de parada

end_turn

La razón de parada más común. Indica que Claude terminó su respuesta de manera natural.
if response.stop_reason == "end_turn":
    # Procesar la respuesta completa
    print(response.content[0].text)

Respuestas vacías con end_turn

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:
  • Agregar bloques de texto inmediatamente después de resultados de herramientas (Claude aprende a esperar que el usuario siempre inserte texto después de resultados de herramientas, por lo que termina su turno para seguir el patrón)
  • Enviar la respuesta completada de Claude de vuelta sin agregar nada (Claude ya decidió que terminó, por lo que permanecerá terminado)
Cómo prevenir respuestas vacías:
# INCORRECTO: Agregar texto inmediatamente después de tool_result
messages = [
    {"role": "user", "content": "Calcula la suma de 1234 y 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": "Aquí está el resultado"  # No agregues texto después de tool_result
        }
    ]}
]

# CORRECTO: Enviar resultados de herramientas directamente sin texto adicional
messages = [
    {"role": "user", "content": "Calcula la suma de 1234 y 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
    )

    # Verificar 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 terminó

        # CORRECTO: Agregar un prompt de continuación en un NUEVO mensaje de usuario
        messages.append({"role": "user", "content": "Por favor continúa"})

        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=messages
        )

    return response
Mejores prácticas:
  1. Nunca agregues bloques de texto inmediatamente después de resultados de herramientas - Esto enseña a Claude a esperar entrada del usuario después de cada uso de herramienta
  2. No reintentes respuestas vacías sin modificación - Simplemente enviar la respuesta vacía de vuelta no ayudará
  3. Usa prompts de continuación como último recurso - Solo si las correcciones anteriores no resuelven el problema

max_tokens

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": "Explica la física cuántica"}]
)

if response.stop_reason == "max_tokens":
    # La respuesta fue truncada
    print("La respuesta fue cortada en el límite de tokens")
    # Considera hacer otra solicitud para continuar

stop_sequence

Claude encontró una de tus secuencias de parada personalizadas.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    stop_sequences=["FIN", "PARAR"],
    messages=[{"role": "user", "content": "Genera texto hasta que digas FIN"}]
)

if response.stop_reason == "stop_sequence":
    print(f"Se detuvo en la secuencia: {response.stop_sequence}")

tool_use

Claude está llamando a una herramienta y espera que la ejecutes.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=[weather_tool],
    messages=[{"role": "user", "content": "¿Cómo está el clima?"}]
)

if response.stop_reason == "tool_use":
    # Extraer y ejecutar la herramienta
    for content in response.content:
        if content.type == "tool_use":
            result = execute_tool(content.name, content.input)
            # Devolver resultado a Claude para respuesta final

pause_turn

Usado con herramientas de 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": "Busca las últimas noticias de IA"}]
)

if response.stop_reason == "pause_turn":
    # Continuar 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"}]
    )

refusal

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": "[Solicitud insegura]"}]
)

if response.stop_reason == "refusal":
    # Claude se negó a responder
    print("Claude no pudo procesar esta solicitud")
    # Considera reformular o modificar la solicitud
Si 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 diferentes restricciones de uso. Aprende más sobre entender los filtros de seguridad de la API de Sonnet 4.5.
Para aprender más sobre rechazos activados por filtros de seguridad de la API para Claude Sonnet 4.5, consulta Entendiendo los Filtros de Seguridad de la API de Sonnet 4.5.

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 de tokens posibles sin conocer el tamaño exacto de la entrada.
# Solicitud con tokens máximos para obtener tanto como sea posible
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=64000,  # Tokens máximos de salida del modelo
    messages=[{"role": "user", "content": "Entrada grande que usa la mayor parte de la ventana de contexto..."}]
)

if response.stop_reason == "model_context_window_exceeded":
    # La respuesta alcanzó el límite de la ventana de contexto antes de max_tokens
    print("La respuesta alcanzó el límite de la ventana de contexto del modelo")
    # La respuesta sigue siendo válida pero fue limitada por la ventana de contexto
Esta 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.

Mejores prácticas para manejar razones de parada

1. Siempre verificar stop_reason

Haz un hábito de 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:
        # Manejar end_turn y otros casos
        return response.content[0].text

2. Manejar respuestas truncadas con gracia

Cuando una respuesta es truncada 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: Advertir al usuario sobre el límite específico
        if response.stop_reason == "max_tokens":
            message = "[Respuesta truncada debido al límite de max_tokens]"
        else:
            message = "[Respuesta truncada debido al límite de ventana de contexto]"
        return f"{response.content[0].text}\n\n{message}"

        # Opción 2: Continuar 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": "Por favor continúa"}]
        )
        return response.content[0].text + continuation.content[0].text

3. Implementar lógica de reintento para pause_turn

Para herramientas de servidor que pueden pausar:
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 response

Razones de parada vs. errores

Es importante distinguir entre valores de stop_reason y errores reales:

Razones de parada (respuestas exitosas)

  • 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 fallas en el procesamiento de solicitudes
  • La respuesta contiene detalles del error
try:
    response = client.messages.create(...)
    
    # Manejar respuesta exitosa con stop_reason
    if response.stop_reason == "max_tokens":
        print("La respuesta fue truncada")
    
except anthropic.APIError as e:
    # Manejar errores reales
    if e.status_code == 429:
        print("Límite de tasa excedido")
    elif e.status_code == 500:
        print("Error del servidor")

Consideraciones de streaming

Cuando usas streaming, stop_reason es:
  • null en el evento inicial message_start
  • Proporcionado en el evento message_delta
  • No proporcionado en ningún otro evento
with 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 terminó con: {stop_reason}")

Patrones comunes

Manejar 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-sonnet-4-5",
            messages=messages,
            tools=tools
        )
        
        if response.stop_reason == "tool_use":
            # Ejecutar herramientas y continuar
            tool_results = execute_tools(response.content)
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_results})
        else:
            # Respuesta final
            return response

Asegurar 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-sonnet-4-5",
            messages=messages,
            max_tokens=4096
        )

        full_response += response.content[0].text

        if response.stop_reason != "max_tokens":
            break

        # Continuar desde donde se quedó
        messages = [
            {"role": "user", "content": prompt},
            {"role": "assistant", "content": full_response},
            {"role": "user", "content": "Por favor continúa desde donde te quedaste."}
        ]

    return full_response

Obtener tokens máximos sin conocer el tamaño de entrada

Con 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):
    """
    Obtener 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  # Establecer a los tokens máximos de salida del modelo
    )

    if response.stop_reason == "model_context_window_exceeded":
        # Obtuvo el máximo de tokens posibles dado el tamaño de entrada
        print(f"Generó {response.usage.output_tokens} tokens (límite de contexto alcanzado)")
    elif response.stop_reason == "max_tokens":
        # Obtuvo exactamente los tokens solicitados
        print(f"Generó {response.usage.output_tokens} tokens (max_tokens alcanzado)")
    else:
        # Completación natural
        print(f"Generó {response.usage.output_tokens} tokens (completación natural)")

    return response.content[0].text
Al manejar apropiadamente los valores de stop_reason, puedes construir aplicaciones más robustas que manejen con gracia diferentes escenarios de respuesta y proporcionen mejores experiencias de usuario.