Ciao a tutti, sono Nina, di ritorno dal mio laboratorio digitale (che, ammettiamolo, è soprattutto il mio tavolo da cucina con molto caffè tiepido). Oggi voglio parlare di qualcosa che fa rumore nei miei canali Slack e che mi perseguita durante le mie sessioni di codifica a tarda notte: il mondo a volte frustrante, spesso brillante degli SDK di IA, in particolare quando si cerca di creare un agente conversazionale che sembri davvero una conversazione, non solo un bot FAQ glorificato.
La mia particolare ossessione degli ultimi tempi è stata integrare l’API Assistants di OpenAI. Ora, so cosa state pensando, “Nina, l’API Assistants? È roba vecchia!” E sì, non avete torto. È disponibile da un po’. Ma ascoltatemi: non sono qui per darvi un tutorial base su “come chiamare client.beta.assistants.create()”. Abbiamo tutti già visto quello. Il mio obiettivo oggi è parlarvi di un problema specifico e spinoso che ho incontrato e di come ho lottato con lo SDK per farlo funzionare come volevo: gestire conversazioni multi-turno con stato utilizzando strumenti personalizzati senza perdere la testa (o il contesto del mio utente). Pensate oltre a una semplice FAQ. Pensate a flussi di lavoro complessi, domande di follow-up e chiamate a strumenti dinamici basate sull’evoluzione dell’intenzione dell’utente.
Il Meno Facile del Mondo: Perché l’API Assistants (Continua) a Contare
Prima dell’API Assistants, costruire qualcosa di statale con i modelli di OpenAI sembrava spesso come giocolare con troppe palline in aria. Eravate responsabili della gestione della cronologia dei messaggi, dell’ingegneria delle richieste per il contesto e dell’orchestrazione delle chiamate agli strumenti da soli. Era… estenuante. L’API Assistants prometteva di alleviare molto di questo peso gestendo i thread, i messaggi e persino l’orchestrazione degli strumenti. E per i casi semplici, funziona magnificamente.
Il mio progetto attuale è costruire un assistente personale per le finanze “intelligente”. Non solo uno che ti dica il tuo saldo, ma uno che possa aiutarti a pianificare budget, suggerire strategie di investimento in base alla tua tolleranza al rischio e persino simulare scenari finanziari futuri. Questo richiede molti scambi, ricordare dichiarazioni precedenti (“Voglio risparmiare per una casa,” “Il mio reddito è di X,” “Cosa succederebbe se investissi in Y?”), e soprattutto, chiamare funzioni specifiche (come recuperare dati di borsa in tempo reale o eseguire un algoritmo di proiezione del budget) al momento giusto.
Il mio primo tentativo è stato un glorioso disastro. Facevo passare tutto l’historico dei messaggi avanti e indietro, cercando di infilare istruzioni complesse nell’invito del sistema e controllando manualmente se era necessaria una chiamata agli strumenti. Ha funzionato, ma era fragile, lento e un vero incubo da debuggare. È stato allora che ho deciso di fare davvero affidamento sull’API Assistants e sul suo SDK.
L’Abbraccio dello SDK: Thread, Messaggi e il Fuggitivo `run`
Al cuore dell’API Assistants si trova il concetto di `Threads` e `Messages`. Crei un `Thread` per una conversazione, aggiungi dei `Messages`, poi “esegui” l’`Assistant` su quel `Thread`. L’`Assistant` elabora quindi i messaggi, decide se deve chiamare uno strumento e genera una risposta. Abbastanza semplice, no?
Ecco dove diventa interessante. Il mio assistente finanziario deve fare cose come:
- Monitorare gli obiettivi: “Voglio risparmiare 50.000 $ per un deposito.”
- Raccogliere informazioni: “Qual è il tuo reddito mensile attuale?”
- Eseguire calcoli: “In base a ciò, quanto tempo ci vorrà se risparmio 1.000 $ al mese?”
- Fornire consigli: “Considera di diversificare il tuo portafoglio con fondi indicizzati a basso costo.”
Ognuna di queste attività richiede spesso una chiamata a uno strumento personalizzato. Ad esempio, `calculate_savings_timeline(goal_amount, monthly_income, monthly_savings)`. La sfida non è solo definire lo strumento; è portare l’Assistant a:
- Riconoscere che ha bisogno dello strumento.
- Identificare tutti i parametri necessari (anche se sono sparsi su diversi messaggi utente).
- Chiedere all’utente i parametri mancanti con tatto.
- Eseguire lo strumento e incorporare i risultati.
Il Problema dei Parametri: Quando l’Assistant Chiede di Più
Supponiamo che un utente dica, “Quanto tempo mi ci vorrà per risparmiare per una casa?” Il mio strumento `calculate_savings_timeline` ha bisogno di `goal_amount`, `monthly_income`, e `monthly_savings`. L’Assistant, essendo intelligente, realizzerà spesso che mancano informazioni. Lo SDK, attraverso l’oggetto `run`, ti dirà che `requires_action`.
Il mio approccio iniziale era semplicemente quello di aspettare lo stato `requires_action` e poi presentare un generico “Di quali informazioni hai bisogno?” all’utente. Era goffo. L’utente spesso non sapeva quali parametri erano necessari per lo strumento. Un approccio migliore, su cui mi sono infine imbattuto, è consistito nell’ispezionare i `tool_calls` nello stato `requires_action`.
def submit_tool_outputs(client, thread_id, run_id, tool_outputs):
return client.beta.threads.runs.submit_tool_outputs(
thread_id=thread_id,
run_id=run_id,
tool_outputs=tool_outputs
)
# ... nel tuo ciclo di conversazione ...
if run.status == 'requires_action':
tool_outputs = []
missing_params = {}
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Ecco il trucco: controlla se manca un parametro richiesto
# Presumiamo che i tuoi strumenti siano definiti con uno schema
# Per semplificare, diciamo che sappiamo che 'monthly_savings' è sempre necessario
if function_name == "calculate_savings_timeline" and "monthly_savings" not in arguments:
missing_params['monthly_savings'] = 'Quanto prevedi di risparmiare ogni mese?'
# ... verifica dei parametri più sofisticata qui ...
if missing_params:
# Informa l'utente di cosa manca
user_prompt = "Ho bisogno di un po' più di informazioni per aiutarti. "
for param, question in missing_params.items():
user_prompt += f"{question} "
return {"status": "waiting_for_user_input", "prompt": user_prompt}
else:
# Tutti i parametri sono presenti, esegui lo strumento
output = execute_tool_function(function_name, arguments) # La tua funzione per eseguire la logica dello strumento
tool_outputs.append({
"tool_call_id": tool_call.id,
"output": json.dumps(output)
})
if tool_outputs:
run = submit_tool_outputs(client, thread.id, run.id, tool_outputs)
# Continua a interrogarе il run fino a quando non è completato o richiede una nuova azione
else:
# Questo caso gestisce se abbiamo rilevato parametri mancanti e abbiamo chiesto all'utente
pass
Questo codice è una versione semplificata, ma l’idea principale è interceptare `requires_action`, vedere cosa l’Assistant vuole fare, e se mancano argomenti, chiedere specificamente all’utente. Questo rende la conversazione molto più naturale, come un umano che fa domande di chiarimento, piuttosto che un generico “errore”.
Il Meno Sottile: Mantenere Tracce tra i Turni
Uno dei più grandi mal di testa era assicurarsi che, se un utente forniva un’informazione (“I miei redditi sono di 5.000 $ al mese”), l’Assistant la ricordasse per future chiamate agli strumenti all’interno della stessa conversazione, anche se non veniva immediatamente utilizzata. L’API Assistants gestisce piuttosto bene questo all’interno di un `Thread` mantenendo la cronologia dei messaggi. Tuttavia, a volte, vuoi guidarlo esplicitamente o fornirgli informazioni che non fanno parte di un messaggio diretto dell’utente.
Mi sono ritrovato a utilizzare `metadata` sugli oggetti `Thread` e `Message` più di quanto pensassi inizialmente. Ad esempio, se l’utente indica esplicitamente la sua tolleranza al rischio, potrei memorizzare questo nelle metadati del thread. Anche se l’Assistant non “legge” direttamente queste metadati nel suo processo di ragionamento, sono inestimabili per la mia logica applicativa quando devo precompilare argomenti per chiamate agli strumenti o prendere decisioni sugli strumenti che sono anche pertinenti.
Un altro modello che ho adottato è stato quello di iniettare messaggi “simili a sistemi” nel thread se dovevo ricordare esplicitamente all’Assistant alcuni fatti derivati da una chiamata a uno strumento o da un processo interno. Ad esempio, dopo che `calculate_savings_timeline` ha restituito che ci vorrebbero 10 anni, potrei aggiungere un messaggio come : `{“role”: “user”, “content”: “Il piano di risparmio dell’utente indica un lasso di tempo di 10 anni per raggiungere il suo obiettivo.”}`. Questo non proviene direttamente dall’utente, ma aiuta a rafforzare il contesto per le risposte successive dell’Assistant.
Gestione dell’Uscita dello Strumento e delle Azioni Successive
Quando uno strumento viene eseguito, ottieni un’uscita. L’Assistant prende quindi questa uscita e genera una risposta. Ma cosa succede se l’uscita dello strumento stessa suscita un nuovo insieme di domande o una nuova chiamata a uno strumento? Ad esempio, lo strumento `calculate_savings_timeline` potrebbe restituire che l’obiettivo è irraggiungibile con i risparmi attuali. L’Assistant dovrebbe idealmente suggerire, “Forse dovremmo esaminare dei modi per aumentare i tuoi risparmi mensili o ridurre il tuo importo obiettivo.” Non si tratta solo di un’uscita testuale; si tratta di concatenare passaggi logici.
Il SDK dell’API Assistants gestisce questo magnificamente mantenendo attivo l’oggetto `run`. Dopo aver eseguito `submit_tool_outputs`, il `run` passa spesso a `in_progress`, per poi generare un nuovo `requires_action` (per un’altra chiamata a uno strumento) o `completed`. Il mio ciclo principale per gestire il flusso di conversazione assomiglia a qualcosa del genere :
def ottenere_risposta_assistant(client, thread_id, assistant_id, messaggio_utente):
client.beta.threads.messages.create(
thread_id=thread_id,
role="user",
content=messaggio_utente
)
run = client.beta.threads.runs.create(
thread_id=thread_id,
assistant_id=assistant_id
)
while run.status in ['queued', 'in_progress', 'cancelling']:
time.sleep(1) # Sii un buon cittadino, non sovraccaricare l'API
run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
if run.status == 'completed':
messages = client.beta.threads.messages.list(thread_id=thread_id, order="desc")
for msg in messages.data:
if msg.role == "assistant":
# Trovare l'ultimo messaggio dell'assistant, spesso il primo
return msg.content[0].text.value # Presumendo che il contenuto sia testuale
return "Nessuna risposta dall'assistant."
elif run.status == 'requires_action':
uscite_strumento = []
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
nome_funzione = tool_call.function.name
argomenti = json.loads(tool_call.function.arguments)
# Qui, la tua logica personalizzata per gestire i parametri mancanti o eseguire strumenti
# Per questo esempio semplificato, supponiamo che tutti i parametri siano presenti e che stiamo eseguendo
print(f"L'assistant vuole chiamare {nome_funzione} con args : {argomenti}")
output = execute_tool_function(nome_funzione, argomenti) # La tua logica di esecuzione dello strumento
uscite_strumento.append({
"tool_call_id": tool_call.id,
"output": json.dumps(output)
})
# Inviare le uscite degli strumenti e continuare l'esecuzione
run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread_id,
run_id=run.id,
tool_outputs=uscite_strumento
)
# Chiamare questa funzione ricorsivamente per elaborare l'esecuzione aggiornata
# Fai attenzione alla profondità di ricorsione in produzione, potresti aver bisogno di un ciclo
return ottenere_risposta_assistant(client, thread_id, assistant_id, "") # Passare un messaggio vuoto
elif run.status == 'failed':
return f"L'esecuzione dell'assistant è fallita : {run.last_error.message}"
else:
return f"Stato di esecuzione inaspettato : {run.status}"
Questa funzione `ottenere_risposta_assistant`, sebbene semplificata, mostra il ciclo centrale. L’essenziale è che `requires_action` non è un vicolo cieco. È un’opportunità per la tua applicazione di intervenire, eseguire lo strumento richiesto e poi restituire i risultati all’Assistant per continuare il suo ragionamento. Questo feedback in feedback chiuso è ciò che rende possibili conversazioni veramente dinamiche e scalabili.
Punti da ricordare per il tuo prossimo progetto di assistente IA
- Adotta il ciclo di vita `run.status` : Non limitarti a controllare se è `completed`. `requires_action` è il tuo amico, non un errore. Crea gestori solidi per ogni stato pertinente.
- Ispeziona `tool_calls` per i parametri mancanti : Invece di semplici indicazioni, immergiti in `run.required_action.submit_tool_outputs.tool_calls` per capire *esattamente* di cosa ha bisogno l’Assistant. Questo migliora la tua esperienza utente.
- Uso strategico dei messaggi di tipo sistema : Se la tua logica di applicazione interna deduce nuovi fatti o deve sottolineare alcuni contesti, considera di iniettare messaggi “utente” che rappresentano questi fatti. L’Assistant li tratterà come parte della cronologia del thread.
- Metadati per lo stato dell’applicazione : Anche se l’Assistant non può direttamente “leggere” i metadati dei thread o dei messaggi per il suo ragionamento, è un posto potente per la tua applicazione per memorizzare e recuperare stati pertinenti alla conversazione. Pensa alle preferenze utente, alle variabili di sessione attuali, ecc.
- Testa casi particolari per l’orchestrazione degli strumenti : Cosa succede se uno strumento fallisce? Cosa succede se l’utente cambia idea durante l’invocazione di uno strumento? Progetta la tua `execute_tool_function` e gestisci gli errori nel ciclo `requires_action` con attenzione.
Costruire agenti IA conversazionali veramente intelligenti non riguarda solo la scelta dell’ultimo modello. Si tratta di maneggiare abilmente i SDK che forniscono per gestire le complessità dell’interazione umana. L’API degli Assistants OpenAI, con un po’ di ingegneria riflessiva attorno alla sua gestione dello stato e all’orchestrazione degli strumenti, può davvero migliorare la tua esperienza conversazionale oltre un semplice scambio. È ancora un viaggio, e sto ancora imparando nuove astuzie, ma spero che queste idee ti evitino alcune notti insonni e caffè freddi!
Articoli correlati
- Arting IA : Libera il tuo potenziale creativo con i generatori d’arte IA
- Migliori strumenti di documentazione API per sviluppatori
- Strumenti di produttività degli sviluppatori che hanno effettivamente cambiato il modo in cui pubblico codice
🕒 Published: