Come evitare che i cicli degli agenti consumino il tuo budget API
Scriverò direttamente l'articolo, con tecniche ingegneristiche concrete, una tabella comparativa ed esempi pratici. Lo manterrò ancorato al comportamento reale dei cicli e ai controlli dei costi, e mi assicurerò che si concluda con indicazioni applicabili.## Il modo più rapido per bruciare un budget API
Una volta ho visto un agente di programmazione “semplice” trasformare una richiesta utente di 400 token in una voce di fattura da 28,000 token in meno di due minuti.
Lo schema era fin troppo familiare: l'agente chiamava uno strumento di ricerca, riassumeva i risultati, rileggeva il proprio riepilogo, interrogava di nuovo lo stesso strumento con una formulazione leggermente diversa, poi ampliava il contesto finché ogni turno includeva l'intera conversazione, metà del repository e una scia di piani falliti. Il modello non aveva nulla che non andasse. Era la progettazione del ciclo a essere sbagliata.
Questo è il problema centrale dei sistemi ad agenti: il costo non cresce in funzione dei prompt degli utenti, ma in funzione del comportamento del ciclo. Un solo ciclo mal progettato può moltiplicare:
- i token di input a causa di un contesto in continua crescita,
- i token di output a causa di un “ragionamento” prolisso,
- la spesa per gli strumenti a causa di chiamate ripetute,
- e la latenza a causa di tentativi ripetuti in serie.
Se sviluppi agenti per la produzione, il vero lavoro non è “rendere il modello più intelligente”. È “rendere il ciclo meno costoso, più breve e meno incline a sfuggire al controllo”.
Perché i cicli degli agenti fanno esplodere i costi
Un normale turno di chat ha un input e un output. Un ciclo di un agente ha una sequenza di questo tipo:
- l'utente pone una domanda,
- il modello pianifica,
- il modello chiama uno strumento,
- lo strumento restituisce dati,
- il modello legge i dati,
- il modello decide il passo successivo,
- si ripete.
Ogni iterazione aggiunge nuovi token al prompt dell'iterazione successiva. Se continui ad aggiungere gli output completi degli strumenti e i ragionamenti precedenti, il prompt cresce più o meno a gradini.
In pratica, questo produce tre moltiplicatori dei costi:
- Accumulo del contesto: ogni passaggio porta con sé quelli precedenti.
- Chiamate eccessive agli strumenti: lo stesso strumento viene chiamato con argomenti leggermente diversi.
- Tentativi ripetuti fuori controllo: il modello continua a “provare ancora una cosa” quando ha già fatto abbastanza.
Un'insidia comune è che lo spreco di token di solito resta invisibile finché non esamini le tracce delle singole esecuzioni. La risposta finale può essere concisa, ma la catena nascosta può essere 10x più grande dell'output visibile all'utente.
Un semplice modello dei costi
Se un ciclo esegue N passaggi, ciascuno con:
Ctoken di contesto mantenuto,Itoken di output degli strumenti reinseriti nel contesto,Otoken di output del modello,
allora il totale approssimativo è:
total_tokens ≈ N * (C + I + O)
È una stima ottimistica, perché C di solito cresce man mano che il ciclo prosegue. In un agente reale, i turni successivi sono spesso i più costosi.
Le leve di controllo che contano davvero
Ci sono alcuni punti su cui puoi intervenire. Il segreto è combinarli.
| Controllo | Cosa riduce | Caso d'uso ideale | Compromesso |
|---|---|---|---|
| Limiti ai passaggi | Cicli fuori controllo | Qualsiasi agente con strumenti | Può interrompersi troppo presto nei compiti difficili |
| Riduzione del contesto | Token di input | Compiti di lunga durata | Rischio di perdere informazioni utili dalla cronologia |
| Sottoagenti meno costosi | Spesa per le attività di routine | Ricerca, estrazione, classificazione | Costi aggiuntivi di coordinamento |
| Caching dei risultati degli strumenti | Spesa per chiamate ripetute agli strumenti | Fonti stabili, query ripetute | Dati obsoleti e invalidazione |
| Osservabilità della spesa | Fatture inattese | Sistemi in produzione | Richiede lavoro di strumentazione |
| Condizioni di arresto dei cicli | Cicli infiniti o quasi infiniti | Agenti inaffidabili | Occorre definire cosa sia “sufficientemente buono” |
Le configurazioni di produzione più efficaci le utilizzano tutte.
1) Imposta limiti rigorosi ai passaggi
Questa è la soluzione più semplice e più sottovalutata: se non imposti un limite massimo rigido, ti affidi al modello perché si fermi da solo.
Di solito parto da:
3–5passaggi per compiti circoscritti,6–8passaggi per compiti di media complessità,- un numero maggiore solo quando c'è un motivo valido.
Un buon limite non è solo una valvola di sicurezza. Impone una migliore scomposizione del compito. Se l'agente ha a disposizione solo quattro mosse, tende a:
- richiedere prima lo strumento giusto,
- evitare deviazioni esplorative,
- e convergere più rapidamente verso una risposta utilizzabile.
Esempio di controllore del ciclo
MAX_STEPS = 5
for step in range(MAX_STEPS):
action = model.next_action(state)
if action.type == "final":
return action.final_answer
observation = run_tool(action.tool_name, action.arguments)
state = update_state(state, action, observation)
raise RuntimeError("Agent stopped after step cap")
Un'insidia comune: i limiti ai passaggi devono applicarsi all' intero piano, non a ciascuna sottoprocedura in modo indipendente. Altrimenti sposti l'esplosione dall'agente principale agli assistenti annidati.
2) Riduci il contesto a ogni turno
La maggior parte degli agenti conserva troppa cronologia. Non ha bisogno di tutte le risposte precedenti degli strumenti, soprattutto se una risposta era lunga e contava un solo dettaglio.
In pratica, ho ottenuto i risultati migliori con una politica di memoria a livelli:
- Conserva integralmente: richiesta dell'utente, obiettivo attuale, decisioni definitive, vincoli irrisolti.
- Riassumi: ragionamenti precedenti, tentativi su percorsi alternativi, output lunghi degli strumenti.
- Elimina: ipotesi senza sbocco, risultati duplicati degli strumenti, vecchie bozze intermedie.
Cosa conservare
Un oggetto di stato compatto funziona meglio della riproposizione della trascrizione integrale:
{
"goal": "Find the failing endpoint and propose a fix",
"constraints": [
"No schema changes",
"Must preserve backward compatibility"
],
"known_facts": [
"500s started after deploy 1842",
"Only /v1/search is affected"
],
"open_questions": [
"Is the new cache layer returning stale payloads?"
],
"step": 3
}
È molto meno costoso che fornire al modello 20 turni di «ragionamento ad alta voce».
Schema pratico per ridurre il contesto
Usa tre buffer:
working_set: turno corrente, output degli strumenti, piano attivo.summary_memory: cronologia compressa.archive: tutto ciò che potrebbe servirti per gli audit, ma non per il contesto del prompt.
L’agente vede solo working_set + summary_memory. Quando working_set diventa troppo grande, riassumilo in summary_memory.
L’insidia è la deriva del riepilogo. Se il riepilogo perde informazioni, può cancellare vincoli e indurre l’agente a ripetere gli stessi errori. Preferisco conservare i fatti strutturati separatamente dai riepiloghi in forma libera, così il modello non deve dedurli dal testo.
3) Usa sottoagenti più economici per le attività semplici
Non ogni passaggio merita il tuo modello migliore.
Se l’attività consiste nel:
- estrarre campi da JSON,
- classificare un ticket,
- verificare se un file è cambiato,
- o generare una breve query di ricerca,
spesso basta un modello più piccolo ed economico. Riserva il modello costoso alla pianificazione, alla sintesi e alla gestione delle ambiguità.
Suddivisione tipica
- Agente principale: ragiona sull’attività e decide quando fermarsi.
- Sottoagente: svolge attività circoscritte come ricerca, estrazione o normalizzazione.
- Verificatore: controlla il formato, la completezza o la presenza di errori evidenti negli output.
Un’architettura pratica:
User
-> Orchestrator (strong model)
-> Search agent (cheap model)
-> Extract agent (cheap model)
-> Final synthesis (strong model)
Perché funziona
Il modello principale resta concentrato sul flusso di controllo invece di fare tutto da solo. Il sottoagente può essere vincolato tramite il prompt e avere un limite rigido di token.
Esempio di prompt per un sottoagente di ricerca:
Return only:
1. the best search query,
2. 3 likely source categories,
3. no explanation.
Un insieme di requisiti così circoscritto mantiene gli output brevi e prevedibili.
Se la tua piattaforma offre accesso a più modelli a prezzi convenienti, è qui che ne trai vantaggio: un modello potente per l’orchestrazione, uno più economico per le sottoattività ripetitive e nessun motivo per consumare token premium nel parsing di routine.
4) Metti in cache i risultati degli strumenti con criterio
I cicli degli agenti spesso pagano più volte per le stesse informazioni:
- letture ripetute dei file,
- interrogazioni ripetute del database,
- ricerche web ripetute,
- chiamate ripetute per embedding o recupero di informazioni.
Mettere in cache gli output degli strumenti è una delle modifiche con il maggiore ritorno sull’investimento.
Usa chiavi semantiche per la cache, non il testo grezzo
Una cache basata sul testo grezzo del prompt non trova corrispondenze se la formulazione cambia leggermente. Le chiavi migliori si basano sull’intento normalizzato:
def cache_key(tool_name, args):
normalized = normalize_args(args)
return f"{tool_name}:{hash(normalized)}"
Esempio di voce della cache:
{
"tool": "repo_search",
"query": "auth middleware rate limit bug",
"result": {
"files": ["src/middleware/auth.py", "src/api/routes.py"],
"ts": 1710001123
}
}
Quando la cache è più utile
- Agenti che ispezionano repository,
- ricerca di documenti,
- basi di conoscenza stabili,
- trasformazioni deterministiche,
- recupero ripetuto di informazioni nei flussi di lavoro con più turni.
Quando la cache può essere dannosa
- dati che cambiano rapidamente,
- risultati specifici per l’utente,
- qualsiasi dato con requisiti di aggiornamento legati al tempo.
L’insidia comune consiste nel mettere in cache gli output degli strumenti senza comunicarne il grado di aggiornamento all’agente. Se il modello crede di aver ricevuto dati in tempo reale quando invece provengono dalla cache, potrebbe prendere decisioni sbagliate. Includi timestamp o intervalli di validità nei metadati degli strumenti.
5) Mostra la spesa nella dashboard
Se monitori solo la latenza e il tasso di successo, ti sfuggirà il vero problema: un successo costoso.
Per ogni esecuzione, devi poter osservare:
- token di input,
- token di output,
- chiamate agli strumenti,
- latenza degli strumenti,
- spesa totale,
- numero di passaggi,
- motivo dell’arresto.
Schema minimo degli eventi
{
"run_id": "r_8f2c",
"step": 4,
"model": "gpt-5.5",
"input_tokens": 1842,
"output_tokens": 221,
"tool_name": "repo_search",
"tool_latency_ms": 312,
"estimated_cost_usd": 0.087,
"stop_reason": "step_cap"
}
Cosa monitorare nella pratica
- Token per attività completata con successo: l’indicatore di costo più chiaro.
- Token per passaggio: rileva la crescita eccessiva dei prompt.
- Chiamate ripetute agli strumenti: di solito indicano un errore di pianificazione.
- Distribuzione dei motivi di arresto: se molte esecuzioni terminano perché raggiungono il limite, il tuo agente non pianifica a sufficienza.
Uno dei grafici più utili è quello del costo per tipo di attività. Alcune categorie saranno naturalmente costose. Va bene così. Ciò che devi individuare è l’attività che dovrebbe essere economica ma non lo è.
6) Interrompi tempestivamente i cicli fuori controllo
Un ciclo dovrebbe fermarsi per più motivi del solo «ho raggiunto il limite di passaggi».
Tra le condizioni di arresto utili ci sono:
- il modello ha prodotto una risposta finale,
- la stessa chiamata a uno strumento si ripete senza nuove informazioni,
- la variazione del livello di confidenza o dell’utilità è sotto la soglia,
- l’attività è risolta secondo un verificatore,
- il piano non fa più progressi.
Esempio di meccanismo di interruzione del ciclo
seen_calls = set()
for step in range(MAX_STEPS):
action = model.next_action(state)
signature = (action.tool_name, canonicalize(action.arguments))
if signature in seen_calls:
return fail("repeated tool call with no new information")
seen_calls.add(signature)
observation = run_tool(action.tool_name, action.arguments)
state = update_state(state, action, observation)
if verifier_says_done(state):
return finalize(state)
Questo rileva un problema molto comune: il modello «vuole essere utile» e si limita a riformulare la stessa query.
Un’altra protezione utile: proseguire solo se c’è un guadagno di utilità
Se un passaggio non modifica abbastanza lo stato, non proseguire. Può bastare una condizione semplice come:
- nessun nuovo file trovato,
- nessuna nuova entità estratta,
- nessun miglioramento del livello di confidenza nella risposta,
- nessun nuovo elemento di prova aggiunto.
La metrica esatta dipende dall’attività, ma è essenziale avere una misura del guadagno marginale.
Cosa succede davvero quando ottimizzi bene questo processo
Quando rendi il ciclo più efficiente, l’agente di solito migliora in modi che all’inizio possono passare inosservati:
- Diventa meno prolisso e più deciso.
- Richiede prima lo strumento giusto.
- Produce meno cicli di «sto ancora indagando».
- Diventa più economico e più veloce, perché meno turni significano meno contesto e meno latenza.
Il compromesso importante è che limiti rigidi e una riduzione aggressiva del contesto possono rendere il sistema fragile nelle attività davvero difficili. Quindi non fingere che l’agente stia «risolvendo» problemi quando in realtà sta solo esaurendo il tempo a disposizione. Rendilo evidente con trasparenza nei log e nei messaggi rivolti agli utenti.
La mia regola pratica: puntare su una competenza entro limiti definiti anziché su un'esplorazione infinita.
Una configurazione pratica per la produzione
Se dovessi mettere in produzione un agente domani, partirei da:
MAX_STEPS = 5- stato strutturato, anziché riproporre l'intera trascrizione
- memoria riepilogativa dopo ogni turno
- sottoagenti economici per ricerca ed estrazione
- cache dei risultati degli strumenti con metadati sull'aggiornamento
- registrazione di token e costi per ogni esecuzione
- rilevatore di chiamate ripetute
- motivi di arresto espliciti
Questa configurazione non è appariscente, ma è ciò che impedisce alla fattura di crescere più rapidamente del prodotto.
Indicazioni pratiche
- Limita prima di tutto i passaggi: è la protezione più economica contro i cicli fuori controllo.
- Riduci drasticamente il contesto e mantieni lo stato strutturato separato dai riepiloghi.
- Usa sottoagenti più economici per compiti circoscritti, anziché spendere token premium ovunque.
- Memorizza nella cache gli output riutilizzabili degli strumenti, ma rendi visibile quanto sono aggiornati, così il modello non si fida ciecamente di dati obsoleti.
- Registra la spesa per esecuzione, per passaggio e per chiamata a uno strumento, così gli aumenti indesiderati dei costi sono subito visibili.
- Interrompi i cicli in caso di ripetizione, assenza di progressi o completamento verificato, non solo quando le risorse si esauriscono.
- Progetta per una competenza entro limiti definiti: risultati sufficientemente buoni, rapidi e prevedibili battono sempre soluzioni ingegnose ma costose.
Un'unica chiave API per Claude Opus 5.5, Sonnet 5, Haiku 4.5 e Fable 5.1, oltre a GPT-6 modelli. Pagamento a consumo, senza abbonamento.
Ottieni la tua chiave API →