Vai al contenuto
LF Lomazzi Federico

Home/Appunti/Elettronica

Elettronica

L'illusione dell'autonomia: perché l'ecosistema dei tool per agenti AI rischia di lavorare contro di te

Lomazzi Federico 9 min di lettura

Un agente AI non è magia autonoma: nella forma più semplice è un modello inserito in un loop, alimentato da un prompt stratificato e collegato a strumenti che gli permettono di agire. Il problema nasce quando deleghiamo orchestrazione, memoria, tool e controllo a strati opachi: aumentano dipendenze, token, superficie d'attacco e possibilità che un errore rientri nel ciclo fino a rafforzarsi. L'autonomia utile comincia quindi dai confini, non dai framework.

Schermo di sviluppo con codice, terminale ed un agente AI che utilizza strumenti durante l'esecuzione

Se avete seguito il fermento degli ultimi anni attorno agli agenti AI saprete certamente che ormai esiste un framework per quasi qualsiasi cosa, wrapper "no-code", marketplace di agenti pronti, orchestratori, librerie per la memoria e piattaforme che promettono di mettere insieme tutto in pochi clic. La cosa è comoda, a volte comodissima, ma secondo me per capire dove si nasconde il problema bisogna prima togliere parecchia scenografia e ridurre l'agente alla sua essenza ingegneristica: UN AGENTE INTELLIGENTE È UN LOOP DI UN PROMPT, IN VARIA MISURA STRATIFICATO, AL QUALE SI POSSONO FORNIRE TOOL PERSONALIZZATI. Detta così sembra quasi banale, ed in effetti lo è; il modello riceve uno stato, decide se rispondere oppure usare uno strumento, osserva il risultato, lo rimette nel proprio contesto ed effettua un'altra inferenza finché arriva ad una condizione di uscita. Tutto il resto può essere utilissimo, ma è infrastruttura costruita attorno a questo ciclo.

L'agenda che può nascondersi dietro il "chiavi in mano"

Quando adottiamo una piattaforma di orchestrazione non acquistiamo soltanto comodità, accettiamo anche le sue decisioni architetturali. Non significa che ogni framework abbia un'agenda nascosta oppure che ogni servizio proprietario si comporti male, sarebbe una generalizzazione abbastanza sciocca, significa invece che QUALSIASI strato non controllato direttamente deve essere trattato come una dipendenza e verificato per ciò che aggiunge. Alcuni servizi possono inserire istruzioni di sistema, wrapper, metadati, tracing e telemetria, possono gestire lo scratchpad su infrastrutture esterne oppure costruire integrazioni più naturali verso determinati provider; ognuna di queste cose può avere una ragione perfettamente legittima, ma dal punto di vista di chi sta costruendo l'agente significa più contesto, più dati che viaggiano, più comportamenti da conoscere ed un'altra parte del sistema che può cambiare senza che sia il nostro codice a cambiare.

Il prompt bloating è forse l'esempio più immediato. Se io penso di avere scritto un system prompt da tremila token ma il framework aggiunge descrizioni dei tool, regole di routing, informazioni per il tracing, istruzioni operative ed altra roba che non vedo direttamente, il modello non sta lavorando sul prompt che credo di avergli dato ma su quello risultante dall'intera catena. Non è soltanto una faccenda economica, anche se i token li pago io, perché ogni istruzione in più entra nella gerarchia del contesto e può interferire con ciò che considero importante. Lo stesso discorso vale per il vendor lock-in: se memoria, embedding, tool schema e gestione delle chiamate sono modellati attorno ad un provider specifico, cambiare modello non significa più modificare una singola chiamata API ma può voler dire cambiare metà applicazione.

Poi c'è il flusso dei dati. Se tool execution, memoria, tracing oppure scratchpad vengono gestiti su server di terzi, bisogna sapere esattamente che cosa esce dal proprio perimetro. Una chiamata ad un database aziendale può produrre dati sensibili, una tool call può contenere identificativi interni, un messaggio d'errore può includere percorsi, nomi di file oppure dettagli infrastrutturali. Il punto non è dire "mai usare servizi esterni", ma sapere che delegare l'orchestrazione significa delegare anche una parte della visibilità sul sistema. E se questa parte non è documentata o non è osservabile, quello che abbiamo guadagnato in semplicità lo abbiamo pagato in CONTROLLO.

Il loop ReAct e la spirale dell'incongruenza

