Salta ai contenuti

Scrivi qualcosa per cercare nel manuale

    Tool use, function calling, structured output

    ~ min di lettura

    Riepilogo in 30 secondi
    • Il tool use (o function calling: stesso meccanismo, due nomi) fa sì che il modello, invece di rispondere, ti chieda di eseguire una funzione che gli hai descritto tu. Il modello non esegue niente: chiede, esegui tu.
    • Ogni strumento lo descrivi con nome, descrizione e uno schema JSON degli argomenti. Il modello decide se e quale chiamare, e con quali valori.
    • Il giro è a più turni: la sua richiesta di chiamata, tu esegui e gli rimandi il risultato, lui produce la risposta finale.
    • L’output strutturato è l’altra faccia: quando ti serve un dato in una forma fissa (JSON), non un’azione. Il trucco portabile è un solo strumento il cui schema è la forma che vuoi, forzato con tool_choice.
    • Il modello può sbagliare o inventare gli argomenti: validali sempre. E ogni strumento che gli dai è una capacità in più, quindi anche una superficie di rischio in più.

    Il modello di base sa fare una cosa sola: produrre testo. Ma spesso ti serve altro. Che compia un’azione (interrogare un database, chiamare un’API, fare un conto affidabile) o che ti dia un dato in una forma precisa che il tuo codice legge senza indovinare. Tool use e output strutturato sono i due meccanismi per questo, e sotto sono lo stesso meccanismo usato in due modi.

    «Tool use» e «function calling» sono la stessa cosa con due nomi diversi: Anthropic la chiama tool use, OpenAI function calling. L’idea è controintuitiva: il modello non esegue niente. Tu gli descrivi degli strumenti che potrebbe usare, e lui, invece di rispondere in prosa, può decidere di chiederti di eseguirne uno, dicendoti quale e con quali argomenti. L’esecuzione la fai tu, nel tuo codice, e gli rimandi il risultato. Lui prosegue da lì.

    Ogni strumento lo dichiari con tre cose: un nome, una descrizione, e uno schema JSON degli argomenti che accetta. La descrizione conta più di quanto sembri: è il testo con cui il modello capisce quando quello strumento è quello giusto.

    Un esempio. Dai al modello un solo strumento, get_weather, e gli chiedi il meteo di Roma.

    tools = [
    {
    "name": "get_weather",
    "description": "Restituisce il meteo attuale di una città.",
    "input_schema": {
    "type": "object",
    "properties": {
    "city": {"type": "string", "description": "Nome della città, es. Roma"},
    },
    "required": ["city"],
    },
    }
    ]
    conversazione = [{"role": "user", "content": "Che tempo fa a Roma?"}]
    message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    tools=tools,
    messages=conversazione,
    )
    # stop_reason == "tool_use": il modello chiede di chiamare lo strumento
    blocco = next(b for b in message.content if b.type == "tool_use")
    risultato = get_weather(**blocco.input) # la TUA funzione, es. "24 gradi, sereno"
    conversazione.append({"role": "assistant", "content": message.content})
    conversazione.append({"role": "user", "content": [
    {"type": "tool_result", "tool_use_id": blocco.id, "content": risultato},
    ]})
    finale = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    tools=tools,
    messages=conversazione,
    )
    print(finale.content[0].text) # -> A Roma ci sono 24 gradi e cielo sereno.

    Il punto chiave è stop_reason. Vale tool_use invece di end_turn: è il segnale che il modello vuole uno strumento, non che ha finito. Accodi la sua richiesta e il tuo risultato alla lista dei messaggi, la stessa che rimandi a ogni chiamata senza stato, come nella prima lezione del modulo, e richiami. In un caso reale questo diventa un ciclo: finché stop_reason è tool_use, esegui e rimandi; quando è end_turn, hai la risposta.

    L’altra faccia del tool use non fa agire il modello: lo costringe a darti un dato in una forma fissa. Ti serve quando vuoi un oggetto JSON che il tuo codice legge direttamente, non una frase da cui estrarre i campi con una regex fragile.

    Il modo più portabile è un trucco. Definisci un solo strumento il cui schema è la forma che vuoi, e forzi il modello a chiamarlo con tool_choice. Non esegui niente: quello che ti interessa sono gli argomenti che il modello ha riempito.

    tools = [{
    "name": "salva_contatto",
    "description": "Salva i dati di contatto estratti dal testo.",
    "input_schema": {
    "type": "object",
    "properties": {
    "nome": {"type": "string"},
    "email": {"type": "string"},
    "azienda": {"type": "string"},
    },
    "required": ["nome", "email"],
    },
    }]
    message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=256,
    tools=tools,
    tool_choice={"type": "tool", "name": "salva_contatto"}, # forza la chiamata
    messages=[{"role": "user",
    "content": "Ciao, sono Anna Rossi della Acme, [email protected]"}],
    )
    dati = message.content[0].input
    # -> {"nome": "Anna Rossi", "email": "[email protected]", "azienda": "Acme"}

    Ora dati è un oggetto già strutturato, con i campi che hai chiesto. Niente parsing del testo libero, niente casi limite del tipo «e se la mail va a capo». Lo schema JSON è anche un contratto: required dice quali campi devono esserci, i tipi dicono cosa aspettarti. Per un JSON semplice esiste anche la scorciatoia vista in System prompt e ruoli: chiudere con un turno assistant che contiene {. Funziona, ma lo schema di uno strumento è più solido, perché dichiara i campi e i tipi invece di sperare che il modello indovini la forma.

    • Valida sempre gli argomenti. Il modello riempie lo schema, ma può sbagliare un valore o inventarlo. Lo schema garantisce la forma, non che il contenuto sia giusto: tratta gli argomenti come input non fidato.
    • Puoi forzare o lasciar scegliere. Con tool_choice obblighi il modello a usare un tool, utile per l’estrazione; senza, decide lui se e quale chiamare, utile quando alcune domande non richiedono strumenti.
    • Gli strumenti costano token. Le definizioni entrano nella finestra a ogni chiamata, come hai visto in Il contesto in token. Molti strumenti descritti a lungo pesano: tienili essenziali.
    • Uno strumento è una capacità, quindi un rischio. Se un tool scrive su un database o manda una mail, un input malevolo che convince il modello a chiamarlo nel modo sbagliato diventa un problema di sicurezza. È il tema di Prompt injection e sicurezza.

    Il tool use fa arrivare al modello dati e capacità che decidi tu. Un caso frequente è dargli accesso a una tua base di documenti: recuperare i pezzi giusti e passarglieli nel prompt, invece di infilare tutto nel contesto. È la prossima lezione.