Considerando l'intelligenza artificiale generativa, non è un segreto che il 2024 sia l'anno degli agenti IA. Un agente IA, nel contesto dell'IA generativa, è essenzialmente un codice che crea autonomamente nuovi contenuti come testo, audio o immagini ed esegue azioni per raggiungere obiettivi specifici, imparando dal suo ambiente. Questo articolo non si soffermerà sugli agenti IA in generale, poiché anche una richiesta ben formulata che risolve un problema tramite un modello di IA generativa può essere considerata un agente IA. Invece, ci concentreremo su un tipo di agenti più potente: quelli basati sugli assistenti avanzati di OpenAI e sul modo in cui essi collaborano per risolvere un problema.

Per prima cosa, spieghiamo cosa rende gli assistenti OpenAI così speciali.

Assistenti OpenAI

Gli assistenti OpenAI sono una nuova funzionalità dei servizi OpenAI, progettata per aiutare gli sviluppatori a risolvere autonomamente compiti complessi. D'ora in poi, li chiameremo semplicemente assistenti.

Per loro natura, tali assistenti possono essere profilati con istruzioni specifiche (queste istruzioni sono in realtà solo prompt di sistema o meta-prompt), e possono utilizzare vari tipi di strumenti come il code interpreter, la ricerca di file (precedentemente nota come recupero di file) e, il più potente di tutti, il function calling.

Non vorrei sorvolare sul function calling, poiché si tratta di una funzione cruciale che OpenAI ha introdotto circa un anno fa e, a mio parere, il function calling è l'elemento centrale per conferire maggiore autonomia agli agenti. Quindi, approfondiamo un po' e spieghiamo come possiamo ottenere un comportamento autonomo nel modo "tradizionale" prima di introdurre il function calling.

Supponiamo di voler preparare una torta. La programmazione tradizionale ci dice che dobbiamo usare la nostra intelligenza per astrarre il processo di cottura della torta in un elenco di passaggi. Poi arriva l'IA generativa e dice: "Posso utilizzare il meccanismo di completamento dei modelli GPT per generare questi passaggi". È fantastico, ma non ancora molto intelligente, almeno non nel senso di autonomia.

In effetti, possiamo chattare con i modelli GPT per affinare i passaggi della nostra ricetta in una serie di messaggi di richiesta-risposta, ma possiamo automatizzare questo processo usando solo il meccanismo di completamento? Non immediatamente, perché per rendere i nostri agenti più autonomi, dobbiamo aiutare i modelli GPT a riconoscere l'intento della richiesta. Insieme al meccanismo di completamento e ad alcune tecniche di concatenazione di prompt, possiamo raggiungere questo obiettivo.

Credo che Figura 1 (come descritto nell'originale, raffigurando un processo iterativo) sia autoesplicativa; mostra come il problema viene risolto attraverso interazioni multiple con il modello linguistico di grandi dimensioni. Vediamo ora come possiamo ottenere un comportamento autonomo con GPT.

Function Calling

Non approfondirò tutte le varianti del function calling, poiché non rientra nello scopo di questo articolo. Mi concentrerò invece sul comportamento dello strumento auto-invoke nel function calling e su come funziona il function calling.

Molte librerie hanno presentato soluzioni ingegnose per tecniche di riconoscimento dell'intento, ma OpenAI ha fatto un passo avanti implementando questo meccanismo direttamente nei suoi servizi (vicino al modello!). Ora abbiamo modelli in grado di riconoscere l'intento dietro una richiesta (ruolo: Utente), aiutare nella risoluzione di problemi complessi attraverso passaggi intermedi (ruolo: Strumento) e poi formulare una risposta finale per gli utenti (ruolo: Assistente). Questo meccanismo è chiamato function calling. Non dimenticate di confrontare il diagramma in Figura 2 (che presumiamo illustri il function calling) con quello di Figura 1 (che presumiamo mostri le interazioni senza function calling).

Ma come aiuta il function calling gli agenti a lavorare in modo più autonomo? Immaginiamo, per analogia, il cervello umano nel ruolo di un modello di IA generativa e consideriamo una nuova richiesta per risolvere un compito quotidiano come scrivere una lettera. Per l'essere umano, scrivere una lettera è legato a funzioni corporee, come scrivere a mano, che è una complessa raccolta di movimenti muscolari speciali, e durante la lettura, i muscoli degli occhi devono muoversi per seguire la scrittura. Queste sono alcune delle funzioni chiave, selezionate qui per semplicità. Il cervello da solo non è molto utile senza questo tipo di funzioni, perché non è il cervello che muove la mano, ma il cervello invia il comando ai muscoli, e i muscoli eseguono l'azione. Il risultato dell'azione viene poi inviato al cervello, che valuta se sono necessarie ulteriori azioni o se la query è completata.

In altre parole: il modello GPT controlla le funzioni che accompagnano la query e determina quali di esse devono essere richiamate per preparare una risposta finale. Quindi, una risposta intermedia viene inviata agli utenti per richiamare effettivamente queste funzioni. Le risposte delle funzioni integrano la richiesta, che a sua volta viene inviata al modello. Questa serie di aumenti dei prompt porta a una query più precisa, che a sua volta porta a una risposta migliore.

Code Interpreter

Ora che sappiamo cosa sono le chiamate a funzione, è il momento di discutere un'altra interessante funzionalità: il Code Interpreter.

Ricordiamo innanzitutto che la previsione del testo è il meccanismo fondamentale dell'IA generativa, ed è ciò che i modelli linguistici di grandi dimensioni fanno meglio. Nella previsione di una soluzione per un problema matematico complesso, non vogliamo che il modello preveda la soluzione basandosi sui suoi dati di addestramento, perché (permettetemi qui un'altra analogia) è ciò che il modello "ricorda" dall'addestramento. Il modello non calcola la soluzione sul momento, ma vogliamo che il modello calcoli la soluzione in modo tradizionale, utilizzando del semplice e basilare codice. Ma chi fornisce il codice? Ovviamente il modello! Perché è bravo a farlo, dato che è stato addestrato con tonnellate di codice Python. Così, gli ingegneri di OpenAI hanno genialmente aggiunto una sandbox Python vicino al modello, dove il modello può eseguire immediatamente il codice previsto della funzione necessaria.

