Torna alle guide
Risoluzione dei problemi·28 agosto 2026·6 min di lettura

API Claude 400 «tool_use ids were found without tool_result blocks» — la regola dell'ordine

È un errore di ordinamento dei messaggi, non un errore degli strumenti. Claude richiede che ogni blocco tool_use in un turno assistant riceva risposta da un blocco tool_result nel turno user immediatamente successivo — stessi ID e nient'altro in mezzo. Il tuo ciclo ne ha eliminato uno, di solito perché lo strumento ha generato un'eccezione.

Ultima revisione: .

È un errore di ordinamento dei messaggi, non un errore degli strumenti. Claude richiede che ogni blocco tool_use in un turno assistant riceva risposta da un blocco tool_result nel turno user immediatamente successivo — stessi ID e nient'altro in mezzo. Il tuo ciclo ne ha eliminato uno, di solito perché lo strumento ha generato un'eccezione.

L’errore

risposta (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."
  }
}

Cause e soluzioni in breve

CausaSoluzione
Lo strumento ha generato un'eccezione, quindi non è stato aggiunto nullaInvia comunque un tool_result, con is_error: true e il testo dell'errore.
Il tool_result è finito in un messaggio successivoDeve essere il messaggio immediatamente successivo: non può esserci alcun turno assistant o user in mezzo.
tool_use_id non corrispondeRipeti l'ID esatto del blocco tool_use; non generarne uno nuovo.
Cronologia ridotta a metà del round-tripRiduci la cronologia a round-trip completi dello strumento, mai tra le due metà dello stesso round-trip.

L'invariante, una volta per tutte

Ogni blocco tool_use in un turno assistant richiede esattamente un blocco tool_result nel messaggio user immediatamente successivo, con lo stesso tool_use_id. Più blocchi tool_use nello stesso turno richiedono più blocchi tool_result in quell'unico messaggio successivo. Nulla può essere inserito tra i due turni.

Rispondi sempre, anche quando lo strumento fallisce

Il modello gestisce perfettamente uno strumento fallito; non può gestire uno strumento mancante. Restituire l'errore come tool_result mantiene valida la conversazione e di solito produce un recupero sensato invece di 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})

Convalida gli ultimi due turni prima dell'invio

Una dozzina di righe di asserzioni intercetta il problema nel punto della chiamata invece di ricevere un 400 dalla rete: esamina gli ID tool_use del turno assistant e verifica che il turno user successivo risponda a tutti.

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}"

Riduci la cronologia ai confini del round-trip

La riduzione della finestra di contesto per numero di messaggi prima o poi taglierà tra un tool_use e il suo tool_result. Tratta la coppia come un'unità indivisibile quando decidi cosa eliminare.

Se chiami tramite Kunavo

Questo problema è nel tuo payload e Kunavo non lo maschera: 400 è nell'elenco degli errori per cui non si ritenta, quindi un round-trip malformato fallisce una volta invece di consumare un secondo round-trip upstream di latenza per arrivare allo stesso errore, e la richiesta rifiutata viene registrata a costo zero. Su /v1/messages parli direttamente il protocollo Messages, quindi i blocchi tools, tool_use e tool_result vengono inoltrati senza traduzione. In questo caso il 400 restituisce il tipo invalid_request_error, come nell'API Anthropic, con il testo del messaggio upstream seguito dall'ID della richiesta upstream e senza un campo request_id; fino al 24 settembre 2026 il tipo era api_error, quindi se i tuoi log risalgono a prima conviene basarsi sullo stato HTTP e sul testo del messaggio.

Domande frequenti

Posso semplicemente eliminare il turno tool_use invece di rispondervi?

Sì, se elimini l'intero turno assistant. Ciò che non è valido è mantenere tool_use e omettere il suo tool_result.

L'endpoint compatibile con OpenAI ha la stessa regola?

È richiesta la stessa associazione, con una sintassi diversa: tool_calls nel messaggio assistant, poi un messaggio con role: "tool" per ogni chiamata, contenente tool_call_id.

Una richiesta rifiutata ha un costo?

Su Kunavo, no. Le richieste fallite vengono registrate a costo zero e non raggiungono mai una chiamata upstream fatturata.

Guide correlate

Ulteriori dettagli sul significato degli errori sono disponibili in riferimento agli errori; ottenere una chiave richiede un minuto tramite registrazione e la guida all’autenticazione.