L’Arte dell’interazione: Gestire output strutturati con Agenti AI e LLM

Il panorama dello sviluppo software sta assistendo a una trasformazione significativa, con gli agenti di intelligenza artificiale che assumono ruoli sempre più centrali. Questi agenti vanno ben oltre i semplici chatbot, evolvendosi in componenti autonomi capaci di eseguire compiti complessi, elaborare vaste quantità di dati e prendere decisioni informate attraverso diverse piattaforme. A differenza dei bot basilari o degli assistenti reattivi, gli agenti AI sono progettati per gestire azioni multi-step, apprendere dalle esperienze, adattarsi a nuove situazioni e operare con un alto grado di indipendenza.

Le loro capacità avanzate sono in gran parte alimentate dall’intelligenza artificiale generativa ciò li abilita a svolgere ruoli specializzati, come accelerare lo sviluppo software attraverso la generazione di codice, aumentare la produttività dei dipendenti automatizzando compiti ripetitivi ed estrarre informazioni significative dai dati. Non sono più solo interfacce conversazionali, ma componenti attivi e decisionali all’interno di sistemi complessi. Questa evoluzione eleva direttamente la criticità dei loro output, poiché qualsiasi errore o incoerenza può innescare fallimenti a cascata attraverso flussi di lavoro operativi e pipeline di dati interconnessi.

Ma se gli output di questi agenti integrati sono inaffidabili, imprevedibili o malformati, possono interrompere interi flussi di lavoro aziendali, portando a fallimenti sistemici, corruzione dei dati e significative inefficienze operative. Pertanto, l’autonomia e la complessità crescenti degli agenti AI aumentano la necessità di output altamente affidabili, strutturati e deterministici, garantendo che questi agenti avanzati possano essere considerati parti integranti dei sistemi di produzione.

La necessità di output prevedibili e strutturati per l’Integrazione

Storicamente, i Large Language Models (LLM) generavano risposte principalmente in testo libero. Sebbene coerente per l’interpretazione umana, questo formato non strutturato è intrinsecamente ambiguo e difficile da analizzare programmaticamente per le macchine. Gli output strutturati affrontano direttamente questa sfida, garantendo che le risposte del modello aderiscano a un formato rigoroso e predefinito, come JSON, XML o Markdown, rendendole costantemente leggibili dalle macchine.

La transizione da output “leggibili dall’uomo” a “leggibili dalla macchina” rappresenta un cambiamento di paradigma nel modo in cui gli sviluppatori interagiscono con gli LLM e li utilizzano. Trasforma l’LLM da un partner conversazionale a una fonte di dati affidabile o a un esecutore di funzioni all’interno di un grafo computazionale più ampio. Questo cambiamento richiede che gli sviluppatori affrontino gli output degli LLM con lo stesso rigore e aspettativa di aderenza al contratto che avrebbero per qualsiasi risposta API tradizionale o schema di database.

Nel contesto più ampio dell’ingegneria del software, formati di dati prevedibili, come i contratti API e gli schemi di database, sono fondamentali per costruire sistemi robusti, scalabili e manutenibili. L’adozione di output strutturati trasforma fondamentalmente gli LLM da semplici generatori di testo in produttori di dati affidabili. Ciò consente loro di funzionare come componenti integrati e prevedibili nei flussi di lavoro automatizzati, simili ai microservizi tradizionali o alle pipeline di dati, promuovendo una mentalità di sviluppo “contract-first” per le interazioni con gli LLM.

L’Imperativo degli output strutturati per gli Agenti AI

Il meccanismo alla base degli output strutturati implica la guida del processo di generazione dei token dellLLM con regole o schemi predefiniti. Tecniche come le Finite State Machines (FSM) sono comunemente impiegate per monitorare e controllare la sequenza di generazione dei token, garantendo che ogni token generato aderisca alla struttura richiesta.

Formati di dati strutturati comuni utilizzati per gli output degli LLM:

  • JSON
  • XML
  • CSV

L’ampio spettro di formati strutturati supportati, che si estende oltre i comuni formati di scambio dati, come quelli sopra indicati, per includere codice per la generazione di interfacce, documenti scientifici e rappresentazioni visive, indica un significativo progresso nelle capacità degli LLM. Questa diversità consente agli LLM di servire una gamma molto più ampia di applicazioni a valle, ampliando fondamentalmente l’ambito dell’integrazione degli agenti AI dalla mera estrazione di dati alla partecipazione attiva in vari strati dello sviluppo software. Ciò consente agli sviluppatori di applicare principi di ingegneria del software ben consolidati alle interazioni con gli LLM, cosa che in precedenza era difficile a causa dell’imprevedibilità intrinseca degli output in linguaggio naturale. L’adozione di output strutturati fornisce il necessario “contratto di interfaccia” per gli LLM, consentendo loro di essere trattati come componenti software affidabili all’interno di un sistema più ampio. Ciò, a sua volta, consente lo sviluppo di applicazioni alimentate dall’AI robuste, scalabili e manutenibili, allineando lo sviluppo dell’IA con le migliori pratiche dell’ingegneria del software tradizionale.