Qui secondo me nasce anche una parte del mito dell'AI che "impazzisce" oppure che improvvisamente decide di fare cose strane. Il pattern ReAct, cioè Reasoning and Acting, ha una logica potentissima: il modello ragiona sullo stato, decide un'azione, usa un tool, osserva il risultato e continua. Ma proprio perché il risultato dell'azione torna nel contesto, un errore non validato può diventare il punto di partenza dell'iterazione successiva. In pratica possiamo avere qualcosa del genere: [Prompt ipotetico / allucinazione] → [Esecuzione tool con dati errati] → [Risultato incongruo nello scratchpad] → [Nuova inferenza rinforzata dall'errore] → [Spirale fuori controllo].

Se uno strato intermedio interpreta male il nome di un tool, converte male un parametro, accetta un output non conforme oppure rimette nello scratchpad un risultato ambiguo, l'agente può iniziare a trattare quell'informazione come stato reale. Alla tornata successiva il modello non sa necessariamente che il dato è nato da un parsing difettoso, vede semplicemente un pezzo di contesto che gli viene presentato come osservazione del mondo; prova quindi a correggere, richiama un altro tool, riceve un secondo risultato e magari rafforza l'errore originale. Da fuori il comportamento sembra frenetico, quasi ostinato. In realtà spesso stiamo guardando un software probabilistico inserito in un ciclo nel quale NESSUNO HA INTERROTTO UNA CATENA DI DATI SBAGLIATI.

Ed è proprio qui che gli strati opachi possono diventare fastidiosi, perché quando non possiedo il loop faccio più fatica a capire quale informazione sia entrata, chi l'abbia trasformata, quale versione dello schema sia stata usata e perché il modello abbia visto un determinato risultato. Una bella dashboard con cento frecce non sostituisce la possibilità di seguire una richiesta dall'inizio alla fine e sapere esattamente chi ha aggiunto cosa.

La prima difesa è costruire il loop da soli

Se il progetto lo consente, io partirei dalla soluzione più noiosa possibile: un ciclo while, una chiamata diretta all'API del modello, uno schema dei tool dichiarato esplicitamente ed un dispatcher scritto da noi. Non perché sia elegante rifiutare i framework, ma perché un agente minimo è molto meno misterioso di quanto spesso venga raccontato. Il loop mantiene la memoria necessaria, invia il contesto al modello, riceve o una risposta finale oppure una richiesta di tool, convalida i parametri, esegue soltanto lo strumento ammesso, inserisce l'esito nel contesto ed effettua il giro successivo. A quel punto sappiamo dove mettere un contatore, dove applicare un timeout, dove misurare i token e soprattutto dove FERMARE il sistema.

La proprietà del system prompt diventa altrettanto importante. Se sto costruendo una logica di business non voglio scoprire a posteriori che fra la mia applicazione ed il modello esiste un altro livello di istruzioni che non avevo considerato; voglio sapere quali regole vengono inviate, quali tool sono disponibili in quella specifica esecuzione e quali informazioni vengono conservate fra un giro e l'altro. Lo stesso vale per i tool, che dovrebbero essere funzioni il più possibile isolate, tracciabili e con contratti chiari, non scatole magiche che accettano un JSON generico e poi decidono internamente che cosa fare.

Questo approccio ha anche un vantaggio abbastanza pratico: quando qualcosa va male, il problema rimane nostro ma almeno È NOSTRO DAVVERO. Possiamo loggarlo, riprodurlo, correggerlo ed eventualmente cambiare modello senza dover prima capire quale comportamento nascosto appartenga al provider, quale all'orchestratore e quale alla nostra applicazione.

Se usiamo soluzioni terze bisogna trattarle come infrastruttura critica

Naturalmente non sempre ha senso riscrivere tutto. Un buon framework può risparmiare settimane, offrire connettori maturi, gestione dello stato, retry, osservabilità ed integrazioni che sarebbe stupido ricostruire per orgoglio. Ma in quel caso lo userei come uso qualsiasi componente infrastrutturale importante, sapendo quali responsabilità gli sto cedendo e mettendo confini dove il modello non può decidere da solo.

La prima cosa è la STRICT OUTPUT SCHEMA VALIDATION. Un tool non dovrebbe restituire "qualcosa che più o meno il modello capirà", dovrebbe restituire un oggetto tipizzato e validato, per esempio con Pydantic in Python oppure Zod in ambiente JavaScript/TypeScript. Se lo schema non corrisponde, il giro non prosegue come se niente fosse: fallisce in modo controllato, registra l'errore ed eventualmente applica una strategia di recovery esplicita. È molto meno spettacolare di un agente che si autocorregge per venti passaggi, ma è anche molto più vicino a come si costruisce software affidabile.

Poi metterei sempre un circuit breaker, cioè un limite rigido al numero di iterazioni e possibilmente anche un tetto di token, costo e tempo. Cinque oppure otto giri non sono una legge universale, ma il concetto sì: un processo probabilistico che può richiamare strumenti, reinserire risultati nel contesto e produrre nuove inferenze NON DEVE avere ricorsione economica ed operativa infinita. Se non arriva alla soluzione entro il budget assegnato, si ferma, produce uno stato diagnostico e passa la mano.

Per le operazioni critiche il human-in-the-loop non deve essere una frase del prompt ma una condizione architetturale. Se il tool cancella dati, spedisce comunicazioni esterne, modifica infrastrutture, effettua una transazione oppure compie un'azione difficilmente reversibile, l'esecuzione deve poter essere sospesa prima dell'effetto collaterale e richiedere un'autorizzazione esplicita. È una filosofia molto semplice: IL MODELLO PUÒ PROPORRE, MA NON DEVE ESSERE L'AUTORITÀ CHE AUTORIZZA SE STESSO.

Infine pulirei il contesto. Uno scratchpad non è una discarica nella quale conservare per sempre ogni tentativo fallito, payload enorme, eccezione e risposta duplicata. Se un tool ha restituito cinquanta kilobyte ma al giro successivo servono tre campi, mantengo i tre campi; se un errore è già stato classificato e gestito non devo necessariamente continuare a trascinarlo per altre dieci inferenze. La memoria corta dell'agente deve contenere lo STATO UTILE, non tutta la cronaca della sua esistenza.

Quanto è piccolo davvero un agente

Per togliere un po' di magia dalla faccenda basta anche un esempio volutamente elementare. Il codice seguente non usa un vero LLM, perché simula_cervello_llm() serve soltanto a rendere visibile il meccanismo, ma contiene già i pezzi fondamentali: memoria, decisione, tool, risultato che rientra nel contesto, conferma umana e limite massimo del loop.

BLOCCO0

Naturalmente manca quasi tutto ciò che servirebbe in produzione, ma proprio per questo l'esempio fa vedere bene la struttura. Sostituite simula_cervello_llm() con una vera chiamata ad un modello che supporta Function Calling o Tool Use, dichiarate gli schemi dei tool ed avete già il nucleo di un agente. Da lì potete aggiungere persistenza, RAG, memoria lunga, più strumenti, scheduling, retry ed osservabilità, ma ogni volta che aggiungete qualcosa sapete esattamente QUALE PROBLEMA state risolvendo e quale nuovo strato state introducendo.

L'autonomia non è nel numero di framework

Alla fine il punto per me rimane abbastanza semplice. L'agente AI è potente proprio perché può essere ridotto ad elementi lineari: un loop, un prompt stratificato, uno stato ed alcuni tool specifici. ReAct ha formalizzato bene l'idea di alternare ragionamento ed azione, mentre i sistemi moderni hanno reso il tool use molto più comodo, ma nessuno di questi progressi elimina la responsabilità dell'architettura che tiene insieme i pezzi.

Più deleghiamo a scatole nere, più dobbiamo sapere che cosa quelle scatole stanno facendo, perché l'astrazione non elimina la complessità ma la SPOSTA in un posto che vediamo meno. Un framework può essere ottimo, un servizio gestito può essere la scelta giusta ed un marketplace può farci risparmiare mesi, ma non dovrebbero diventare un alibi per non conoscere prompt effettivo, schema dei tool, memoria, autorizzazioni, telemetria e condizioni di uscita del loop. Prima viene il controllo, poi la comodità, costruendo l'agente in modo che quando qualcosa sbaglia non abbia anche la libertà di trasformare quell'errore nel punto di partenza del giro successivo!

31 visite

Fonti

  1. ReAct: Synergizing Reasoning and Acting in Language Models arxiv.org
  2. OWASP Cheat Sheet Series, AI Agent Security cheatsheetseries.owasp.org
  3. Anthropic, Building effective agents www.anthropic.com
  4. Anthropic, Advanced tool use on the Claude Developer Platform www.anthropic.com
  5. Anthropic, Trustworthy agents in practice www.anthropic.com
  6. Unsplash, immagine di apertura di Bernd Dittrich unsplash.com

Commenti

Nessun commento, per ora.

Per commentare serve un accesso. Qui siamo tutti tecnici e colleghi!

Accedi per commentare

Scarica