Volver a las guías
Solución de problemas·28 de agosto de 2026·6 min de lectura

Claude API 400 «tool_use ids were found without tool_result blocks» — la regla de orden

Este es un error de orden de mensajes, no de herramientas. Claude exige que cada bloque tool_use de un turno assistant sea respondido por un bloque tool_result en el turno user inmediatamente posterior: los mismos ids y nada entre ambos. Su bucle omitió uno, normalmente porque la herramienta lanzó un error.

Última revisión: .

Este es un error de orden de mensajes, no de herramientas. Claude exige que cada bloque tool_use de un turno assistant sea respondido por un bloque tool_result en el turno user inmediatamente posterior: los mismos ids y nada entre ambos. Su bucle omitió uno, normalmente porque la herramienta lanzó un error.

El error

response (HTTP 400)
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "messages.1: tool_use ids were found without tool_result blocks immediately after: toolu_01A... Each tool_use block must have a corresponding tool_result block in the next message."
  }
}

Causas y soluciones de un vistazo

CausaSolución
La herramienta lanzó un error, por lo que no se añadió nadaEnvíe igualmente un tool_result, con is_error: true y el texto del error.
El tool_result llegó en un mensaje posteriorDebe ser el mensaje inmediatamente siguiente: no puede haber un turno assistant o user entre ambos.
tool_use_id no coincideRepita exactamente el id del bloque tool_use; no lo genere de nuevo.
El historial se recortó a mitad del intercambioRecorte siempre en intercambios completos de herramienta, nunca entre las dos mitades de uno.

La invariante, expresada una vez

Cada bloque tool_use de un turno assistant necesita exactamente un bloque tool_result en el mensaje user inmediatamente siguiente, con el mismo tool_use_id. Si hay varios bloques tool_use en un turno, ese único mensaje siguiente necesita varios bloques tool_result. No puede haber nada entre ambos turnos.

Responda siempre, incluso cuando falle la herramienta

El modelo gestiona perfectamente una herramienta fallida; no puede gestionar una ausente. Devolver el error como tool_result mantiene válida la conversación y normalmente permite una recuperación razonable en lugar de un 400.

tool_loop.py
results = []
for block in (b for b in resp.content if b.type == "tool_use"):
    try:
        out = run_tool(block.name, block.input)
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": str(out),
        })
    except Exception as e:
        # A failed tool still owes the model an answer.
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": f"Tool failed: {e}",
            "is_error": True,
        })

messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": results})

Valide los dos últimos turnos antes de enviar

Una docena de líneas de aserciones detecta el problema en el punto de llamada, en lugar de recibir un 400 de la red: recorra los ids tool_use del turno assistant y confirme que el siguiente turno user responde a todos.

validate.py
def check_pairs(messages):
    for i, m in enumerate(messages):
        if m["role"] != "assistant" or not isinstance(m.get("content"), list):
            continue
        ids = {b.get("id") for b in m["content"]
               if isinstance(b, dict) and b.get("type") == "tool_use"}
        if not ids:
            continue
        nxt = messages[i + 1] if i + 1 < len(messages) else None
        answered = {b.get("tool_use_id") for b in (nxt or {}).get("content", [])
                    if isinstance(b, dict) and b.get("type") == "tool_result"}
        missing = ids - answered
        assert not missing, f"message {i}: unanswered tool_use {missing}"

Recorte el historial en los límites de los intercambios

El recorte de la ventana de contexto por número de mensajes acabará separando un tool_use de su tool_result. Trate la pareja como una unidad indivisible al decidir qué eliminar.

Si llamas a través de Kunavo

Este problema está en su payload y Kunavo no lo oculta: 400 figura en la lista de errores que no se reintentan, por lo que un intercambio de herramientas mal formado falla una vez en lugar de gastar la latencia de un segundo intercambio ascendente para llegar al mismo error, y la solicitud rechazada se registra con coste cero. En /v1/messages habla directamente el protocolo Messages, por lo que los bloques tools, tool_use y tool_result se reenvían sin traducirse. Allí el 400 vuelve tipificado como invalid_request_error, igual que en la API de Anthropic, con el texto del mensaje del proveedor seguido del propio request id del proveedor y sin campo request_id; hasta el 24 de septiembre de 2026 el tipo era api_error, así que, si sus registros son anteriores, use el estado HTTP y el texto del mensaje para decidir la rama.

Preguntas frecuentes

¿Puedo simplemente eliminar el turno tool_use en lugar de responderlo?

Sí, si elimina el turno assistant completo. Lo que no es válido es conservar tool_use y omitir su tool_result.

¿El endpoint compatible con OpenAI tiene la misma regla?

Se requiere la misma correspondencia, con otra sintaxis: tool_calls en el mensaje assistant y después un mensaje role: "tool" por cada llamada, con tool_call_id.

¿Cuesta algo una solicitud rechazada?

En Kunavo, no. Las solicitudes fallidas se registran con coste cero y nunca llegan a una llamada ascendente facturada.

Guías relacionadas

Encontrarás más detalles sobre el significado de los errores en referencia de errores; obtener una clave lleva un minuto mediante registro y la guía de autenticación.