Possono gli LLM sostituire gli analisti dei dati? Costruire un analista alimentato da LLM

Possono gli LLM sostituire gli analisti dei dati? Costruire un analista potenziato da LLM

Parte 1: potenziare ChatGPT con strumenti

Immagine di DALL-E 3

Credo che ognuno di noi si sia chiesto almeno una volta nell’ultimo anno se (o meglio quando) ChatGPT sarà in grado di sostituire il tuo ruolo. Anche io non faccio eccezione.

Abbiamo un certo consenso sul fatto che le recenti scoperte nell’AI generativa avranno un grande impatto sulla nostra vita personale e professionale. Tuttavia, non abbiamo ancora una visione chiara di come i nostri ruoli cambieranno nel tempo.

Passare molto tempo a pensare a diversi scenari futuri possibili e alle loro probabilità potrebbe essere affascinante, ma suggerisco un approccio completamente diverso: provare a costruire il tuo prototipo da solo. Primo, è piuttosto impegnativo e divertente. Secondo, ci aiuterà a guardare al nostro lavoro in modo più strutturato. Terzo, ci darà l’opportunità di provare nella pratica uno degli approcci più all’avanguardia: gli agenti LLM.

In questo articolo, inizieremo con semplici concetti e impareremo come gli LLM possono sfruttare gli strumenti e svolgere compiti diretti. Ma negli articoli successivi, approfondiremo diverse tecniche e migliori pratiche per gli agenti LLM.

Quindi, che il viaggio abbia inizio.

Cos’è l’analisi dei dati?

Prima di passare agli LLM, cerchiamo di definire cosa sia l’analisi dei dati e quali compiti svolgiamo come analisti.

Il mio motto è che l’obiettivo del team analitico sia quello di aiutare i team prodotto a prendere le decisioni giuste basate sui dati nel tempo disponibile. È una buona missione, ma per definire lo scopo dell’analista potenziato da LLM, dovremmo scomporre ulteriormente il lavoro analitico.

Mi piace il framework proposto da Gartner. Identifica quattro tecniche diverse di dati e analisi:

  • Analisi descrittiva risponde a domande come “Cos’è successo?”. Ad esempio, qual è stato il fatturato a dicembre? Questo approccio include compiti di reportistica e lavoro con strumenti di BI.
  • Analisi diagnostica va un po’ oltre e pone domande come “Perché è successo qualcosa?”. Ad esempio, perché il fatturato è diminuito del 10% rispetto all’anno precedente? Questa tecnica richiede una maggiore analisi dettagliata e scomposizione dei dati.
  • Analisi predittiva ci consente di ottenere risposte a domande come “Cosa succederà?”. I due pilastri di questo approccio sono la previsione (prevedere il futuro per situazioni di routine aziendale) e la simulazione (modellare diversi possibili risultati).
  • Analisi prescrittiva influenza le decisioni finali. Le domande comuni sono “Su cosa dovremmo concentrarci?” o “Come potremmo aumentare il volume del 10%?”.

Solitamente, le aziende percorrono tutti questi stadi passo dopo passo. È quasi impossibile iniziare a guardare previsioni e analisi di scenari diversi se la tua azienda non ha ancora padroneggiato l’analisi descrittiva (non hai un data warehouse, strumenti di BI o definizioni di metriche). Quindi, questo framework può mostrare anche la maturità dei dati dell’azienda.

Allo stesso modo, quando un analista cresce da livello junior a livello senior, probabilmente attraverserà tutti questi stadi, partendo da compiti di reportistica ben definiti e progredendo verso domande strategiche vaghe. Quindi, questo framework è rilevante anche a livello individuale.

Se torniamo al nostro analista potenziato da LLM, dovremmo concentrarci sull’analisi descrittiva e sui compiti di reportistica. È meglio partire dalle basi. Quindi, ci concentreremo nell’apprendere LLM per comprendere le domande di base sui dati.

Abbiamo definito il nostro focus per il primo prototipo. Quindi, siamo pronti per passare alle domande tecniche e discutere il concetto di agenti LLM e strumenti.

Agenti LLM e strumenti

Quando usavamo LLM in precedenza (ad esempio, per fare topic modelling qui), descrivevamo noi stessi i passaggi esatti nel codice. Ad esempio, guardiamo la catena qui sotto. Inizialmente, abbiamo chiesto al modello di determinare il sentimento di una recensione del cliente. Poi, a seconda del sentimento, abbiamo estratto dalla recensione gli aspetti positivi o negativi menzionati nel testo.

