watch·ia
AccueilActusTutosGlossaireCette semaineTendancesSources
/
À chaud
20:47Des modèles d'Anthropic et OpenAI ont agi de manière autonome lors de tests cyber britanniques·19:30Jeff Dean et d'autres chercheurs de Google lancent Discovery Loop pour automatiser la recherche scientifique·16:47Demis Hassabis devient président de Google DeepMind et chief scientist d'Alphabet·15:14Des agents IA autonomes d'OpenAI et Anthropic tentent de pirater des cibles en ligne·20:47Des modèles d'Anthropic et OpenAI ont agi de manière autonome lors de tests cyber britanniques·19:30Jeff Dean et d'autres chercheurs de Google lancent Discovery Loop pour automatiser la recherche scientifique·16:47Demis Hassabis devient président de Google DeepMind et chief scientist d'Alphabet·15:14Des agents IA autonomes d'OpenAI et Anthropic tentent de pirater des cibles en ligne·20:47Des modèles d'Anthropic et OpenAI ont agi de manière autonome lors de tests cyber britanniques·19:30Jeff Dean et d'autres chercheurs de Google lancent Discovery Loop pour automatiser la recherche scientifique·16:47Demis Hassabis devient président de Google DeepMind et chief scientist d'Alphabet·15:14Des agents IA autonomes d'OpenAI et Anthropic tentent de pirater des cibles en ligne·20:47Des modèles d'Anthropic et OpenAI ont agi de manière autonome lors de tests cyber britanniques·19:30Jeff Dean et d'autres chercheurs de Google lancent Discovery Loop pour automatiser la recherche scientifique·16:47Demis Hassabis devient président de Google DeepMind et chief scientist d'Alphabet·15:14Des agents IA autonomes d'OpenAI et Anthropic tentent de pirater des cibles en ligne·

Le function calling : laisser une IA déclencher des actions

mercredi 17 juin 202607:128 min de lecture
L'essentiel — 3 points
  • 01Le function calling laisse l'IA demander l'exécution d'une fonction — mais c'est TON code qui l'exécute, toujours.
  • 02Le cycle tient en 4 temps : tu décris l'outil, l'IA demande l'appel, tu exécutes, tu renvoies le résultat.
  • 03À la fin tu as un script Python complet, compatible OpenAI/Mistral/Ollama, que tu adaptes à tes propres fonctions.
AGENTS

À la fin de ce tuto, tu auras un assistant en ligne de commande qui répond à « Où en est ma commande 4521 ? » en appelant réellement une fonction get_order_status() dans ton code, puis en formulant la réponse en français. Tu comprendras chaque étape du mécanisme, et tu pourras y brancher tes propres fonctions (base de données, API interne, envoi de mail…).

Durée : 20–30 min. Prérequis : Python 3.10+, savoir lancer un script, une clé API (OpenAI ou Mistral — le code est identique, on verra la nuance).

Le modèle mental (lis ça avant de coder)

Le malentendu numéro un : l'IA n'exécute jamais ton code. Elle ne fait que demander « j'aimerais appeler get_order_status avec order_id="4521" ». C'est ton programme qui lance la vraie fonction et lui renvoie le résultat. Tu gardes le contrôle total.

Le cycle complet tient en quatre temps :

1. TU décris les outils disponibles  ──►  envoyés au modèle avec la question
2. L'IA décide                        ──►  "appelle get_order_status(order_id='4521')"
3. TON code exécute la fonction       ──►  {"statut": "expédiée", ...}
4. TU renvoies le résultat à l'IA     ──►  "Votre commande 4521 a été expédiée, livraison le 25/06."

Garde ce schéma en tête : tout le reste n'est que la mise en œuvre de ces quatre flèches.

Étape 0 — Installation

pip install openai

Et ta clé dans l'environnement :

export OPENAI_API_KEY="sk-..."     # macOS / Linux
# setx OPENAI_API_KEY "sk-..."     # Windows (puis rouvrir le terminal)

Tu préfères Mistral ? Même code, tu changes juste le client : OpenAI(base_url="https://api.mistral.ai/v1", api_key="...") et le modèle (mistral-small-latest). Avec Ollama en local : base_url="http://localhost:11434/v1".

Étape 1 — Un appel simple, pour voir le problème

Avant les outils, regardons ce qui se passe sans. Crée assistant.py :

from openai import OpenAI

client = OpenAI()  # lit OPENAI_API_KEY automatiquement

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Où en est ma commande 4521 ?"}],
)
print(resp.choices[0].message.content)

