Un modello linguistico, da solo, sa solo generare testo. Non conosce la data di oggi, non puo' leggere il tuo database, non sa che tempo fa a Milano in questo momento e non puo' inviare una email. Il function calling (chiamato tool use da Anthropic) e' il meccanismo che colma questa distanza: permette al modello di chiedere al tuo programma di eseguire una funzione — per esempio "cerca il meteo di Milano" — ricevere il risultato e usarlo per comporre la risposta finale. E' il mattone fondamentale di ogni agente IA.

A chi serve e cosa imparerai

Questa guida e' per chi ha gia' fatto qualche chiamata alle API di un modello (OpenAI, Claude o Gemini) e vuole fare il salto successivo: collegare l'IA al mondo reale. Useremo Python e le API di OpenAI come esempio, ma il concetto e' identico negli altri SDK. Al termine saprai definire uno strumento, gestire il "ciclo" delle chiamate e costruire un mini-assistente capace di usare funzioni che scrivi tu.

Prerequisiti: Python 3.8+, la libreria openai installata (pip install openai) e una chiave API impostata nella variabile d'ambiente OPENAI_API_KEY.

Come funziona davvero: il malinteso da chiarire

Il punto piu' frainteso e' questo: il modello non esegue le funzioni. Non si collega a internet ne' lancia codice da solo. Quello che fa e' molto piu' sottile: tu gli descrivi quali strumenti ha a disposizione, e lui — quando lo ritiene utile — risponde non con del testo, ma con una richiesta strutturata del tipo "esegui la funzione meteo con parametro citta'=Milano". A quel punto sei tu, nel tuo codice, a eseguire davvero la funzione e a rimandargli il risultato. Il modello poi lo trasforma in una risposta in linguaggio naturale.

Il flusso completo ha quindi quattro fasi: 1) invii la domanda e la lista degli strumenti; 2) il modello chiede di chiamare uno strumento; 3) tu esegui e restituisci il risultato; 4) il modello produce la risposta finale.

Passo 1: scrivere la funzione e descriverla

Partiamo da una funzione Python vera, qui semplificata. In un caso reale chiamerebbe un'API meteo; per l'esempio restituisce un valore fisso:

def get_meteo(citta):
    # In produzione qui chiameresti una vera API meteo
    dati = {"Milano": "22 gradi, sereno", "Roma": "27 gradi, poco nuvoloso"}
    return dati.get(citta, "dati non disponibili")

Ora va descritta al modello con uno schema, in modo che sappia quando e come usarla. Si usa il formato JSON Schema:

tools = [{
    "type": "function",
    "function": {
        "name": "get_meteo",
        "description": "Restituisce il meteo attuale di una citta' italiana",
        "parameters": {
            "type": "object",
            "properties": {
                "citta": {"type": "string", "description": "Il nome della citta', es. Milano"}
            },
            "required": ["citta"]
        }
    }
}]

La descrizione e' cruciale: e' l'unico modo che il modello ha per capire a cosa serve lo strumento e quando invocarlo. Scrivila con cura, come se spiegassi la funzione a un collega.

Lo schema JSON descrive al modello nome, scopo e parametri della funzione. Immagine: Pexels.

Passo 2: il ciclo completo delle chiamate

Ecco l'esempio integrale che mette insieme le quattro fasi. Da leggere con attenzione, perche' e' lo schema che ritroverai in ogni agente:

import json
from openai import OpenAI

client = OpenAI()
messaggi = [{"role": "user", "content": "Che tempo fa a Milano?"}]

# Fase 1 e 2: il modello decide se chiamare lo strumento
r = client.chat.completions.create(
    model="gpt-4o",
    messages=messaggi,
    tools=tools,
)
msg = r.choices[0].message
messaggi.append(msg)

# Fase 3: eseguiamo le funzioni richieste e restituiamo i risultati
if msg.tool_calls:
    for chiamata in msg.tool_calls:
        args = json.loads(chiamata.function.arguments)
        risultato = get_meteo(args["citta"])
        messaggi.append({
            "role": "tool",
            "tool_call_id": chiamata.id,
            "content": risultato,
        })

    # Fase 4: il modello compone la risposta finale usando il risultato
    r2 = client.chat.completions.create(model="gpt-4o", messages=messaggi)
    print(r2.choices[0].message.content)
else:
    print(msg.content)

Risultato atteso: una frase naturale come "A Milano ci sono 22 gradi ed e' sereno". Nota che abbiamo richiamato il modello due volte: la prima per ottenere la richiesta di chiamata, la seconda per la risposta finale. Questo "andata e ritorno" e' il cuore del function calling.

Passo 3: piu' strumenti e chiamate in parallelo

Nulla ti vieta di passare piu' strumenti contemporaneamente (meteo, calcolatrice, ricerca su database): il modello sceglie quali usare. I modelli recenti possono anche richiedere piu' chiamate in parallelo in un solo turno — per questo nel codice cicliamo su msg.tool_calls invece di gestirne una sola. La regola d'oro: per ogni chiamata richiesta devi restituire un messaggio con ruolo tool e lo stesso tool_call_id, altrimenti la conversazione va in errore.

Lo stesso concetto con Claude e Gemini

Il meccanismo e' universale. Con le API di Anthropic si passa il parametro tools e il modello risponde con blocchi di tipo tool_use, a cui rispondi con blocchi tool_result. Con Gemini di Google la logica e' analoga, con le function declarations. Cambiano i nomi dei campi, non l'idea: descrivi lo strumento, il modello lo richiede, tu lo esegui, lui usa il risultato.

Concatenando piu' strumenti si costruisce un vero e proprio agente. Immagine: Pexels.

Errori comuni e come evitarli

  • Il modello non chiama mai lo strumento: la descrizione e' troppo vaga. Rendila esplicita e indica con esempi quando usarlo.
  • "messages with role 'tool' must respond to a tool_call": hai dimenticato di restituire un risultato per ogni tool_call_id, oppure non hai accodato il messaggio dell'assistente prima dei risultati.
  • Argomenti malformati: il modello potrebbe produrre JSON non valido. Proteggi sempre json.loads con un blocco try/except e, in caso di errore, restituisci un messaggio che spieghi il problema cosi' il modello puo' riprovare.
  • Loop infiniti: in un agente che concatena strumenti, imposta un numero massimo di iterazioni per sicurezza.

Quando usarlo (e quando no)

Il function calling e' indispensabile quando il modello ha bisogno di dati aggiornati o azioni reali: meteo, prezzi, ricerche nel tuo database, invio di messaggi, interrogazione di un gestionale. Non serve, invece, quando il compito e' puramente di testo (riassumere, tradurre, riscrivere): in quei casi aggiungere strumenti complica inutilmente il codice. Padroneggiato questo schema, hai in mano l'ingrediente principale degli agenti: da qui puoi costruire assistenti che prenotano, calcolano, cercano e agiscono per conto tuo, sempre tenendo il controllo su cosa possono e non possono fare.