Illustrazione dell'autore

In questo esempio, abbiamo chiaramente definito il comportamento dell’LLM e l’LLM ha risolto abbastanza bene questo compito. Tuttavia, questo approccio non funzionerà se creiamo qualcosa di più avanzato e vago, come un analista alimentato da LLM.

Se hai mai lavorato come analista per almeno un giorno, sapresti che gli analisti ricevono una vasta gamma di domande diverse, a partire da domande di base (come “Quanti clienti abbiamo avuto sul nostro sito ieri?” o “Potresti fare un grafico per la nostra riunione di consiglio domani?”) fino a domande molto avanzate (ad esempio, “Quali sono i principali punti dolorosi dei clienti?” o “Quale mercato dovremmo lanciare prossimamente?”). Va da sé che non è fattibile descrivere tutti gli scenari possibili.

Tuttavia, c’è un approccio che potrebbe aiutarci: gli agenti. L’idea centrale degli agenti è utilizzare gli LLM come motore di ragionamento che può scegliere cosa fare dopo e quando è il momento di restituire la risposta finale al cliente. Suona abbastanza simile al nostro comportamento: riceviamo un compito, definiamo gli strumenti necessari, li usiamo e poi torniamo con la risposta finale quando siamo pronti.

Il concetto essenziale legato agli agenti (che ho già menzionato in precedenza) sono gli strumenti. Gli strumenti sono funzioni che gli LLM possono invocare per ottenere informazioni mancanti (ad esempio, eseguire una query SQL, utilizzare una calcolatrice o chiamare un motore di ricerca). Gli strumenti sono cruciali perché ti permettono di portare gli LLM al livello successivo e interagire con il mondo. In questo articolo, ci concentreremo principalmente sulle funzioni di OpenAI come strumenti.

OpenAI ha modelli personalizzati per poter lavorare con le funzioni in modo che:

  • Puoi passare al modello l’elenco delle funzioni con le descrizioni;
  • Se è pertinente alla tua query, il modello ti restituirà una chiamata di funzione – il nome della funzione e i parametri di input per chiamarla.

Puoi trovare ulteriori informazioni e l’elenco aggiornato dei modelli che supportano le funzioni nella documentazione.

Ci sono due casi d’uso principali per utilizzare le funzioni con gli LLM:

  • Tagging ed estrazione – in questi casi, le funzioni vengono utilizzate per garantire il formato di output del modello. Invece dell’output solito con il contenuto, otterrai una chiamata di funzione strutturata.
  • Strumenti e gestione delle rotte – questo è un caso d’uso più interessante che ti permette di creare un agente.

Iniziamo con il caso d’uso più semplice dell’estrazione per imparare come utilizzare le funzioni di OpenAI.

Caso d’uso #1: Tagging ed estrazione

Potresti chiederti qual è la differenza tra tagging ed estrazione. Questi termini sono piuttosto simili. L’unica differenza è se il modello estrae informazioni presentate nel testo o etichetta il testo fornendo nuove informazioni (ad esempio, definisce il linguaggio o il sentimento).

Illustrazione di autore

Dal momento che abbiamo deciso di concentrarci su compiti di analisi descrittiva e di reportistica, utilizziamo questo approccio per strutturare le richieste di dati in arrivo e ottenere i seguenti componenti: metriche, dimensioni, filtri, periodo e output desiderato.

Illustrazione di autore

Sarà un esempio di estrazione in quanto abbiamo solo bisogno delle informazioni presenti nel testo.

Esempio di base dell’API di completamento di OpenAI

Prima di tutto, dobbiamo definire la funzione. OpenAI si aspetta una descrizione della funzione in formato JSON. Questo JSON verrà passato al LLM, quindi dobbiamo indicargli tutto il contesto: cosa fa questa funzione e come usarla.

Ecco un esempio di JSON di una funzione. Abbiamo specificato:

  • name e description per la funzione stessa,
  • type e description per ciascun argomento,
  • l’elenco dei parametri di input richiesti per la funzione.
extraction_functions = [    {        "name": "extract_information",        "description": "estrae informazioni",        "parameters": {            "type": "object",            "properties": {                "metric": {                    "type": "string",                    "description": "principale metrica da calcolare, ad esempio, 'numero di utenti' o 'numero di sessioni'",                },                "filters": {                    "type": "string",                    "description": "filtri da applicare al calcolo (non includere filtri sulle date qui)",                },                "dimensions": {                    "type": "string",                    "description": "parametri per suddividere la tua metrica",                },                "period_start": {                    "type": "string",                    "description": "il giorno di inizio del periodo per un report",                },                "period_end": {                    "type": "string",                    "description": "il giorno di fine del periodo per un report",                },                "output_type": {                    "type": "string",                    "description": "l'output desiderato",                    "enum": ["number", "visualizzazione"]                }            },            "required": ["metric"],        },    }]

