Avanzato 13 minAPI

Chiamate a funzioni e output JSON strutturati con Ollama

Il function calling permette a un LLM di decidere autonomamente di chiamare una funzione del tuo codice — consultare il meteo, interrogare un database, inviare un'email — restituendo gli argomenti nel formato corretto. Il function calling con Ollama si basa su due componenti: il parametro format per garantire un JSON valido e il campo tools dell'API per dichiarare le funzioni disponibili. Questa guida mostra entrambi in Python, quali modelli locali sono davvero affidabili e come rendere il tutto robusto con la validazione dello schema e i tentativi di ripetizione in caso di errore.

Di Mohamed Meguedmi·Agg. 2026-08-27·Testato su Windows, macOS e Linux

#Perché il function calling in locale

Un LLM genera testo, non azioni. Il function calling riempie questo vuoto: invece di rispondere in prosa, il modello restituisce un oggetto strutturato che dice « chiama la funzione get_meteo con la città Parigi ». Il tuo codice esegue la funzione, ottiene il risultato reale, poi lo restituisce al modello che scrive la risposta finale. Questo è il meccanismo di base degli agenti e degli assistenti che interagiscono con il mondo esterno.

In locale, la sfida è duplice. Innanzitutto garantire che l'output sia un JSON analizzabile al 100% — un modello prolisso che aggiunge «Ecco il JSON:» compromette tutta la tua pipeline. Poi assicurarsi che il modello scelga la funzione giusta con gli argomenti corretti, cosa che diventa delicata con i modelli piccoli. Ollama gestisce entrambi gli aspetti tramite la sua API, ma con misure di salvaguardia da conoscere.

Output JSON garantiti
Il parametro format vincola la decodifica: il modello può produrre solo JSON sintatticamente valido, e persino conforme a uno schema preciso.
Chiamata di funzione
Il campo tools dichiara funzioni nel formato OpenAI; il modello restituisce tool_calls con gli argomenti da passare.
100 % locale
Tutto funziona sulla tua macchina attraverso il daemon Ollama su http://localhost:11434, senza chiave API né fughe di dati.

#Prerequisiti e modelli supportati

Il kit Copilota Locale

Questa guida ti porta al modello. Il kit ti porta al copilota che scrive codice nel tuo editor.

  • Spazio online a vita
  • PDF + file
  • Aggiornamenti a vita

La modalità JSON (parametro format) funziona con qualsiasi modello. Il function calling tramite tools, invece, richiede un modello addestrato all'uso degli strumenti — altrimenti il campo tool_calls rimane vuoto. Non tutti i modelli si equivalgono: un 3B «compatibile» sulla carta sbaglia spesso gli argomenti, mentre un 14B+ se la cava bene con schemi semplici.

Ollama installato
Daemon avviato e raggiungibile su http://localhost:11434. Verifica con ollama list.
SDK Python
pip install ollama pydantic — le client officiel plus Pydantic pour la validation.
Modello affidabile per l'uso di strumenti
qwen3.5:9b, mistral-small (24B) e gpt-oss:20b sono eccellenti punti di partenza nel 2026. In Q4: Qwen 3.5 9B ≈ 6,6 GB, un 24B ≈ 14 GB di VRAM.
GPU consigliato
Una RTX 3060 da 12 GB fa girare comodamente Qwen 3.5 9B; punta a un modello da 20-24B (RTX 4070/4080 da 16 GB) per un uso davvero affidabile degli strumenti.
i
Modalità JSON ≠ chiamata di funzioni
Il parametro format garantisce un JSON valido, ma non provoca una chiamata di funzione: il modello compila un oggetto che TU interpreti. Il campo tools, invece, attiva una vera selezione di una funzione da parte del modello. Spesso si combinano i due.

#Forzare un JSON valido con il parametro format

Il caso più semplice: vuoi che il modello risponda sempre in JSON, mai in testo libero. Passa format: 'json' alla chiamata chat. Ollama vincola quindi la decodifica token per token per produrre un oggetto sintatticamente valido. Importante: mantieni un'istruzione esplicita nel prompt che descriva i campi attesi, altrimenti il modello inventa una struttura.

json_mode.py
import ollama
import json

resp = ollama.chat(
    model='qwen3.5:9b',
    messages=[{
        'role': 'user',
        'content': (
            "Extrais le nom, la ville et l'age de ce texte et reponds "
            "UNIQUEMENT en JSON avec les cles nom, ville, age. "
            "Texte : Marie, 34 ans, habite a Lyon."
        ),
    }],
    format='json',  # contraint la sortie a un JSON valide
    options={'temperature': 0},
)