File Search

Un altro punto debole dell'IA generativa è la mancanza di attualità dei dati. Il modello "ricorda" i dati fino alla data limite dell'addestramento. Ma cosa succede se il modello deve prevedere dati che non erano presenti nei dati di addestramento o al momento dell'addestramento? O, per esempio, come faremmo a prevedere il meteo attuale per un luogo specifico in modo dinamico, che non può essere risolto tramite completamento? In tal caso, otterremo ovviamente una grande allucinazione. Alcuni dati attuali ci aiuteranno a ridurre le allucinazioni, e possiamo raggiungere questo obiettivo utilizzando lo strumento di ricerca file per recuperare nuovi dati attuali "al volo".

Threads e Runs

Abbiamo una soluzione per i problemi di precisione e una soluzione per ridurre le allucinazioni, ma non abbiamo ancora finito con i problemi. Un aspetto molto importante del meccanismo di completamento è che è senza stato (stateless). La cronologia delle previsioni non è gestita dal modello, il che significa che il modello non ricorda nulla tra due query consecutive, quindi gli utenti devono gestire la cronologia della chat.

Immaginate di incontrare una persona e di iniziare una sorta di chat. Tutte le domande e risposte scambiate diventano parte della cronologia della chat. È come una memoria a breve termine, e la manteniamo in mente durante la chat, non è vero? Gli assistenti OpenAI affrontano questa limitazione introducendo i concetti di Threads (Discussioni) e Runs (Esecuzioni). Un Thread è un oggetto che memorizza la cronologia dei messaggi tra un utente e un assistente. Ogni nuova interazione con l'assistente viene aggiunta al thread, consentendo all'assistente di mantenere il contesto e "ricordare" le conversazioni precedenti. Questo elimina la necessità per lo sviluppatore di gestire manualmente la cronologia dei messaggi, rendendo le interazioni molto più fluide e naturali.

Quando si interagisce con un assistente all'interno di un thread, si crea un Run. Un Run rappresenta una singola esecuzione dell'assistente su un thread. Durante un Run, l'assistente elabora i messaggi nel thread, applica le sue istruzioni, utilizza i suoi strumenti (come function calling, code interpreter o file search) e genera una risposta. I Run sono transitori e possono avere diversi stati (in sospeso, in esecuzione, completato, fallito, ecc.), fornendo un meccanismo robusto per gestire il flusso di lavoro e l'esecuzione complessa degli agenti IA. In sintesi, i Threads forniscono la persistenza della conversazione, fungendo da memoria a lungo termine per l'assistente, mentre i Runs orchestrano l'intelligenza dell'assistente per rispondere a ogni nuova richiesta all'interno di quel contesto.