Pydantic

Passiamo finalmente alle librerie e alla programmazione. Pydantic è una potente libreria Python che consente la validazione dei dati a runtime utilizzando annotazioni di tipo. Viene spesso combinata con schemi JSON per garantire che i dati generati aderiscano rigorosamente a strutture e tipi predefiniti. La convergenza su JSON e Pydantic per gli output strutturati degli LLM porta a una maggiore interoperabilità, uno sviluppo semplificato e un ecosistema più robusto per l’integrazione degli LLM. Ciò accelera la loro adozione negli ambienti di produzione fornendo contratti di dati familiari, affidabili e facilmente verificabili.

Non-determinismo dei LLM

I Large Language Models sono fondamentalmente non-deterministici. Ciò significa che, anche se venisse dato loro sempre lo stesso prompt di input, genererebbero output diversi in esecuzioni ripetute. Questa variabilità deriva dalla loro natura probabilistica, dai metodi di campionamento impiegati durante la generazione del testo, dalla casualità intrinseca incorporata nel loro design e dalla vasta diversità dei loro dati di addestramento.

Sebbene il non-determinismo sia intrinseco agli LLM, esistono tecniche che gli sviluppatori possono impiegare per migliorarne la prevedibilità e la coerenza:

  • Impostazione della temperatura: Questo parametro controlla direttamente la casualità e la creatività dell’output del LLM. Un valore di temperatura più basso (più vicino a 0) riduce l’imprevedibilità, portando a output più uniformi e coerenti, ideali per compiti che richiedono alta precisione e coerenza fattuale, come la sintesi o la documentazione tecnica.
  • Modalità Strict / Applicazione dello schema: Molte API LLM moderne (ad esempio, OpenAI, Cohere) hanno introdotto output strutturati o modalità strict che impongono l’aderenza a uno schema JSON predefinito. Ad esempio, il parametro strict_tools di Cohere garantisce che le chiamate agli strumenti generate dal modello aderiscano rigorosamente alle descrizioni degli strumenti fornite, eliminando nomi o parametri di strumenti allucinati e garantendo tipi di dati corretti. Questo metodo migliora significativamente l’affidabilità garantendo la conformità del formato.
  • Prompt engineering: Oltre all’impostazione della temperatura, le istruzioni esplicite nel prompt sono vitali. Ciò include indicare al LLM di produrre solo JSON, XML o CSV validi, senza alcun riempitivo conversazionale o testo esplicativo aggiuntivo. Inoltre, dettagliare lo schema richiesto, inclusi campi specifici, i loro tipi di dati attesi (ad esempio, stringa, numero, booleano, data) e qualsiasi vincolo, è cruciale. Istruzioni eccessivamente complicate o vaghe dovrebbero essere evitate.

Diversi SDK e framework sono stati sviluppati per semplificare la gestione degli output strutturati dagli LLM, offrendo astrazioni e funzionalità che vanno oltre il prompt engineering di base.

LLamaindex vs Cheshirecat

Premesso che normalmente sono di parte, quella che segue è però un’analisi prettamente tecnica dei due framework senza dichiarazione di preferenza di uno rispetto all’altro.

LlamaIndex fornisce un approccio a strati per l’output strutturato:

  • Output parsers: Questi moduli operano sia prima che dopo le interazioni con gli endpoint di completamento del testo LLM generici. Prima della chiamata LLM, possono aggiungere istruzioni di formato al prompt. Dopo la chiamata, analizzano l’output LLM grezzo nel formato strutturato specificato.
  • Pydantic programs: Questi sono moduli generici progettati per mappare un prompt di input direttamente a un output strutturato rappresentato da un oggetto Pydantic. Possono utilizzare API di chiamata di funzione o API di completamento del testo più parser di output. L’output strutturato deve semplicemente essere convertito nel formato oggetto corretto.
  • Programmi pydantic predefiniti: LlamaIndex offre programmi Pydantic predefiniti che mappano gli input a tipi di output specifici, come i dataframe.
  • LlamaParse per l’estrazione strutturata dadocumenti: LlamaParse è uno strumento specifico all’interno di LlamaIndex che consente l’estrazione di dati strutturati (come JSON) direttamente dalla fase di parsing di un documento, riducendo costi e tempi. Può essere attivato impostando structured_output=True nelle API e supporta la definizione di uno schema JSON personalizzato tramite structured_output_json_schema o l’utilizzo di schemi predefiniti per documenti comuni come fatture o curriculum (structured_output_json_schema_name=invoice).