data = json.loads(resp['message']['content'])
print(data)  # {'nom': 'Marie', 'ville': 'Lyon', 'age': 34}
→
Sempre temperature 0
Per l'estrazione strutturata, imposta temperature a 0. Vuoi determinismo e conformità, non creatività. Questo riduce notevolmente le allucinazioni relative ai campi.

#JSON strutturato secondo uno schema (structured outputs)

Da fine 2024, Ollama accetta anche uno schema JSON completo in format (non solo la stringa 'json'). La decodifica è quindi vincolata a rispettare lo schema: tipi, campi obbligatori, enumerazioni. È molto più robusto della sola stringa 'json', perché il modello non può strutturalmente produrre un oggetto non conforme. Con Pydantic, lo schema viene generato automaticamente.

structured_output.py
import ollama
from pydantic import BaseModel

class Personne(BaseModel):
    nom: str
    ville: str
    age: int

resp = ollama.chat(
    model='mistral-small',
    messages=[{'role': 'user',
               'content': 'Marie, 34 ans, habite a Lyon.'}],
    format=Personne.model_json_schema(),  # schema JSON complet
    options={'temperature': 0},
)

# validation stricte : leve une erreur si non conforme
personne = Personne.model_validate_json(resp['message']['content'])
print(personne)  # nom='Marie' ville='Lyon' age=34

Qui la decodifica è vincolata alla struttura di Personne e model_validate_json esegue un ulteriore controllo di validazione in Python. Doppia rete di sicurezza: l'output è garantito analizzabile dal parser E conforme ai tipi dichiarati. È il pattern consigliato per qualsiasi estrazione di dati in produzione locale.

#L'API tools passo passo in Python

Passiamo al vero function calling. Si dichiarano le funzioni nel campo tools nel formato OpenAI (name, description, parameters in JSON Schema). Il modello legge queste definizioni e, se ritiene utile chiamare una funzione, restituisce uno o più tool_calls al posto di un messaggio testuale. Spetta a te eseguire la funzione e restituire il risultato.

  1. 01
    Descrivere le funzioni
    Per ogni funzione, fornisci un name chiaro, una descrizione precisa (il modello la usa per scegliere) e un campo parameters in formato JSON Schema che elenchi gli argomenti e indichi quali sono required.
  2. 02
    Inviare la chiamata con tools
    Passa la lista tools a ollama.chat. Il modello decide da solo se chiamare una funzione o rispondere direttamente.
  3. 03
    Leggere le tool_calls
    Controlla resp['message'].get('tool_calls'). Se è presente, il modello vuole chiamare una funzione con gli argomenti forniti.
  4. 04
    Eseguire e restituire il risultato
    Chiama la vera funzione Python, poi rimanda il suo risultato al modello in un messaggio con role 'tool', affinché rediga la risposta finale.
tools_definition.py
def get_meteo(ville: str) -> str:
    # ici un vrai appel API ; on simule
    return f"Il fait 22 C et ensoleille a {ville}."

tools = [{
    'type': 'function',
    'function': {
        'name': 'get_meteo',
        'description': "Renvoie la meteo actuelle d'une ville donnee.",
        'parameters': {
            'type': 'object',
            'properties': {
                'ville': {
                    'type': 'string',
                    'description': 'Nom de la ville, ex: Paris',
                },
            },
            'required': ['ville'],
        },
    },
}]

#Il ciclo chiamata → esecuzione → risposta

Il function calling è uno scambio di andata e ritorno. Prima chiamata: il modello restituisce un tool_call. Esegui la funzione. Seconda chiamata: invii al modello il risultato e il modello scrive la risposta in linguaggio naturale. Ecco il ciclo completo, riutilizzabile per più funzioni.

boucle_tools.py
import ollama

dispatch = {'get_meteo': get_meteo}

messages = [{'role': 'user',
             'content': 'Quel temps fait-il a Marseille ?'}]

resp = ollama.chat(model='mistral-small',
                   messages=messages, tools=tools)
msg = resp['message']
messages.append(msg)

for call in msg.get('tool_calls') or []:
    fn = call['function']['name']
    args = call['function']['arguments']
    resultat = dispatch[fn](**args)  # execution reelle
    messages.append({
        'role': 'tool',
        'name': fn,
        'content': resultat,
    })

# second appel : le modele redige la reponse finale
final = ollama.chat(model='mistral-small', messages=messages)
print(final['message']['content'])
!
Non eseguire mai alla cieca gli argomenti
Il modello controlla il nome della funzione e i suoi argomenti. Usa un dizionario di dispatch (lista bianca) invece di eval o di un getattr dinamico e valida ogni argomento prima dell'esecuzione. Un modello compromesso o che produce allucinazioni non deve poter chiamare qualsiasi cosa.

