- Fornire al modello un database e tre strumenti che lo leggono
- Descrivere quegli strumenti in modo che il modello sappia quando usare ciascuno
- Eseguire il ciclo che trasforma le chiamate a strumenti in risultati degli strumenti
- Osservarlo mentre richiede più strumenti contemporaneamente
- Restituire gli errori al modello invece di sollevarli
- Tracciare il confine tra ciò che il modello non farà e ciò che non può fare
Configurazione
Ti servono Python 3.9 o successivo, il pacchettorequests e una chiave API Venice. Consulta Generare una chiave API se non ne hai una. Tutto il resto è nella libreria standard.
agent.py con gli import e il blocco di intestazione che ogni chiamata riutilizza:
GET /models/traits associa nomi di trait stabili al modello che attualmente ricopre quel ruolo. Leggere function_calling_default all’avvio significa che il tuo agente continua a funzionare quando il modello sottostante viene sostituito. Consulta Modelli per l’elenco completo dei trait.1. Un database su cui vale la pena fare domande
Va bene qualsiasi file SQLite. Questo è un piccolo negozio con clienti, prodotti e gli ordini che li mettono in relazione, il che è sufficiente affinché una domanda vera richieda un join e un aggregato:2. Tre strumenti a cui il modello può ricorrere
Gli strumenti riflettono il modo in cui una persona affronta un database sconosciuto: scoprire cosa contiene, guardare da vicino una tabella e poi interrogarla.description non è un commento. È l’unica cosa che il modello legge quando decide quale strumento chiamare e cosa metterci:
3. Il ciclo
Il function calling è una conversazione, non una richiesta. Il modello risponde con chiamate a strumenti, tu le esegui, aggiungi i risultati e chiedi di nuovo. Termina quando il modello risponde con del contenuto invece che con chiamate.messages prima dei risultati. Porta con sé i tool_calls a cui i risultati rispondono, e su un modello di reasoning porta anche un campo reasoning_content. Ricostruire il messaggio a mano e scartare campi che non ti aspettavi è il modo più comune per rompere il secondo round.
Ogni risultato è associato alla sua chiamata tramite tool_call_id. Non c’è nient’altro a identificarlo.
max_rounds è un limite reale, non una formalità. Un modello che continua a interrogare senza concludere farà altrimenti loop finché non esaurirai la pazienza o il credito.
4. Cosa fa davvero
Aggiungi un blocco main ed eseguilo:stderr mentre avvengono, così puoi osservarlo lavorare:
Il round 4 è la parte che una singola chiamata a funzione non può fare. Il modello non poteva scrivere quella query finché non avesse visto la risposta a quella precedente.
La tua esecuzione non corrisponderà a questa chiamata per chiamata. Il modello a volte descrive tutte e tre le tabelle in una volta e a volte una alla volta, e occasionalmente salta
list_tables e indovina un nome. I numeri sono stabili perché provengono dal database; la strada per arrivarci no.
Il round 2 ha restituito tre chiamate agli strumenti in un’unica risposta, e il ciclo qui sopra le esegue una dopo l’altra. Sono indipendenti, quindi un
ThreadPoolExecutor qui vale la pena non appena i tuoi strumenti fanno I/O reale. Mantieni i messaggi tool nello stesso ordine delle chiamate che li hanno prodotti.usage mostra che dà i suoi frutti:
5. Lascia che gli errori raggiungano il modello
L’istinto è sollevare un’eccezione su una query malformata. Resistigli. Un errore è informazione, e il modello può agire di conseguenza. Chiedi di una tabella che non esiste:run_query ha restituito {"error": "OperationalError: no such table: purchases"} come un normale risultato dello strumento invece di sollevare un’eccezione, il modello l’ha letto, ha chiamato list_tables per scoprire cosa esistesse davvero e si è corretto. Se l’eccezione si fosse propagata, lo script sarebbe morto su un refuso.
È per questo che ogni strumento restituisce JSON anche nel percorso di fallimento. La regola è semplice: se un umano che sta facendo il debug del tuo strumento vorrebbe vedere il messaggio, allora lo vuole vedere anche il modello.
6. Ciò che non farà e ciò che non può fare
Chiedi all’agente di distruggere qualcosa:SELECT per i clienti spagnoli, non ne ha trovato nessuno perché la colonna memorizza ES invece di Spain, e ha riportato quello:
run_query è la parte che non dipende da una scelta:
Quella seconda riga è il motivo per cui
run_query cattura sqlite3.Warning insieme a sqlite3.Error. Il driver Python rifiuta le istruzioni impilate, ma solleva Warning per esse, e Warning non è una sottoclasse di Error. Catturare solo sqlite3.Error lascia sfuggire un’istruzione impilata dall’handler e uccide il ciclo invece di restituire un messaggio che il modello può leggere.Controllare quando usare gli strumenti
tool_choice decide quanta voce in capitolo abbia il modello:
"required" è più brutale di quanto sembri. Chiedere a questo agente What is 2 + 2? con tool_choice impostato su "required" lo fa chiamare list_tables, guardare un database di cui non ha bisogno e poi rispondere 4 al round successivo. Con "auto" risponde 4 immediatamente e non chiama nulla. Ricorri a "required" quando uno strumento deve davvero essere eseguito, ad esempio per registrare una richiesta, e lascialo stare altrimenti.
Regolare l’agente
Prossimi passi
Il ciclo che ora hai è lo stesso che sta dietro alla maggior parte degli agenti. Cambiano solo gli strumenti.- Sostituisci gli strumenti SQL con chiamate HTTP e diventa un agente API.
- Aggiungi Ricerca e scraping web come strumento e potrà controllare il web live a metà risposta.
- Chiedi un risultato tipizzato invece che prosa con le Risposte strutturate.
- Guarda una versione più ampia di questo pattern nell’Agente di ricerca privato.
Function Calling
Riferimento per l’array tools e per tool_choice.
Risposte strutturate
Vincola la risposta finale a uno schema JSON.
Prompt Caching
Mantieni economica la conversazione che cresce.
Agente di ricerca privato
Lo stesso ciclo con strumenti web e un pianificatore.