L’approccio di LlamaIndex sottolinea l’importanza di gestire gli output strutturati a vari livelli di astrazione, dal parsing di basso livello all’integrazione con modelli pydantic per la tipizzazione forte. Questo consente agli sviluppatori di costruire pipeline LLM robuste che non solo generano dati strutturati, ma li elaborano in modo affidabile all’interno di flussi di lavoro complessi. LlamaIndex non si limita a consentire output strutturati, ma li richiede per molte delle sue funzionalità principali, promuovendo un’architettura che naturalmente gestisce i dati LLM in modo rigoroso. Questo sposta il peso della gestione dell’output dalla logica applicativa personalizzata al framework stesso, riducendo la complessità per gli sviluppatori.

Cheshire Cat AI è un framework open-source per la costruzione di agenti conversazionali, particolarmente focalizzato sull’integrazione in applicazioni industriali e tecniche dove la coerenza delle risposte è cruciale. È progettato per funzionare come un microservizio con API first, supportando la chat tramite WebSocket e la gestione degli agenti con API REST personalizzabile.

Per quanto riguarda la gestione degli output strutturati, Cheshire Cat AI offre diverse funzionalità attraverso la sua CLI e il framework:

  • Output JSON strutturato: La Cheshire Cat CLI (cat_chat.py) consente agli utenti di ricevere le risposte come messaggi strutturati in JSON o come testo semplice. È possibile specificare il flag –notext per visualizzare la risposta JSON completa anziché solo il testo.
  • Salvataggio dell’output in file JSON: Le risposte possono essere salvate opzionalmente in un file JSON per ulteriori elaborazioni o analisi. Questo è particolarmente utile per le applicazioni industriali che richiedono input per ulteriori calcoli o archiviazione dei dati.
  • Gestione della memoria procedurale: Cheshire Cat enfatizza l’uso della memoria procedurale, consentendo l’interazione con il LLM senza fare affidamento sulla cronologia delle conversazioni, il che è fondamentale per applicazioni in cui la coerenza della risposta non può essere influenzata da conversazioni precedenti. Questo è un aspetto importante per garantire un comportamento più deterministico in contesti specifici.
  • Integrazione con plugin e strumenti: Cheshire Cat è estensibile tramite plugin e supporta la funzione di chiamata (tools), consentendo agli agenti di interagire con il mondo esterno e di accedere a informazioni o eseguire azioni. Questo è essenziale per la generazione di output strutturati che attivano sistemi esterni o aggiornano database.

L’approccio di Cheshire Cat AI alla gestione degli output strutturati è profondamente radicato nella sua visione di integrare agenti AI conversazionali in applicazioni industriali. La sua enfasi sulla coerenza, la capacità di produrre e salvare output JSON, il supporto per la memoria procedurale e l’estensione tramite plugin, lo rendono uno strumento prezioso per gli sviluppatori che cercano di implementare agenti AI affidabili in ambienti complessi dove la prevedibilità dell’output è non negoziabile.

BONUS n8n

n8n offre modelli predefiniti per i nodi Structured Output Parser o Auto-Fixing Output Parser, che possono essere integrati nei flussi di lavoro per convalidare e correggere automaticamente le risposte del LLM. È importante che gli sviluppatori siano consapevoli che i formati di output dei nodi LLM all’interno di n8n possono talvolta cambiare tra le versioni.

Per flussi di lavoro n8n robusti, si consigliano le seguenti strategie:

  • Prompting esplicito all’interno dei Nodi: Anche con le funzionalità native di output strutturato, essere molto specifici nel prompt all’interno dei nodi LLM di n8n è cruciale. Istruzioni specifica di output possono migliorare significativamente l’aderenza del modello alla struttura desiderata.
  • Auto-Fixing Output Parser: Per migliorare la resilienza, gli sviluppatori dovrebbero integrare un nodo parser auto-correttivo a valle del nodo LLM. Questo nodo tenta di correggere eventuali output malformati, prevenendo l’interruzione o l’errore del flusso di lavoro.
  • Riprova in caso di fallimento: L’abilitazione dei meccanismi di riprova sui nodi LLM è una buona pratica per gestire errori transitori o output iniziali malformati. Ciò consente al flusso di lavoro di tentare automaticamente di nuovo la chiamata LLM, aumentando le possibilità di una risposta corretta e formattata.
  • Ramificazione in caso di errore: Per una gestione degli errori più sofisticata, gli sviluppatori possono configurare i flussi di lavoro n8n per ramificarsi in caso di errore. Se un nodo LLM produce un output non valido, il flusso di lavoro può deviare verso un ramo separato dove la stringa può essere elaborata da un altro nodo specificamente incaricato di riformattare o analizzare.
  • Pattern Multi-Agente (all’interno di n8n): Una strategia altamente efficace per compiti complessi è adottare un pattern multi-agente all’interno di n8n. Ciò implica l’utilizzo di un nodo Agent esclusivamente per generare il contenuto testuale grezzo e un secondo nodo successivo la cui unica responsabilità è formattare quel testo grezzo nell’output JSON o strutturato desiderato. Questa separazione delle responsabilità semplifica il compito di ciascun agente, aumentando significativamente il tasso di successo complessivo e la robustezza del flusso di lavoro.