Indice

Claude Opus 5.5: l'effort come unica manopola

Sintesi della lezione basata sulla pagina di lancio "Claude Opus 5.5" e sulla documentazione ufficiale della piattaforma.

Fonte: https://www.anthropic.com/claude-opus-5-5

Il concetto in una frase

Claude Opus 5.5 (id di modello claude-opus-5-5, uscito il 22 settembre 2026) non è interessante per i benchmark: è interessante perché chiude una porta e ne apre una sola. Il ragionamento interno del modello non si può più spegnere, e di conseguenza l'unico volante rimasto per decidere quanto il modello pensa — e quindi quanto costa e quanto ci mette — è un singolo parametro: effort.

Concetto

Tre termini da fissare prima di andare avanti.

Token
l'unità in cui il modello legge e scrive (circa 0,55 parole sul tokenizer attuale). Si paga a milione di token: Opus 5.5 costa 4 $ per milione in input e 20 $ per milione in output, contro i 5 $/25 $ di Opus 5. Finestra di contesto 1M token, output massimo 128K.
Thinking block (blocco di ragionamento)
un blocco di contenuto che il modello produce prima della risposta visibile, in cui ragiona. Sono token generati, quindi si pagano come output, anche quando non li vedi.
Adaptive thinking (ragionamento adattivo)
la modalità in cui è il modello a decidere turno per turno se e quanto pensare. Sostituisce il vecchio schema manuale thinking: {type: "enabled", budget_tokens: N}, dove era l'utente a fissare un tetto rigido di token di ragionamento. Su Opus 5.5 l'adaptive thinking è sempre attivo e non disattivabile: sia thinking: {"type": "disabled"} sia {"type": "enabled", "budget_tokens": N} restituiscono un errore 400.

Perché esiste questo assetto. Il modo vecchio di controllare il costo era binario e grezzo: o si spegne il ragionamento, o gli si dà un budget fisso di token che non ha idea di quanto sia difficile il problema davanti. Entrambi sbagliati per motivi diversi — spegnere il ragionamento su Opus 5 produceva persino fallimenti silenziosi, con il modello che scriveva una chiamata a tool nel testo visibile invece che in un blocco tool_use, e la chiamata non partiva mai. effort risolve il problema spostando il controllo da "quanti token di pensiero" a "quanto ti impegni": è un segnale comportamentale, non un tetto. Sui problemi facili il modello smette da solo, su quelli difficili continua.

Il secondo pezzo del concetto è la contropartita di questa scelta. Se il ragionamento è sempre presente e viene rimandato indietro a ogni turno della conversazione, diventa un bene estraibile: chi ha accesso all'API potrebbe raccogliere catene di ragionamento di un modello di frontiera e usarle per addestrarne uno più economico (distillazione). Per impedirlo, Opus 5.5 introduce la preserved thinking: ogni blocco di ragionamento viene firmato crittograficamente e legato al prefisso esatto della conversazione in cui è nato. Se si modifica qualcosa che stava prima di quel blocco, la firma non torna e la richiesta viene rifiutata. Il risultato pratico, per chi scrive codice, è un vincolo architetturale netto: la conversazione diventa append-only.

Schema / Flusso

Le due manopole e i due vincoli, nel ciclo di una richiesta agentica:

output_config.effort: low · medium · high · xhigh · max LA RICHIESTA — system + tools + messages[...] (prefisso firmato: NON si tocca più) max_tokens tetto DURO su thinking + testo Opus 5.5 thinking: sempre on response.content = [ thinking(firmato), text, tool_use, ... ] APPEND: response.content INTEGRALE + user message con i tool_result ricomincia il giro

La macchina di validazione della preserved thinking, sul giro successivo:

richiesta N+1 il prefisso (system + tools + messaggi precedenti) è byte-identico? NO firma valida il modello riusa il proprio ragionamento default (account nati dal 31/08/2026) HTTP 400 — "Invalid `signature` in `thinking` block. The block is bound to a different conversation." oppure prefix_mismatch_behavior: "drop_block" (beta thinking-binding-controls-2026-08-01) il blocco viene scartato in silenzio; la risposta riporta input_transformations: [{type: "thinking_dropped", path: "messages.1.content.0"}]

Approfondimento tecnico

I cinque livelli di effort, e la trappola del default

I valori ammessi sono low, medium, high, xhigh, max, si passano dentro output_config (non al primo livello della richiesta) e non richiedono header beta. Il punto che fa inciampare tutti in migrazione: su Opus 5.5 il default è medium, mentre su ogni altro modello che supporta effort è high. Una richiesta che oggi omette effort girava a high su Opus 5 e girerà a medium su Opus 5.5 — un gradino più in basso, a parità di codice. Se si cambia solo la stringa del modello e la qualità cala, è quasi sempre questo, non il modello.

Cosa scegliere: low per i sotto-agenti e i compiti meccanici (meno chiamate a tool, più consolidate, zero preamboli); medium per il lavoro agentico ordinario; high per il ragionamento difficile; xhigh per le sessioni lunghe oltre la mezz'ora con budget di token nell'ordine dei milioni; max quando la correttezza vale più del costo. La documentazione insiste su un punto controintuitivo: non riportare i livelli da un modello precedente, va rifatto lo sweep sulle proprie eval. La stessa etichetta significa cose diverse su modelli diversi.