#Validazione dello schema e dei pattern di retry

In locale, i piccoli modelli a volte sbagliano: argomenti mancanti, tipo errato, funzione inesistente. Non fidarti mai dell'output grezzo. Sottoponi ogni tool_call a una validazione con Pydantic e, se la validazione fallisce, riprova inserendo il messaggio di errore nel contesto: spesso il modello si corregge al secondo tentativo.

retry_validation.py
from pydantic import BaseModel, ValidationError

class MeteoArgs(BaseModel):
    ville: str

def valider_appel(call):
    fn = call['function']['name']
    if fn not in dispatch:
        raise ValueError(f"Fonction inconnue: {fn}")
    args = MeteoArgs.model_validate(call['function']['arguments'])
    return fn, args

def appel_avec_retry(messages, max_essais=3):
    for essai in range(max_essais):
        resp = ollama.chat(model='mistral-small',
                           messages=messages, tools=tools)
        try:
            calls = resp['message'].get('tool_calls') or []
            return [valider_appel(c) for c in calls], resp
        except (ValidationError, ValueError) as e:
            messages.append({
                'role': 'user',
                'content': f"Erreur: {e}. Corrige et reessaie.",
            })
    raise RuntimeError('Echec apres retries')
Validare prima di eseguire
Un modello Pydantic per funzione rileva gli argomenti mancanti o di tipo errato prima che raggiungano il tuo codice.
Riprova con feedback
Riinserire il messaggio di errore nel contesto guida il modello verso la correzione. 2-3 tentativi sono sufficienti quasi sempre.
Elenco delle funzioni consentite
Rifiuta qualsiasi nome di funzione non presente nel dispatch. È sia una misura di sicurezza sia una protezione contro le allucinazioni.
Fallback controllato
Dopo N fallimenti, rispondi all'utente con un messaggio chiaro anziché andare in crash — soprattutto con un modello piccolo.

#Le insidie dei piccoli modelli nell'uso degli strumenti

L'uso di strumenti è cognitivamente impegnativo: il modello deve comprendere l'intenzione, scegliere la funzione corretta, mappare gli argomenti e rispettare il formato. Al di sotto dei 7B, i risultati sono fragili. Ecco cosa si rompe più spesso in locale e come risolverlo.

tool_calls vuoto
Il modello risponde con testo invece di chiamare la funzione. Spesso un modello non addestrato all'uso degli strumenti o una descrizione della funzione troppo vaga. Passa a Qwen 3.5 o Mistral Small e cura le descrizioni.
Argomenti errati
Il modello inventa o dimentica campi. Impostali come required nello schema, riduci il numero di funzioni esposte contemporaneamente ed esegui sempre la validazione.
Funzione inventata dal modello
Il modello chiama una funzione che non esiste. È obbligatorio avere una lista bianca dal lato del dispatch.
JSON sporco
Senza formato, un modello piccolo aggiunge testo intorno al JSON. Usa sempre format='json' o uno schema per l'estrazione pura.
Troppe funzioni
Oltre 5-6 strumenti, i modelli piccoli si perdono. Suddividi per sottoattività oppure effettua il routing in due fasi.
→
Il giusto compromesso in locale
Per un function calling affidabile senza una GPU di fascia alta, mistral-small (24B) in Q4 (≈14 GB di VRAM) offre spesso il miglior rapporto qualità/risorse su una RTX 4080. Al di sotto di questa fascia, qwen3.5:9b (≈6,6 GB) se la cava con poche funzioni ben descritte e gpt-oss:20b è un'alternativa molto veloce. Per un uso decisamente orientato agli agenti, glm-4.7-flash (MoE 30B-A3B, ≈19 GB) eccelle se hai 24 GB di VRAM.

#Per approfondire

Il function calling è la base degli agenti e delle integrazioni avanzate. Queste guide del sito approfondiscono gli argomenti trattati in questa guida:

Integrare Ollama tramite API REST in Python
L'endpoint compatibile con OpenAI sulla porta :11434, lo streaming e la modalità JSON in una vera app FastAPI/Flask.
Creare un agente IA locale con LangChain e Ollama
Passare dal function calling di base a un agente completo che concatena strumenti, memoria e ragionamento.
MCP e LLM locale: collegare server MCP a Ollama
Standardizzare l’accesso agli strumenti (file, web, database) tramite Model Context Protocol invece di definire ogni funzione a mano.
Questa guida ti è stata utile?

Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.