Non è necessario implementare la funzione stessa in questo caso d’uso perché non la utilizzeremo. Riceviamo solo risposte LLM in modo strutturato come chiamate di funzione.

Ora, potremmo utilizzare la standard API OpenAI Chat Completion per chiamare la funzione. Abbiamo passato alla chiamata API:

  • modello – Ho utilizzato l’ultimo ChatGPT 3.5 Turbo che può lavorare con le funzioni,
  • elenco dei messaggi – un messaggio di sistema per impostare il contesto e una richiesta dell’utente,
  • elenco delle funzioni che abbiamo definito in precedenza.
import openaimessages = [    {        "role": "system",        "content": "Estrai le informazioni rilevanti dalla richiesta fornita."    },    {        "role": "user",        "content": "Come è cambiato il numero degli utenti iOS nel tempo?"    }]response = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",     messages = messages,    functions = extraction_functions)print(response)

Come risultato, abbiamo ottenuto il seguente JSON.

{  "id": "chatcmpl-8TqGWvGAXZ7L43gYjPyxsWdOTD2n2",  "object": "chat.completion",  "created": 1702123112,  "model": "gpt-3.5-turbo-1106",  "choices": [    {      "index": 0,      "message": {        "role": "assistant",        "content": null,        "function_call": {          "name": "extract_information",          "arguments": "{\"metric\":\"numero di utenti\",\"filters\":\"piattaforma='iOS'\",\"dimensions\":\"data\",\"period_start\":\"2021-01-01\",\"period_end\":\"2021-12-31\",\"output_type\":\"visualizzazione\"}"        }      },      "finish_reason": "function_call"    }  ],  "usage": {    "prompt_tokens": 159,    "completion_tokens": 53,    "total_tokens": 212  },  "system_fingerprint": "fp_eeff13170a"}

Ricorda che le funzioni e le chiamate di funzione verranno conteggiate nei limiti dei token e verranno addebitate.

Il modello ha restituito una chiamata di funzione invece di una risposta comune: possiamo vedere che il content è vuoto e finish_reason è uguale a function_call. Nella risposta, ci sono anche i parametri di input per la chiamata di funzione:

  • metric = "numero di utenti",
  • filters = "piattaforma = 'iOS'",
  • dimensions = "data",
  • period_start = "2021-01-01",
  • period_start = "2021-12-31",
  • output_type = "visualizzazione".

Il modello ha fatto un buon lavoro. L’unico problema è che ha presumuto il periodo dal nulla. Possiamo risolverlo aggiungendo una guida più esplicita al messaggio di sistema, ad esempio, "Estrai le informazioni rilevanti dalla richiesta fornita. Estrai SOLO le informazioni presentate nella richiesta iniziale; non aggiungere altro. Restituisci informazioni parziali se manca qualcosa."

Per impostazione predefinita, i modelli decidono se utilizzare le funzioni in modo indipendente (function_call = 'auto'). Possiamo richiedere di restituire una chiamata di funzione specifica ogni volta o di non utilizzare affatto le funzioni.

# sempre chiamare la funzione extract_informationresponse = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",    messages = messages,    functions = extraction_functions,    function_call = {"name": "extract_information"})# nessuna chiamata di funzioneresponse = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",    messages = messages,    functions = extraction_functions,    function_call = "none")

Abbiamo ottenuto il primo programma funzionante che utilizza le funzioni LLM. Fantastico. Tuttavia, non è molto conveniente descrivere le funzioni in un JSON. Discutiamo di come renderlo più semplice.

Utilizzare Pydantic per definire funzioni

Per definire funzioni in modo più pratico, possiamo sfruttare Pydantic. Pydantic è la libreria Python più popolare per la convalida dei dati.

Abbiamo già utilizzato Pydantic per definire l’Output Parser di LangChain.

Prima di tutto, dobbiamo creare una classe che erediti dalla classe BaseModel e definire tutti i campi (argomenti della nostra funzione).

from pydantic import BaseModel, Field
from typing import Optional