Effort non è un budget

È l'errore concettuale più comune. Ci sono tre grandezze distinte che si confondono facilmente:

ParametroNaturaChi lo vedeCosa succede se si esaurisce
output_config.effortsegnale comportamentale, nessun numeroil modello lo "sente"niente: non si esaurisce
max_tokenstetto duro per risposta (thinking + testo)il modello non lo satroncamento a metà frase
output_config.task_budgetbudget indicativo in token per l'intero loop agenticoil modello vede un contatore alla rovesciail modello chiude con grazia

Corollario operativo: ai livelli alti di effort va alzato max_tokens, altrimenti il modello si tronca mentre ragiona. 64K è un punto di partenza ragionevole; oltre i valori grandi serve lo streaming, perché una risposta non-streaming lunga sbatte contro i timeout HTTP dell'SDK.

Cambiare effort a metà conversazione senza bruciare la cache

Il prompt caching (memorizzazione del prefisso della richiesta per non ripagarlo a ogni turno) funziona per corrispondenza di prefisso: cambia un byte all'inizio e cade tutto quello che viene dopo. Siccome effort al primo livello della richiesta influenza il rendering del prompt, cambiarlo tra una richiesta e l'altra azzera la cache. Opus 5.5 offre la via d'uscita: un messaggio role: "system" con content vuoto e il nuovo output_config.effort dentro l'array messages, sotto header beta mid-conversation-output-config-2026-07-01. Il nuovo livello vale dal turno utente successivo e il prefisso resta intatto. Su Opus 5.5 la cache in lettura costa il 5% del prezzo base di input (0,20 $/MTok), quindi non è un dettaglio: una sessione agentica lunga paga o non paga a seconda di questo.

Cosa invalida la firma dei blocchi di ragionamento

Il prefisso controllato è: il prompt system di primo livello, l'array tools, e tutti i messaggi precedenti al blocco.

Invalidano: modificare/riordinare/cancellare un messaggio passato, cambiare il system prompt, aggiungere o togliere un tool, accorciare il contenuto di un vecchio tool_result, ri-codificare un'immagine, rimettere a posto un blocco di thinking tolto prima, togliere un blocco di thinking in mezzo tenendo quelli dopo.

Non invalidano: appendere messaggi nuovi, cambiare effort/max_tokens/tool_choice/metadata, aggiungere o togliere marcatori cache_control, togliere i blocchi di thinking dall'inizio, dalla fine, o tutti.

Le vie legittime per fare le cose che prima si facevano editando il prefisso:

Cambiare istruzioni

messaggio system a metà conversazione, invece di riscrivere il system di primo livello.

Cambiare i tool

blocchi tool_addition / tool_removal (beta inline-tools-2026-09-15).

Aggiornare il contesto

va messo nell'ultimo messaggio utente, non rigenerando il primo.

Tagliare la storia vecchia

compaction lato server, non cancellazione lato client.

Da notare: se un account è nato prima del 31 agosto 2026 il controllo non scatta per default, ma scrivere append-only comunque è l'unica scelta che fa funzionare il codice su entrambi i regimi senza doverlo toccare dopo.

Le altre tre rotture rispetto a Opus 5

  1. Il forced tool use non esiste più. tool_choice di tipo any e tool danno 400 (tool_choice: type "tool" and "any" are not supported for this model.), anche sull'endpoint di conteggio token. Il sostituto è tool_choice: {"type": "auto"} più strict: true sulla definizione del tool (garantisce che l'input validi contro lo schema) oppure gli structured outputs, dicendo nel prompt quando il tool si applica.
  2. Computer use vecchio stile fuori uso. computer_20251124 dà 400 su Claude API e Google Cloud — va dichiarato il toolset computer_toolset_20260801, senza header beta, senza name e senza dimensioni dello schermo (su Amazon Bedrock il vecchio continua invece a funzionare).
  3. Il testo "di progresso" cambia contenitore. Il testo che il modello scriveva tra una chiamata a tool e l'altra ora torna dentro blocchi thinking di progress-update, non più come blocchi text. Siccome il default di thinking.display è "omitted", quel campo arriva vuoto: nessuna richiesta fallisce, ma l'interfaccia smette di mostrare avanzamenti e sembra impiantata. Per riaverli: display: "updates" (beta thinking-display-updates-2026-08-18, mostra solo gli avanzamenti tenendo nascosto il ragionamento) o "summarized" (mostra entrambi, mescolati). Questa è la rottura più insidiosa perché non dà errore.

Rifiuti

Opus 5.5 può restituire HTTP 200 con stop_reason: "refusal" e un stop_details.category — le categorie coperte sono più ampie che su Opus 5 ("bio", "cyber", "reasoning_extraction"). Va sempre controllato stop_reason prima di leggere content. Il fallback lato server non ritenta i rifiuti di categoria "reasoning_extraction": quello torna al chiamante.

