Avancé 13 minAPI

Function calling et sorties JSON structurées avec Ollama

Le function calling permet à un LLM de décider lui-même d'appeler une fonction de votre code — chercher la météo, interroger une base, envoyer un e-mail — en renvoyant les arguments au bon format. Ollama function calling repose sur deux briques : le paramètre format pour garantir un JSON valide, et le champ tools de l'API pour déclarer les fonctions disponibles. Ce guide montre les deux en Python, quels modèles locaux sont réellement fiables, et comment blinder le tout avec validation de schéma et retry.

Par Mohamed Meguedmi·Màj 2026-07-20·Testé sur Windows, macOS, Linux

#Pourquoi le function calling en local

Un LLM génère du texte, pas des actions. Le function calling comble ce fossé : au lieu de répondre en prose, le modèle renvoie un objet structuré qui dit « appelle la fonction get_meteo avec la ville Paris ». Votre code exécute la fonction, récupère le résultat réel, puis le repasse au modèle qui rédige la réponse finale. C'est le mécanisme de base des agents et des assistants qui interagissent avec le monde extérieur.

En local, l'enjeu est double. D'abord garantir que la sortie soit un JSON parsable à 100 % — un modèle bavard qui ajoute « Voici le JSON : » casse tout votre pipeline. Ensuite s'assurer que le modèle choisisse la bonne fonction avec les bons arguments, ce qui devient délicat avec les petits modèles. Ollama gère les deux via son API, mais avec des garde-fous à connaître.

Sorties JSON garanties
Le paramètre format contraint le décodage : le modèle ne peut produire qu'un JSON syntaxiquement valide, voire conforme à un schéma précis.
Function calling
Le champ tools déclare des fonctions au format OpenAI ; le modèle renvoie des tool_calls avec les arguments à passer.
100 % local
Tout tourne sur votre machine via le daemon Ollama sur http://localhost:11434, sans clé API ni fuite de données.

#Prérequis et modèles compatibles

Le JSON mode (paramètre format) fonctionne avec n'importe quel modèle. Le function calling via tools, lui, exige un modèle entraîné pour le tool-use — sinon le champ tool_calls reste vide. Tous les modèles ne se valent pas : un 3B « compatible » sur le papier se trompe souvent d'arguments, tandis qu'un 14B+ tient la route sur des schémas simples.

Ollama installé
Daemon lancé et joignable sur http://localhost:11434. Vérifiez avec ollama list.
SDK Python
pip install ollama pydantic — le client officiel plus Pydantic pour la validation.
Modèle tool-use fiable
qwen2.5:14b, llama3.1:8b, mistral-nemo:12b sont de bons points de départ. En Q4_K_M : 8B≈5 Go, 14B≈9 Go de VRAM.
GPU conseillé
Une RTX 3060 12GB fait tourner un 8B confortablement ; visez 14B+ (RTX 4070/4080) pour du tool-use fiable.
i
JSON mode ≠ function calling
Le paramètre format garantit un JSON valide mais ne fait pas appeler de fonction : le modèle remplit un objet que VOUS interprétez. Le champ tools, lui, déclenche une vraie sélection de fonction par le modèle. On combine souvent les deux.

#Forcer un JSON valide avec le paramètre format

Le cas le plus simple : vous voulez que le modèle réponde toujours par un JSON, jamais par du texte libre. Passez format: 'json' à l'appel chat. Ollama contraint alors le décodage token par token pour produire un objet syntaxiquement valide. Important : gardez une instruction explicite dans le prompt décrivant les champs attendus, sinon le modèle invente une structure.

json_mode.py
import ollama
import json

resp = ollama.chat(
    model='llama3.1:8b',
    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}
Toujours temperature 0
Pour de l'extraction structurée, mettez temperature à 0. Vous voulez du déterminisme et de la conformité, pas de la créativité. Ça réduit nettement les hallucinations de champs.

#JSON structuré par schéma (structured outputs)

Depuis fin 2024, Ollama accepte aussi un schéma JSON complet dans format (pas seulement la chaîne 'json'). Le décodage est alors contraint à respecter le schéma : types, champs requis, énumérations. C'est bien plus robuste que 'json' seul, car le modèle ne peut structurellement pas produire un objet non conforme. Avec Pydantic, on génère le schéma automatiquement.

structured_output.py
import ollama
from pydantic import BaseModel

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

