Cours Python · Module 10: IA et modèles de langage

Function Calling - L'IA avec des outils

6 min de lecture
Dans cette leçon5

Un modèle de langage ne connaît ni la météo du jour au Serengeti, ni la population actuelle des lions - il sait seulement assembler des mots. Le function calling lui donne des outils : tes fonctions Python, vers lesquelles il peut se tourner quand la question dépasse ce qu'il sait. Malgré son nom, rien à voir avec un centre d'appels téléphoniques.

L'essentiel : le modèle n'exécute pas la fonction

Avant d'écrire la moindre ligne de code, comprends bien ceci - c'est la source de tous les malentendus autour du function calling. Le modèle ne lance jamais ta fonction lui-même. Il se contente de dire : « je veux appeler get_weather avec l'argument Serengeti ». L'exécution, elle, t'appartient. Le function calling n'est donc pas « appeler des fonctions dans du code Python » - ça, c'est ton travail à toi. C'est la capacité de l'IA à décider d'invoquer des fonctions externes. La conversation se déroule en tours :

  1. Définir les outils (descriptions de fonctions).
  2. Envoyer une requête avec les outils au modèle.
  3. Le modèle renvoie des tool_calls.
  4. Exécuter la fonction sélectionnée.
  5. Renvoyer le résultat au modèle.

Le modèle rédige alors, à partir de ce résultat, une réponse en langage humain. Nous allons parcourir ces tours un par un. Commençons par la façon de décrire un outil au modèle - car le modèle ne voit pas ton code, seulement sa description.

1from openai import OpenAI
2import json
3
4client = OpenAI()
5
6tools = [
7    {
8        "type": "function",
9        "function": {
10            "name": "get_weather",
11            "description": "Recupere la meteo pour un lieu du Safari",
12            "parameters": {
13                "type": "object",
14                "properties": {
15                    "location": {
16                        "type": "string",
17                        "description": "Nom du lieu, par exemple Serengeti"
18                    }
19                },
20                "required": ["location"]
21            }
22        }
23    }
24]

Remarque bien : il ne s'agit que de la description de l'outil, pas de son fonctionnement. Dans l'API OpenAI, les outils se définissent sous forme de liste de dictionnaires décrivant les fonctions et les paramètres - ni fichiers JSON à charger séparément, ni classes Python à instancier, et encore moins des modules npm. Les champs description sont ici les plus importants : c'est sur eux que le modèle s'appuie pour décider quand prendre l'outil et quoi mettre dans l'argument. Meilleure est la description, plus le choix du modèle est juste. La section required indique quels arguments sont obligatoires. Rien ne t'empêche de mettre plusieurs dictionnaires dans cette liste - un outil météo, un outil sur les animaux du Safari - et le modèle décidera lequel lui sert.

Deuxième tour : le modèle demande un appel

On envoie maintenant la question accompagnée de la liste d'outils. Lis attentivement ce que le modèle renvoie - ce n'est pas une réponse destinée à l'utilisateur.

1response = client.chat.completions.create(
2    model="gpt-5",
3    messages=[{"role": "user", "content": "Quelle est la meteo au Serengeti ?"}],
4    tools=tools,
5    tool_choice="auto"
6)
7
8message = response.choices[0].message
9if message.tool_calls:
10    for tool_call in message.tool_calls:
11        function_name = tool_call.function.name
12        arguments = json.loads(tool_call.function.arguments)
13        print(f"Le modele veut appeler : {function_name}({arguments})")

Au lieu d'une réponse toute faite, le modèle a renvoyé des tool_calls - la liste de ses demandes d'appel. Les arguments arrivent sous forme de texte JSON, d'où le json.loads qui les transforme en dictionnaire Python. La boucle for n'est pas un détail de style : le modèle peut réclamer plusieurs outils d'un seul coup, et tu dois tous les traiter. À ce stade, rien ne s'est encore produit - le modèle a seulement dit ce qu'il voulait. L'exécution, c'est notre étape suivante.

Troisième tour : c'est toi qui exécutes la fonction

Le modèle ne connaît le nom de la fonction que sous forme de chaîne de caractères. Il nous faut donc sa véritable implémentation, et un moyen de passer de ce nom au bon morceau de code.

1def get_weather(location: str) -> dict:
2    # Dans une vraie application - appel a une API meteo
3    return {"location": location, "temperature": 28, "conditions": "Ensoleille"}
4
5available_functions = {
6    "get_weather": get_weather
7}