Lance-le :

python assistant.py

Résultat typique :

Je n'ai pas accès à votre système de commandes. Pour connaître le statut
de la commande 4521, contactez le service client...

Logique : le modèle ne connaît pas tes commandes. C'est exactement ce trou que le function calling va combler.

Étape 2 — Décrire l'outil que l'IA pourra demander

On déclare l'outil dans un format précis (du JSON Schema). Ajoute ceci dans assistant.py :

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Récupère le statut et la date de livraison d'une commande à partir de son numéro.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "Le numéro de commande, ex. '4521'."
                }
            },
            "required": ["order_id"],
        },
    },
}]

Décortiquons, car chaque champ a un rôle :

  • name : l'identifiant que l'IA renverra quand elle voudra l'appeler.
  • description : le champ le plus important. C'est uniquement là-dessus que l'IA décide quand utiliser l'outil. Vague = elle l'appelle au mauvais moment ou jamais. Sois explicite sur ce que fait l'outil.
  • parameters : les arguments attendus, en JSON Schema. Ici un seul, order_id, de type string.
  • required : les arguments obligatoires.

Étape 3 — Premier aller-retour : l'IA demande l'appel

Maintenant on renvoie la question avec les outils, et on observe ce que l'IA répond :

messages = [{"role": "user", "content": "Où en est ma commande 4521 ?"}]

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)

msg = resp.choices[0].message
print(msg.tool_calls)

Cette fois, msg.content est vide, mais msg.tool_calls contient :

[ChatCompletionMessageToolCall(
    id='call_abc123',
    function=Function(
        name='get_order_status',
        arguments='{"order_id":"4521"}'   # ⚠️ une CHAÎNE, pas un dict
    ),
    type='function'
)]

L'IA n'a rien exécuté : elle te dit « voilà la fonction que je veux, avec ces arguments ». Note que arguments est une chaîne JSON — on devra la parser. Le champ id servira à relier le résultat à cette demande précise.

Étape 4 — Exécuter la fonction dans ton code

C'est ici que tu prends la main. La vraie fonction (remplace plus tard par ta base de données) :

import json

ORDERS = {
    "4521": {"statut": "expédiée", "livraison_estimée": "2026-06-25"},
    "4522": {"statut": "en préparation", "livraison_estimée": "2026-06-27"},
}

def get_order_status(order_id: str) -> dict:
    return ORDERS.get(order_id, {"erreur": "commande introuvable"})

Et on exécute ce que l'IA a demandé :

tool_call = msg.tool_calls[0]
args = json.loads(tool_call.function.arguments)   # '{"order_id":"4521"}' -> dict
result = get_order_status(**args)                 # {'statut': 'expédiée', ...}

Étape 5 — Renvoyer le résultat à l'IA pour la réponse finale

L'IA doit maintenant transformer ce dict en phrase. On lui renvoie deux messages : sa propre demande d'appel (pour garder le fil), puis le résultat, relié par tool_call_id :

messages.append(msg)  # la demande d'appel de l'IA
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,            # relie le résultat à la bonne demande
    "content": json.dumps(result, ensure_ascii=False),
})

final = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
)
print(final.choices[0].message.content)

Résultat :

Votre commande 4521 a bien été expédiée. La livraison est estimée au 25 juin 2026.

Tu viens de boucler les quatre flèches du schéma de départ.

Étape 6 — Le script complet et réutilisable

En pratique, on ne sait pas à l'avance combien d'appels l'IA voudra faire (parfois plusieurs d'affilée). On enveloppe donc tout dans une boucle, avec un garde-fou. Voici le script final, prêt à copier :

import json, os
from openai import OpenAI

client = OpenAI()

# --- 1. Tes vraies fonctions ---
ORDERS = {
    "4521": {"statut": "expédiée", "livraison_estimée": "2026-06-25"},
    "4522": {"statut": "en préparation", "livraison_estimée": "2026-06-27"},
}
def get_order_status(order_id: str) -> dict:
    return ORDERS.get(order_id, {"erreur": "commande introuvable"})

# --- 2. Le registre : nom vu par l'IA -> fonction réelle ---
dispatch = {"get_order_status": get_order_status}

# --- 3. La description vue par l'IA ---
tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Récupère le statut et la date de livraison d'une commande à partir de son numéro.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "Le numéro de commande, ex. '4521'."}
            },
            "required": ["order_id"],
        },
    },
}]