resp = ollama.chat(
    model='qwen2.5:14b',
    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

Ici le décodage est verrouillé sur la forme de Personne, et model_validate_json refait une passe de validation côté Python. Double filet de sécurité : la sortie est garantie parsable ET conforme aux types déclarés. C'est le pattern recommandé pour toute extraction de données en production locale.

#L'API tools pas à pas en Python

Passons au vrai function calling. On déclare les fonctions dans le champ tools au format OpenAI (name, description, parameters en JSON Schema). Le modèle lit ces définitions et, s'il juge utile d'appeler une fonction, renvoie un ou plusieurs tool_calls au lieu d'un message texte. À vous d'exécuter la fonction et de renvoyer le résultat.

  1. 01
    Décrire les fonctions
    Pour chaque fonction, donnez un name clair, une description précise (le modèle s'en sert pour choisir) et un parameters en JSON Schema listant les arguments et lesquels sont required.
  2. 02
    Envoyer l'appel avec tools
    Passez la liste tools à ollama.chat. Le modèle décide seul s'il appelle une fonction ou répond directement.
  3. 03
    Lire les tool_calls
    Inspectez resp['message'].get('tool_calls'). S'il est présent, le modèle veut appeler une fonction avec les arguments fournis.
  4. 04
    Exécuter et renvoyer
    Appelez la vraie fonction Python, puis repassez son résultat au modèle dans un message de role 'tool' pour qu'il rédige la réponse 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'],
        },
    },
}]

#La boucle appel → exécution → réponse

Le function calling est un aller-retour. Premier appel : le modèle renvoie un tool_call. Vous exécutez la fonction. Second appel : vous renvoyez le résultat, le modèle rédige la réponse en langage naturel. Voici la boucle complète, réutilisable pour plusieurs fonctions.

boucle_tools.py
import ollama

dispatch = {'get_meteo': get_meteo}

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

resp = ollama.chat(model='qwen2.5:14b',
                   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='qwen2.5:14b', messages=messages)
print(final['message']['content'])
!
N'exécutez jamais aveuglément les arguments
Le modèle contrôle le nom de la fonction et ses arguments. Utilisez un dictionnaire de dispatch (liste blanche) plutôt qu'un eval ou un getattr dynamique, et validez chaque argument avant exécution. Un modèle compromis ou halluciné ne doit pas pouvoir appeler n'importe quoi.

#Validation de schéma et patterns de retry

En local, les petits modèles ratent parfois : arguments manquants, mauvais type, fonction inexistante. Ne faites jamais confiance à la sortie brute. Encadrez chaque tool_call par une validation Pydantic, et si elle échoue, relancez avec le message d'erreur en contexte — souvent le modèle se corrige au second essai.

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='qwen2.5:14b',
                           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')
Valider avant d'exécuter
Un modèle de Pydantic par fonction attrape les arguments manquants ou mal typés avant qu'ils n'atteignent votre code.
Retry avec feedback
Réinjecter le message d'erreur dans le contexte guide le modèle vers la correction. 2-3 essais suffisent presque toujours.
Liste blanche de fonctions
Refusez tout nom de fonction hors dispatch. C'est à la fois une sécurité et un garde-fou contre les hallucinations.
Fallback gracieux
Après N échecs, répondez au user par un message clair plutôt que de planter — surtout avec un petit modèle.

#Les pièges des petits modèles en tool-use

Le tool-use est cognitivement exigeant : le modèle doit comprendre l'intention, choisir la bonne fonction, mapper les arguments et respecter le format. En dessous de 7B, les résultats sont fragiles. Voici ce qui casse le plus souvent en local et comment y remédier.

tool_calls vide
Le modèle répond en texte au lieu d'appeler la fonction. Souvent un modèle non entraîné au tool-use, ou une description de fonction trop floue. Passez à qwen2.5 ou llama3.1, et soignez les descriptions.
Mauvais arguments
Le modèle invente ou oublie des champs. Rendez-les required dans le schéma, réduisez le nombre de fonctions exposées à la fois, et validez systématiquement.
Fonction hallucinée
Le modèle appelle une fonction qui n'existe pas. Liste blanche obligatoire côté dispatch.
JSON pollué
Sans format, un petit modèle ajoute du texte autour du JSON. Utilisez toujours format='json' ou un schéma pour l'extraction pure.
Trop de fonctions
Au-delà de 5-6 tools, les petits modèles se perdent. Segmentez par sous-tâche ou faites du routage en deux étapes.
Le bon compromis en local
Pour du function calling fiable sans GPU haut de gamme, qwen2.5:14b en Q4_K_M (≈9 Go de VRAM) est souvent le meilleur rapport qualité/ressources. En dessous, llama3.1:8b tient sur peu de fonctions bien décrites.

#Pour aller plus loin

Le function calling est la brique de base des agents et des intégrations avancées. Ces guides du site prolongent celui-ci :

Intégrer Ollama via l'API REST en Python
L'endpoint OpenAI-compatible sur :11434, le streaming et le JSON mode dans une vraie app FastAPI/Flask.
Créer un agent IA local avec LangChain et Ollama
Passer du function calling brut à un agent complet qui enchaîne outils, mémoire et raisonnement.
MCP et LLM local : connecter des serveurs MCP à Ollama
Standardiser l'accès aux outils (fichiers, web, bases) via Model Context Protocol au lieu de définir chaque fonction à la main.
Ce guide vous a aidé ?

Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.