Come si colloca rispetto ai vicini

Non va confuso con Fable 5.1: Fable resta il modello per il ragionamento più esigente e le sessioni lunghissime, costa 10 $/50 $ per milione e ha default high; la regola pratica della documentazione è partire da Opus 5.5 e salire a Fable solo quando le eval su Opus 5.5 ad alto effort restano insufficienti.

E non va confuso effort con la compaction o il context editing: effort governa quanto il modello produce, la compaction riassume la storia quando ci si avvicina al limite di contesto, il context editing cancella i vecchi risultati dei tool. Tre leve su tre problemi diversi — costo di generazione, saturazione del contesto, rumore nel contesto.

Esempio pratico

Scenario. Un servizio interno che smista ticket di supporto gira oggi su claude-opus-5 con tre tool (search_kb, get_customer, create_jira), con thinking: {"type": "disabled"} perché qualcuno l'aveva messo per risparmiare, e con tool_choice forzato su create_jira nella fase finale. Va portato su Opus 5.5 per il taglio di prezzo del 20%, senza perdere qualità e senza bruciare la cache.

Passi.

  1. Cambia l'id del modello da claude-opus-5 a claude-opus-5-5.
  2. Togli il blocco thinking e sostituiscilo con un livello di effort esplicito. Dove si spegneva il ragionamento per risparmiare, il rimpiazzo è low o medium, non l'assenza del parametro.
  3. Sostituisci il forced tool use con auto + strict: true e un'istruzione esplicita nel prompt.
  4. Rendi il loop append-only: appendi response.content per intero, non solo il testo. È l'errore che rompe tutto, perché estrarre la stringa e riappendere quella butta via i blocchi di thinking firmati.
pythonimport anthropic, json

client = anthropic.Anthropic()

TOOLS = [
    {
        "name": "create_jira",
        "description": "Apre un ticket Jira. Usalo solo dopo aver identificato cliente e categoria.",
        "strict": True,                      # sostituisce il tool_choice forzato
        "input_schema": {
            "type": "object",
            "properties": {"summary": {"type": "string"},
                           "customer_id": {"type": "string"}},
            "required": ["summary", "customer_id"],
            "additionalProperties": False,   # obbligatorio con strict
        },
    },
    # ... search_kb, get_customer
]

SYSTEM = "Sei il triage dei ticket. Quando cliente e categoria sono noti, apri il ticket con create_jira."

messages = [{"role": "user", "content": "Il cliente ACME non riesce ad accedere dal 3 marzo."}]

while True:
    resp = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=16000,
        system=SYSTEM,                        # fisso per tutta la sessione: è nel prefisso firmato
        tools=TOOLS,                          # idem: non toccarlo a metà conversazione
        tool_choice={"type": "auto"},         # "tool"/"any" darebbero 400
        output_config={"effort": "medium"},   # default esplicito; niente campo `thinking`
        messages=messages,
    )

    if resp.stop_reason == "refusal":
        raise RuntimeError(f"rifiuto: {resp.stop_details.category}")

    # APPEND-ONLY: l'intero content, blocchi thinking firmati compresi
    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason != "tool_use":
        break

    results = []
    for block in resp.content:
        if block.type == "tool_use":
            out = dispatch(block.name, block.input)   # esecuzione lato applicazione
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": json.dumps(out)})
    # tutti i tool_result in UN SOLO messaggio utente
    messages.append({"role": "user", "content": results})

print(resp.usage.cache_read_input_tokens)   # se resta 0, qualcosa sta invalidando il prefisso
  1. Chiudi in low senza perdere la cache. Il riassunto finale per l'operatore non merita medium: va inserito un messaggio system a solo effort prima dell'ultimo turno utente.
pythonresp = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=4096,
    system=SYSTEM,
    output_config={"effort": "medium"},
    messages=messages + [
        {"role": "system", "content": [], "output_config": {"effort": "low"}},
        {"role": "user", "content": "Riassumi in tre righe per l'operatore."},
    ],
    betas=["mid-conversation-output-config-2026-07-01"],
)
  1. Fai lo sweep di effort sulle eval di regressione: gira il set di ticket a low, medium, high e confronta accuratezza contro token spesi. Non ereditare il livello dal codice Opus 5.

Risultato atteso. Il loop gira senza 400. usage.cache_read_input_tokens è diverso da zero dal secondo turno in poi, perché system e tools non cambiano mai e i messaggi si limitano a crescere in coda — se resta a zero, c'è un invalidatore silenzioso in giro (un timestamp nel system prompt, un ordine di chiavi JSON che varia, un tool aggiunto a runtime). Il costo per ticket scende di circa il 20% per il solo cambio di listino, più quanto si guadagna scendendo di effort dove le eval reggono. Se l'interfaccia mostrava all'operatore il testo tra una chiamata e l'altra, quella parte è ora muta: per riaverla va aggiunto thinking: {"type": "adaptive", "display": "updates"} con il beta thinking-display-updates-2026-08-18, renderizzando i blocchi thinking non vuoti prima del tool_use che precedono.

Riferimenti