Le dictionnaire available_functions est le pont entre le monde du modèle et ton code : le modèle fournit la chaîne "get_weather", et toi, à partir de cette chaîne, tu retrouves la vraie fonction. C'est toi qui décides de ce qui s'exécute réellement - le modèle n'a accès à rien de ce que tu ne lui donnes pas ici. Note que c'est l'endroit naturel pour un contrôle de sécurité : une fonction absente de ce dictionnaire ne sera jamais appelée, quoi que le modèle demande.

Tous les tours réunis

Voici maintenant la conversation complète : la question, la demande du modèle, l'exécution, le renvoi du résultat et la réponse finale. Suis du regard la liste messages, qui s'allonge d'un tour à l'autre.

1def chat_with_tools(user_message: str):
2    messages = [{"role": "user", "content": user_message}]
3
4    response = client.chat.completions.create(
5        model="gpt-5", messages=messages, tools=tools
6    )
7    message = response.choices[0].message
8
9    if message.tool_calls:
10        messages.append(message)  # garde la demande du modele
11
12        for tool_call in message.tool_calls:
13            name = tool_call.function.name
14            args = json.loads(tool_call.function.arguments)
15            result = available_functions[name](**args)  # execution de ton cote
16
17            messages.append({
18                "role": "tool",
19                "tool_call_id": tool_call.id,
20                "content": json.dumps(result)  # renvoie le resultat au modele
21            })
22
23        final = client.chat.completions.create(model="gpt-5", messages=messages)
24        return final.choices[0].message.content
25
26    return message.content

Ce qui compte ici, ce sont les deux appels à create : le premier, où le modèle réclame un outil, et le second - une fois le résultat ajouté - où le modèle rédige la réponse destinée à l'humain. Entre les deux, le geste clé tient en une ligne : messages.append(tool_response). Le rôle "tool" et le tool_call_id sont ta façon de dire au modèle « voici le résultat exact de la fonction que tu as demandée ». Sans ce second tour, l'utilisateur recevrait du JSON brut au lieu d'une phrase.

LangChain - la même idée, moins de code

Mener les tours à la main devient vite fastidieux. LangChain emballe toute cette boucle dans un agent : tu te contentes de marquer tes fonctions comme outils, et il mène la conversation à ta place. Tout part d'un import, from langchain.tools import tool, qui te donne le décorateur.

1from langchain.tools import tool
2from langchain.agents import create_tool_calling_agent, AgentExecutor
3from langchain_openai import ChatOpenAI
4from langchain_core.prompts import ChatPromptTemplate
5
6@tool
7def calculate_distance(from_loc: str, to_loc: str) -> str:
8    """Calcule la distance entre deux lieux du Safari."""
9    return f"Distance de {from_loc} a {to_loc} : 150 km"
10
11llm = ChatOpenAI(model="gpt-5")
12prompt = ChatPromptTemplate.from_messages([
13    ("system", "Tu es un assistant Safari serviable."),
14    ("human", "{input}"),
15    ("placeholder", "{agent_scratchpad}")
16])
17
18agent = create_tool_calling_agent(llm, [calculate_distance], prompt)
19executor = AgentExecutor(agent=agent, tools=[calculate_distance], verbose=True)
20executor.invoke({"input": "Quelle est la distance de Nairobi au Serengeti ?"})

Le décorateur @tool fait exactement ce que nous écrivions à la main plus haut : il construit la description destinée au modèle à partir de la docstring, et la liste des arguments à partir de la signature. AgentExecutor, lui, enchaîne pour toi tous les tours de l'exemple précédent, en boucle, jusqu'à ce que le modèle estime avoir toutes les informations.

Retiens une phrase de cette leçon : le function calling, ce n'est pas « l'IA exécute du code », c'est le modèle qui demande, et toi qui exécutes et renvoies le résultat. Tout le reste - OpenAI ou LangChain - n'est qu'une écriture plus confortable de la même conversation en tours.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Qu'est-ce que le Function Calling dans le contexte des LLM ?

  2. 2. Comment définit-on des outils dans l'API OpenAI ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Définis un outil et gère l'appel de fonction effectué par le modèle.

  • Tri vertical

    Range les étapes dans le bon ordre :

  • Tri horizontal

    Range l'import du décorateur tool depuis LangChain :

  • Clique dans l'ordre

    Clique sur les boutons pour ajouter une réponse d'outil :

Articles utiles