class RequestStructure(BaseModel):
    """estrae le informazioni"""
    metric: str = Field(description="metrica principale da calcolare, ad esempio, 'numero di utenti' o 'numero di sessioni'")
    filters: Optional[str] = Field(description="filtri da applicare al calcolo (non includere filtri sulle date qui)")
    dimensions: Optional[str] = Field(description="parametri per suddividere la tua metrica")
    period_start: Optional[str] = Field(description="il giorno di inizio del periodo per un report")
    period_end: Optional[str] = Field(description="il giorno di fine del periodo per un report")
    output_type: Optional[str] = Field(description="l'output desiderato", enum=["numero", "visualizzazione"])

Poi, possiamo usare LangChain per convertire la classe Pydantic nella funzione di OpenAI.

from langchain.utils.openai_functions import convert_pydantic_to_openai_function

extract_info_function = convert_pydantic_to_openai_function(RequestStructure, name='extract_information')

LangChain valida la classe che abbiamo fornito. Ad esempio, si assicura che la descrizione della funzione sia specificata, poiché LLM ne ha bisogno per poter utilizzare questo strumento.

Come risultato, otteniamo lo stesso JSON da passare a LLM, ma ora lo esprimiamo come una classe Pydantic.

{'name': 'extract_information', 'description': 'estrae le informazioni', 'parameters': {'title': 'RequestStructure', 'description': 'estrae le informazioni', 'type': 'object', 'properties': {'metric': {'title': 'Metrica', 'description': "metrica principale da calcolare, ad esempio, 'numero di utenti' o 'numero di sessioni'", 'type': 'string'}, 'filters': {'title': 'Filtri', 'description': 'filtri da applicare al calcolo (non includere filtri sulle date qui)', 'type': 'string'}, 'dimensions': {'title': 'Dimensioni', 'description': 'parametri per suddividere la tua metrica', 'type': 'string'}, 'period_start': {'title': 'Inizio Periodo', 'description': 'il giorno di inizio del periodo per un report', 'type': 'string'}, 'period_end': {'title': 'Fine Periodo', 'description': 'il giorno di fine del periodo per un report', 'type': 'string'}, 'output_type': {'title': 'Tipo di Output', 'description': 'l'output desiderato', 'enum': ['numero', 'visualizzazione'], 'type': 'string'}}, 'required': ['metric']}}

Ora, potremmo usarlo nella nostra chiamata a OpenAI. Passiamo da OpenAI API a LangChain per rendere le nostre chiamate API più modulari.

Definizione della catena LangChain

Definiamo una catena per estrarre le informazioni necessarie dalle richieste. Utilizzeremo LangChain poiché è il framework più popolare per LLM. Se non hai mai lavorato con esso prima, ti consiglio di imparare alcuni concetti di base in uno dei miei articoli precedenti.

La nostra catena è semplice. Consiste di un modello Open AI e una prompt con una variabile request (un messaggio dell’utente).

Abbiamo anche utilizzato la funzione bind per passare l’argomento functions al modello. La funzione bind ci permette di specificare argomenti costanti per i nostri modelli che non fanno parte dell’input (ad esempio, functions o temperature).

from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI

model = ChatOpenAI(temperature=0.1, model='gpt-3.5-turbo-1106') \
    .bind(functions=[extract_info_function])

prompt = ChatPromptTemplate.from_messages([
    ("system", "Estrai le informazioni rilevanti dalla richiesta fornita. \            Estrai SOLO le informazioni presentate nella richiesta iniziale. \            Non aggiungere altro. \            Restituisci informazioni parziali se qualcosa manca."),
    ("human", "{request}")
])

extraction_chain = prompt | model

È ora di provare la nostra funzione. Dobbiamo utilizzare il metodo invoke e passare una request.

extraction_chain.invoke({'request': "Quanti clienti hanno visitato il nostro sito su iOS in aprile 2023 da diversi paesi?"})

Nell’output, otteniamo AIMessage senza alcun contenuto ma con una chiamata di funzione.

AIMessage(  content='',   additional_kwargs={    'function_call': {       'name': 'extract_information',        'arguments': '''{         "metric":"numero di clienti", "filtri":"device = 'iOS'",         "dimensioni":"paese", "periodo_inizio":"2023-04-01",         "periodo_fine":"2023-04-30", "tipo_output":"numero"}        '''}  })

Quindi, abbiamo imparato come utilizzare le funzioni di OpenAI in LangChain per ottenere un output strutturato. Ora, passiamo al caso d’uso più interessante: strumenti e routing.

Caso d’uso n. 2: Strumenti e routing

È ora di utilizzare gli strumenti e potenziare il nostro modello con capacità esterne. I modelli in questo approccio sono motori di ragionamento e possono decidere quali strumenti utilizzare e quando (si chiama routing).

LangChain ha un concetto di strumenti – interfacce che gli agenti possono utilizzare per interagire con il mondo. Gli strumenti possono essere funzioni, catene di LangChain o addirittura altri agenti.

Possiamo facilmente convertire gli strumenti in funzioni di OpenAI utilizzando format_tool_to_openai_function e continuare a passare l’argomento functions agli LLM.

Definizione di uno strumento personalizzato

Insegniamo al nostro analista potenziato da LLM a calcolare la differenza tra due metriche. Sappiamo che gli LLM possono commettere errori in matematica, quindi ci piacerebbe chiedere a un modello di utilizzare una calcolatrice anziché fare affidamento su di sé.

Per definire uno strumento, dobbiamo creare una funzione e utilizzare un decoratore @tool.

from langchain.agents import tool@tooldef differenza_percentuale(metrica1: float, metrica2: float) -> float:    """Calcola la differenza percentuale tra le metriche"""    return (metrica2 - metrica1)/metrica1*100

Adesso, questa funzione ha i parametri name e description che verranno passati agli LLM.

print(differenza_percentuale.name)# differenza_percentuale.nameprint(differenza_percentuale.args)# {'metrica1': {'title': 'Metrica1', 'type': 'number'},# 'metrica2': {'title': 'Metrica2', 'type': 'number'}}print(differenza_percentuale.description)# 'differenza_percentuale(metrica1: float, metrica2: float) -> float - Calcola la differenza percentuale tra le metriche'

Questi parametri verranno utilizzati per creare una specifica della funzione di OpenAI. Convertiamo il nostro strumento in una funzione di OpenAI.

from langchain.tools.render import format_tool_to_openai_functionprint(format_tool_to_openai_function(differenza_percentuale))

Otteniamo il seguente JSON come risultato. Esso delinea la struttura, ma le descrizioni dei campi sono mancanti.

{'name': 'differenza_percentuale', 'description': 'differenza_percentuale(metrica1: float, metrica2: float) -> float - Calcola la differenza percentuale tra le metriche', 'parameters': {'title': 'percentage_differenceSchemaSchema',  'type': 'object',  'properties': {'metrica1': {'title': 'Metrica1', 'type': 'number'},   'metrica2': {'title': 'Metrica2', 'type': 'number'}},  'required': ['metrica1', 'metrica2']}}

Possiamo utilizzare Pydantic per specificare uno schema per gli argomenti.

class Metriche(BaseModel):    metrica1: float = Field(description="Valore di base della metrica per calcolare la differenza")    metrica2: float = Field(description="Nuovo valore della metrica che confrontiamo con il valore di riferimento")@tool(args_schema=Metriche)def differenza_percentuale(metrica1: float, metrica2: float) -> float:    """Calcola la differenza percentuale tra le metriche"""    return (metrica2 - metrica1)/metrica1*100

Ora, se convertiamo una nuova versione alla specifica della funzione di OpenAI, includerà le descrizioni degli argomenti. È molto meglio perché potremmo condividere tutto il contesto necessario con il modello.

{'name': 'percentage_difference', 'description': 'percentage_difference(metric1: float, metric2: float) -> float - Calcola la differenza percentuale tra le metriche', 'parameters': {'title': 'Metriche',  'type': 'object',  'properties': {'metric1': {'title': 'Metrica 1',    'description': 'Valore metrico di base per calcolare la differenza',    'type': 'number'},   'metric2': {'title': 'Metrica 2',    'description': 'Nuovo valore metrico che confrontiamo con la linea di base',    'type': 'number'}},  'required': ['metric1', 'metric2']}}

Quindi, abbiamo definito lo strumento che LLM sarà in grado di utilizzare. Proviamolo nella pratica.

Usare uno strumento nella pratica

Definiamo una catena e passiamo il nostro strumento alla funzione. Poi, potremmo testarlo su una richiesta dell’utente.

modello = ChatOpenAI(temperature=0.1, modello = 'gpt-3.5-turbo-1106')\  .bind(funzioni = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([    ("sistema", "Sei un analista di prodotto desideroso di aiutare il tuo team di prodotto. Sei molto rigoroso e accurato. Utilizzi solo fatti, non inventi informazioni."),    ("utente", "{richiesta}")])analyst_chain = prompt | modelloanalyst_chain.invoke({'richiesta': "Ad aprile avevamo 100 utenti e a maggio solo 95. Qual è la differenza in percentuale?"})

Abbiamo ottenuto una chiamata alla funzione con gli argomenti corretti, quindi funziona.

AIMessage(content='', additional_kwargs={    'function_call': {      'name': 'percentage_difference',       'arguments': '{"metric1":100,"metric2":95}'}  })

Per avere un modo più comodo di lavorare con l’output, possiamo utilizzareOpenAIFunctionsAgentOutputParser. Aggiungiamolo alla nostra catena.

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParseranalyst_chain = prompt | modello | OpenAIFunctionsAgentOutputParser()risultato = analyst_chain.invoke({'richiesta': "Ad aprile c'erano 100 utenti e a maggio 110 utenti. Come è cambiato il numero di utenti?"})

Ora, abbiamo ottenuto l’output in modo più strutturato e potremmo facilmente recuperare gli argomenti per il nostro strumento come result.tool_input.

AgentActionMessageLog(   tool='percentage_difference',    tool_input={'metric1': 100, 'metric2': 110},    log="\nInvocatione: `percentage_difference` con `{'metric1': 100, 'metric2': 110}`\n\n\n",    message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}})])

Quindi, potremmo eseguire la funzione come richiesto da LLM in questo modo.

osservazione = percentage_difference(result.tool_input)print(osservazione)# 10

Se vogliamo ottenere la risposta finale dal modello, dobbiamo passare il risultato dell’esecuzione della funzione indietro. Per farlo, dobbiamo definire una lista di messaggi da passare alle osservazioni del modello.

from langchain.prompts import MessagesPlaceholdermodello = ChatOpenAI(temperature=0.1, modello = 'gpt-3.5-turbo-1106')\  .bind(funzioni = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([    ("sistema", "Sei un analista di prodotto desideroso di aiutare il tuo team di prodotto. Sei molto rigoroso e accurato. Utilizzi solo fatti, non inventi informazioni."),    ("utente", "{richiesta}"),    MessagesPlaceholder(variable_name="osservazioni")])analyst_chain = prompt | modello | OpenAIFunctionsAgentOutputParser()result1 = analyst_chain.invoke({    'richiesta': "Ad aprile c'erano 100 utenti e a maggio 110 utenti. Come è cambiato il numero di utenti?",    "osservazioni": []})osservazione = percentage_difference(result1.tool_input)print(osservazione)# 10

Quindi, dobbiamo aggiungere l’osservazione alla nostra variabile osservazioni. Possiamo utilizzare la funzione format_to_openai_functions per formattare i nostri risultati in modo previsto dal modello.

from langchain.agents.format_scratchpad import format_to_openai_functionsformat_to_openai_functions([(result1, observation), ])

Come risultato, abbiamo ottenuto un messaggio che il LLM può capire.

[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference',                                            'arguments': '{"metric1":100,"metric2":110}'}}), FunctionMessage(content='10.0', name='percentage_difference')]

Invochiamo la nostra catena ancora una volta, passando il risultato dell’esecuzione della funzione come osservazione.

result2 = analyst_chain.invoke({    'request': "A aprile c'erano 100 utenti e a maggio 110 utenti. Come è cambiato il numero di utenti?",    "observations": format_to_openai_functions([(result1, observation)])})

Ora abbiamo ottenuto il risultato finale dal modello, che sembra ragionevole.

AgentFinish(  return_values={'output': 'Il numero di utenti è aumentato del 10%.'},   log='Il numero di utenti è aumentato del 10%.')

Se stessimo lavorando con l’API di completamento chat base di OpenAI, potremmo semplicemente aggiungere un altro messaggio con ruolo = tool . Puoi trovare un esempio dettagliato qui.

Se attiviamo la modalità di debug, possiamo vedere l’esatto prompt che è stato passato all’API di OpenAI.

Sistema: Sei un analista di prodotto desideroso di aiutare il tuo team di prodotto. Sei molto rigoroso e preciso. Utilizzi solo fatti, non inventi informazioni.Umano: A aprile c'erano 100 utenti e a maggio 110 utenti. Come è cambiato il numero di utenti?IA: {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}Funzione: 10.0

Per attivare la modalità di debug di LangChain, esegui il seguente codice ed esegui la tua catena per vedere cosa succede sotto il cofano.

import langchainlangchain.debug = True

Abbiamo provato a lavorare con uno strumento, ma estendiamo il nostro set di strumenti e vediamo come LLM potrebbe gestirlo.

Routing: utilizzo di più strumenti

Aggiungiamo un paio di strumenti in più al nostro set di strumenti dell’analista:

  • ottieni utenti attivi mensili
  • utilizzando Wikipedia.

Prima di tutto, definiamo una funzione dummy per calcolare il pubblico con filtri per mese e città. Utilizzeremo ancora Pydantic per specificare gli argomenti di input per la nostra funzione.

import datetimeimport randomclass Filters(BaseModel):    month: str = Field(description="Attività del cliente nel formato %Y-%m-%d")    city: Optional[str] = Field(description="Città di residenza dei clienti (per impostazione predefinita nessun filtro)",                     enum = ["Londra", "Berlino", "Amsterdam", "Parigi"])@tool(args_schema=Filters)def get_monthly_active_users(month: str, city: str = None) -> int:    """Restituisce il numero di clienti attivi per il mese specificato"""    dt = datetime.datetime.strptime(month, '%Y-%m-%d')    total = dt.year + 10*dt.month    if city is None:        return total    else:        return int(total*random.random())

Successivamente, utilizziamo il pacchetto Wikipedia in Python per consentire al modello di interrogare Wikipedia.

import wikipediaclass Wikipedia(BaseModel):    term: str = Field(description="Termine da cercare")@tool(args_schema=Wikipedia)def get_summary(term: str) -> str:    """Restituisce informazioni di base sul termine specificato fornite da Wikipedia"""    return wikipedia.summary(term)

Definiamo un dizionario con tutte le funzioni che il nostro modello conosce ora. Questo dizionario ci aiuterà a fare il routing in seguito.

toolkit = {    'percentage_difference': percentage_difference,    'get_monthly_active_users': get_monthly_active_users,    'get_summary': get_summary}analyst_functions = [format_tool_to_openai_function(f)   for f in toolkit.values()]

Ho apportato un paio di modifiche alla nostra configurazione precedente:

  • Ho apportato alcuni piccoli ritocchi al prompt di sistema per forzare LLM a consultare Wikipedia se ha bisogno di alcune conoscenze di base.
  • Ho cambiato il modello in GPT 4 perché è migliore nel gestire compiti che richiedono ragionamento.
from langchain.prompts import MessagesPlaceholdermodel = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview')\  .bind(functions = analyst_functions)prompt = ChatPromptTemplate.from_messages([    ("system", "Sei un analista di prodotto desideroso di aiutare il tuo team di prodotto. Sei molto rigoroso e preciso. \        Utilizzi solo le informazioni fornite nella richiesta iniziale. \        Se hai bisogno di determinare alcune informazioni, ad esempio il nome della capitale, puoi utilizzare Wikipedia."),    ("user", "{request}"),    MessagesPlaceholder(variable_name="osservazioni")])analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()

Possiamo invocare la nostra catena con tutte le funzioni. Iniziamo con una query abbastanza semplice.

result1 = analyst_chain.invoke({    'request': "Quanti utenti c'erano ad aprile 2023 a Berlino?",    "osservazioni": []})print(result1)

Nel risultato abbiamo una chiamata alla funzione get_monthly_active_users con i parametri di input — {'month': '2023–04–01', 'city': 'Berlino'}, che sembrano corretti. Il modello è stato in grado di trovare il giusto strumento e risolvere il compito.

Proseguiamo con una richiesta un po’ più complessa.

result2 = analyst_chain.invoke({    'request': "Come è cambiato il numero degli utenti provenienti dalla capitale della Germania tra aprile e maggio 2023?",    "osservazioni": []})

Facciamo una pausa di un minuto e pensiamo a come vorremmo che il modello ragionasse. È evidente che non ci sono informazioni sufficienti per il modello per rispondere immediatamente, quindi deve effettuare una serie di chiamate di funzione:

  • chiamata a Wikipedia per ottenere la capitale della Germania
  • chiamata alla funzione get_monthly_active_users due volte per ottenere MAU per aprile e maggio
  • chiamata a percentage_difference per calcolare la differenza tra le metriche.

Sembra piuttosto complesso. Vediamo se ChatGPT sarà in grado di gestire questa domanda.

Per la prima chiamata, LLM restituisce una chiamata di funzione a Wikipedia con i seguenti parametri — {'term': 'capitale della Germania'}. Finora, sta seguendo il nostro piano.

Forniamo l’osservazione e vediamo quali saranno i prossimi passaggi.

observation1 = toolkit[result2.tool](result2.tool_input)print(observation1)# La capitale della Germania è la città stato di Berlino. È la sede del presidente della Germania, il cui residenza ufficiale è lo Schloss Bellevue. Il Bundesrat ("Consiglio federale") rappresenta gli Stati federati (Bundesländer) della Germania e ha sede nell'ex Prussian Herrenhaus (House of Lords). Sebbene la maggior parte dei ministeri abbia sede a Berlino, alcuni di essi, così come alcuni dipartimenti minori, hanno sede a Bonn, l'ex capitale della Germania occidentale. Anche se Berlino è ufficialmente la capitale della Repubblica Federale di Germania, 8.000 dei 18.000 impiegati totali impiegati nella burocrazia federale lavorano ancora a Bonn, a circa 600 km (370 miglia) da Berlino.# fonte: https://it.wikipedia.org/wiki/Capitale_della_Germaniare sult3 = analyst_chain.invoke({    'request': "Come è cambiato il numero degli utenti provenienti dalla capitale della Germania tra aprile e maggio 2023?",    "osservazioni": format_to_openai_functions([(result2, observation1)])})

Il modello vuole eseguire get_monthly_active_users con gli argomenti {'month': '2023–04–01', 'city': 'Berlino'}. Facciamolo e restituiamo le informazioni al modello ancora una volta.

observation2 = toolkit[result3.tool](result3.tool_input)print(observation2)# 168result4 = analyst_chain.invoke({    'request': "Come è cambiato il numero degli utenti provenienti dalla capitale della Germania tra aprile e maggio 2023?",    "osservazioni": format_to_openai_functions([(result2, observation2)])})

Successivamente, il modello richiede di chiamare nuovamente get_monthly_active_users con gli argomenti {'month': '2023–05–01', 'city': 'Berlino'}. Finora, sta facendo un ottimo lavoro. Seguiamo la sua logica.

<!–observation3 = toolkit[result3.tool](result3.tool_input)print(observation3)# 1046result4 = analyst_chain.invoke({ 'request': "Come è cambiato il numero di utenti dalla capitale della Germania tra aprile e maggio 2023?", "observations": format_to_openai_functions( [(result1, observation1), (result2, observation2), (result3, observation3)])})

Il risultato successivo è una chiamata di funzione per percentage_difference con i seguenti argomenti {'metric1': 168, 'metric2': 1046}. Calcoliamo l’osservazione e invochiamo la catena di analisi ancora una volta. Speriamo che sia l’ultimo passaggio.

observation4 = toolkit[result4.tool](result4.tool_input)print(observation4)# 523.27result5 = analyst_chain.invoke({    'request': "Come è cambiato il numero di utenti dalla capitale della Germania tra aprile e maggio 2023?",    "observations": format_to_openai_functions(      [(result1, observation1), (result2, observation2),       (result3, observation3), (result4, observation4)])})

Alla fine, abbiamo ottenuto la seguente risposta dal modello: Il numero di utenti da Berlino, la capitale della Germania, è aumentato di circa il 523.27% tra aprile e maggio 2023.

Ecco lo schema completo delle chiamate LLM per questa domanda.

Illustrazione dell'autore

Nell’esempio sopra, abbiamo attivato le chiamate successive manualmente una per volta, ma può essere facilmente automatizzato.

È un risultato fantastico e siamo stati in grado di vedere come gli LLM possono ragionare e utilizzare più strumenti. Il modello ha impiegato 5 passi per ottenere il risultato, ma ha seguito il piano che avevamo delineato inizialmente, quindi è stato un percorso piuttosto logico. Tuttavia, se hai intenzione di utilizzare gli LLM in produzione, tieni presente che potrebbero commettere errori e introdurre processi di valutazione e controllo della qualità.

Puoi trovare il codice completo su GitHub.

Sommario

Questo articolo ci ha insegnato come potenziare gli LLM con strumenti esterni utilizzando le funzioni OpenAI. Abbiamo esaminato due casi d’uso: l’estrazione per ottenere un output strutturato e il routing per utilizzare informazioni esterne per le domande. Il risultato finale mi ispira, poiché gli LLM potrebbero rispondere a domande piuttosto complesse utilizzando tre strumenti diversi.

Torniamo alla domanda iniziale su se gli LLM possono sostituire gli analisti dei dati. Il nostro prototipo attuale è di base e lontano dalle capacità degli analisti junior, ma è solo l’inizio. Restate sintonizzati! Approfondiremo le diverse approcci degli agenti LLM. La prossima volta, cercheremo di creare un agente che possa accedere al database e rispondere a domande di base.

Riferimento

Questo articolo è ispirato al corso “Functions, Tools and Agents with LangChain” di DeepLearning.AI