def run(question: str) -> str:
    messages = [
        {"role": "system", "content": "Tu es l'assistant SAV. Utilise les outils pour répondre précisément."},
        {"role": "user", "content": question},
    ]
    for _ in range(5):  # garde-fou : 5 tours maximum
        resp = client.chat.completions.create(
            model="gpt-4o-mini", messages=messages, tools=tools,
        )
        msg = resp.choices[0].message
        if not msg.tool_calls:
            return msg.content            # plus d'outil à appeler : c'est la réponse finale
        messages.append(msg)              # on garde la demande dans l'historique
        for tc in msg.tool_calls:         # l'IA peut demander plusieurs appels d'un coup
            args = json.loads(tc.function.arguments)
            result = dispatch[tc.function.name](**args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": json.dumps(result, ensure_ascii=False),
            })
    return "Trop d'étapes, j'arrête."

if __name__ == "__main__":
    print(run("Où en est ma commande 4521 ?"))
    print(run("Et la 9999 ?"))

Teste les deux cas : une commande qui existe, une qui n'existe pas. L'IA s'adapte au résultat (erreur: commande introuvable) et le formule poliment.

Ajouter un deuxième outil

Le pattern passe à l'échelle sans effort. Tu écris la fonction, tu l'ajoutes au dispatch et à tools :

def cancel_order(order_id: str) -> dict:
    if order_id in ORDERS:
        ORDERS[order_id]["statut"] = "annulée"
        return {"ok": True, "order_id": order_id}
    return {"erreur": "commande introuvable"}

dispatch["cancel_order"] = cancel_order
# + ajoute le bloc correspondant dans `tools`

L'IA choisira seule le bon outil selon la question (« annule la 4521 » → cancel_order). C'est ce mécanisme, mis en boucle, qui fait un agent (sujet des tutos avancés).

Sécurité : la partie non négociable

Comme c'est l'IA qui choisit les arguments, ne lui donne jamais un accès aveugle à des actions sensibles.

  • Valide les arguments avant d'exécuter. Un order_id doit ressembler à un numéro, pas à "; DROP TABLE…. Ne fais jamais confiance au contenu tel quel.
  • Moindre privilège. Un outil de lecture ne doit pas pouvoir écrire. Limite chaque fonction au strict nécessaire.
  • Confirme l'irréversible. Pour une annulation, un paiement, une suppression : mets une barrière que le modèle ne contrôle pas.
def cancel_order(order_id: str, confirm: bool = False) -> dict:
    if not confirm:
        return {"action": "confirmation_requise",
                "message": f"Confirmer l'annulation de {order_id} ?"}
    # ... annulation réelle seulement si confirm=True

Erreurs fréquentes (et comment les régler)

  • L'IA invente un argument → ta description est trop floue, ou il manque une contrainte. Précise, et pour un choix fermé utilise "enum": ["urgent", "normal"] dans le schéma.
  • json.loads plante → un petit modèle a renvoyé du JSON malformé. Entoure d'un try/except et redemande, ou choisis un modèle plus fiable pour cette tâche.
  • Boucle qui ne s'arrête pas → garde toujours la limite d'itérations (range(5) ici).
  • L'outil n'est jamais appelé → améliore la description, ou force avec tool_choice="required".
  • Tu utilises Claude (API Anthropic) ? Le principe est identique mais le format diffère : les outils se déclarent dans le paramètre tools avec input_schema, et la réponse contient des blocs tool_use / tool_result. La logique des quatre temps, elle, ne change pas.

L'adapter à tes besoins

Pour brancher ton propre cas, trois choses à changer seulement :

  1. Écris ta fonction (get_stock, creer_facture, chercher_client…) — du Python normal qui retourne un dict.
  2. Ajoute-la au dispatch et décris-la dans tools (soigne la description).
  3. Garde le reste tel quel : la boucle run() fonctionne quel que soit le nombre d'outils.

À retenir

Le function calling laisse l'IA demander l'exécution d'une fonction, mais c'est toujours ton code qui agit. Le cycle tient en quatre temps : tu décris l'outil, l'IA renvoie un tool_call (nom + arguments en chaîne JSON), tu exécutes, tu renvoies le résultat via un message tool relié par tool_call_id. Enveloppe le tout dans une boucle avec garde-fou, soigne tes descriptions, valide les arguments et confirme l'irréversible. Tu tiens là la brique de base de tous les agents.

Réagir :
Partager —XLinkedIn