📍 Guide principal : Développer avec l’API Google Gemini — pour situer le function calling dans l’ensemble des capacités.
Un modèle de langage, seul, ne sait que produire du texte. Il ne connaît pas votre stock, ne peut pas créer un devis, n’a aucun accès à votre base de données. Le function calling change cela : vous décrivez vos fonctions au modèle, et lui décide, quand c’est pertinent, d’en appeler une avec les bons arguments. Votre code exécute la fonction, renvoie le résultat, et le modèle l’intègre dans une réponse en langage naturel. C’est le pas qui transforme un assistant bavard en assistant qui agit.
Ce tutoriel construit un assistant d’inventaire qui répond à des questions comme « reste-t-il des souris en stock ? » en consultant réellement vos données. On verra les deux approches : l’automatique, qui fait tout le va-et-vient pour vous, et la manuelle, qui vous rend le contrôle pour la production. Il prolonge directement le premier tutoriel Python, dont il réutilise le client et l’environnement.
Ce que vous allez apprendre
- Comprendre le cycle complet du function calling : décision, appel, exécution, réponse finale.
- Exposer une fonction Python au modèle via l’appel automatique de fonctions.
- Reprendre le contrôle avec la déclaration manuelle et la boucle d’exécution.
- Gérer plusieurs outils et forcer le modèle à en utiliser un.
- Sécuriser l’exécution des fonctions choisies par le modèle.
Ce que vous allez construire
Un assistant d’inventaire en console. Vous lui posez une question en français ; il identifie le produit concerné, appelle votre fonction de consultation de stock, et formule une réponse claire à partir du chiffre réel. La même mécanique sert ensuite pour n’importe quelle action : créer une commande, envoyer un message, interroger une API externe.
Prérequis
- L’environnement du premier tutoriel :
google-genaiinstallé, clé dans.env. - Savoir écrire une fonction Python avec des arguments typés.
- ⏱️ Temps estimé : environ 30 minutes.
Étape 1 — Comprendre le cycle avant de coder
Le function calling se déroule en quatre temps, et bien les distinguer évite la confusion. Un : vous envoyez la question de l’utilisateur au modèle, accompagnée de la description de vos fonctions. Deux : le modèle, plutôt que de répondre directement, renvoie un appel de fonction — le nom d’une de vos fonctions et les arguments qu’il a déduits de la question. Trois : votre code exécute cette fonction et obtient un résultat. Quatre : vous renvoyez ce résultat au modèle, qui rédige enfin la réponse en langage naturel.
Le point essentiel : le modèle n’exécute jamais votre code lui-même. Il se contente de demander un appel ; c’est toujours votre programme qui décide d’exécuter, et comment. Cette séparation est ce qui rend le function calling sûr quand on le manipule correctement.
Étape 2 — Préparer la fonction métier
Commençons par la donnée. Notre « base de stock » sera un simple dictionnaire pour le tutoriel, mais en conditions réelles cette fonction interrogerait une base de données. Ce qui compte, c’est sa signature : un nom parlant, des arguments typés, une docstring claire — car ces éléments servent à décrire la fonction au modèle.
def trouver_stock(produit: str) -> dict:
"""Renvoie la quantite en stock pour un produit donne.
Args:
produit: le nom du produit recherche, par exemple "clavier".
"""
inventaire = {"clavier": 12, "souris": 0, "ecran": 5, "cable hdmi": 30}
quantite = inventaire.get(produit.lower(), 0)
return {"produit": produit, "quantite": quantite}
La docstring n’est pas décorative : dans l’approche automatique, le SDK la lit pour expliquer au modèle à quoi sert la fonction et ce qu’attend chaque argument. Une fonction bien documentée est une fonction que le modèle saura appeler à bon escient. Notez le type de retour dict : le résultat renvoyé au modèle doit être sérialisable, un dictionnaire est le format naturel.
✅ Point d’étape — Testez la fonction seule :
trouver_stock("souris")doit renvoyer{'produit': 'souris', 'quantite': 0}.
Étape 3 — L’approche automatique
La voie la plus rapide consiste à passer directement la fonction Python au modèle. Le SDK se charge alors de tout le cycle : il détecte l’appel, exécute votre fonction, renvoie le résultat, et vous ne récupérez que la réponse finale.
from dotenv import load_dotenv
from google import genai
from google.genai import types
load_dotenv()
client = genai.Client()
config = types.GenerateContentConfig(tools=[trouver_stock])
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Reste-t-il des souris en stock ?",
config=config,
)
print(response.text)
On déclare l’outil en passant la fonction elle-même dans tools=[trouver_stock], et c’est tout. Sous le capot, le modèle a demandé un appel à trouver_stock avec produit="souris", le SDK l’a exécuté, a obtenu quantite: 0, et le modèle a rédigé une réponse du type « Non, il n’y a plus de souris en stock actuellement. » Vous n’avez géré aucune des étapes intermédiaires.
Cette approche est parfaite pour démarrer et pour les scripts. Sa limite : vous ne voyez pas ce qui se passe entre les deux, ce qui complique la journalisation, la validation des arguments ou la gestion fine des erreurs. Pour une application en production, on préfère souvent reprendre la main.
✅ Point d’étape — La réponse mentionne bien l’absence de stock pour les souris. Si le modèle invente un chiffre, vérifiez que
tools=[trouver_stock]est bien dans la configuration.
Étape 4 — L’approche manuelle, pour le contrôle
En mode manuel, on décrit la fonction par un schéma explicite, et on gère soi-même la boucle. C’est plus verbeux, mais on voit et on contrôle chaque étape. D’abord, la déclaration : un dictionnaire qui décrit le nom, le but et les paramètres de la fonction.
declaration = {
"name": "trouver_stock",
"description": "Renvoie la quantite en stock d'un produit donne.",
"parameters": {
"type": "object",
"properties": {
"produit": {
"type": "string",
"description": "Nom du produit, par exemple 'clavier' ou 'souris'.",
},
},
"required": ["produit"],
},
}
outil = types.Tool(function_declarations=[declaration])
config = types.GenerateContentConfig(tools=[outil])
Ce schéma suit la structure JSON Schema : un objet avec des propriétés typées, et une liste des champs obligatoires. C’est cette description, et non le code de la fonction, que le modèle reçoit. On l’emballe dans un types.Tool, lui-même placé dans la configuration.
Vient ensuite le premier appel. Cette fois, on ne récupère pas un texte mais un appel de fonction, exposé par response.function_calls.
contents = ["Combien de cables hdmi reste-t-il ?"]
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=contents,
config=config,
)
appel = response.function_calls[0]
print("Fonction demandee :", appel.name)
print("Arguments :", dict(appel.args))
À l’exécution, vous voyez le modèle réclamer trouver_stock avec {'produit': 'cable hdmi'}. Il a compris la question, identifié la bonne fonction et extrait l’argument — mais il n’a rien exécuté. La balle est dans votre camp.
✅ Point d’étape —
appel.namevaut"trouver_stock"etappel.argscontient le produit. Siresponse.function_callsest vide, le modèle a jugé qu’aucune fonction n’était nécessaire — reformulez la question pour qu’elle appelle clairement le stock.
Étape 5 — Exécuter et renvoyer le résultat
On exécute maintenant la vraie fonction avec les arguments fournis, puis on renvoie son résultat au modèle pour qu’il rédige la réponse finale. Le résultat se transmet sous la forme d’une part de réponse de fonction, dans un message de rôle tool.
resultat = trouver_stock(**appel.args)
partie_reponse = types.Part.from_function_response(
name=appel.name,
response=resultat,
)
# On rejoue l'historique : question, tour du modele, puis resultat de la fonction
contents.append(response.candidates[0].content)
contents.append(types.Content(role="tool", parts=[partie_reponse]))
reponse_finale = client.models.generate_content(
model="gemini-2.5-flash",
contents=contents,
config=config,
)
print(reponse_finale.text)
Décortiquons. trouver_stock(**appel.args) exécute la fonction en dépliant les arguments fournis par le modèle. types.Part.from_function_response emballe le résultat sous une forme que le modèle comprend. On reconstitue ensuite l’historique complet : la question initiale, le tour du modèle qui contenait l’appel (response.candidates[0].content), puis notre message de rôle tool portant le résultat. Le second appel produit enfin la phrase attendue : « Il reste 30 câbles HDMI en stock. » Le modèle a bien utilisé votre chiffre, pas une invention.
✅ Point d’étape — La réponse finale contient le bon nombre (30). Si le modèle redemande une fonction au lieu de répondre, c’est que l’historique renvoyé est incomplet : vérifiez que les deux
appendsont bien présents.
Étape 6 — Plusieurs outils et appel forcé
Une application réelle expose plusieurs fonctions. Il suffit de toutes les déclarer ; le modèle choisit la bonne selon la question. Ajoutons une fonction de prix à côté du stock.
def trouver_prix(produit: str) -> dict:
"""Renvoie le prix unitaire d'un produit, en francs CFA."""
tarifs = {"clavier": 9000, "souris": 4500, "ecran": 65000}
return {"produit": produit, "prix_fcfa": tarifs.get(produit.lower())}
config = types.GenerateContentConfig(tools=[trouver_stock, trouver_prix])
Avec cette configuration, « combien coûte un écran ? » déclenche trouver_prix, tandis que « en reste-t-il ? » déclenche trouver_stock. Le modèle aiguille selon l’intention.
Parfois, on veut forcer le modèle à appeler une fonction plutôt que de répondre librement — utile quand une réponse non outillée n’a pas de sens. On passe alors un tool_config en mode ANY.
config = types.GenerateContentConfig(
tools=[trouver_stock, trouver_prix],
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(mode="ANY")
),
)
En mode ANY, le modèle doit obligatoirement choisir une fonction. Le mode par défaut, AUTO, le laisse décider entre appeler une fonction ou répondre directement. Réservez ANY aux cas où l’appel est certain ; ailleurs, AUTO est plus naturel.
Un détail qui surprend au début : response.function_calls est une liste, pas un appel unique. Sur une question comme « le clavier est-il en stock, et combien coûte-t-il ? », le modèle peut demander d’un coup trouver_stock et trouver_prix. Vous exécutez alors chaque appel de la liste, vous renvoyez toutes les réponses de fonction dans le même tour, et le modèle compose une réponse unique qui mêle les deux informations. Itérer sur la liste plutôt que de prendre systématiquement function_calls[0] rend votre code prêt pour ces requêtes combinées, qui sont fréquentes dès qu’un utilisateur pose une question à plusieurs facettes.
Étape 7 — Sécuriser l’exécution
Le modèle choisit la fonction et les arguments, mais c’est votre code qui exécute. Cette frontière est aussi une responsabilité. Trois règles de prudence. D’abord, n’exposez que des fonctions sans danger : consulter, calculer, lister — oui ; supprimer une table ou virer de l’argent sans confirmation — non. Ensuite, validez toujours les arguments reçus avant de les utiliser, car le modèle peut produire une valeur inattendue. Enfin, pour toute action irréversible, intercalez une confirmation humaine : le modèle prépare l’action, un humain l’approuve.
Cette discipline rejoint celle des agents IA en général. Si vous concevez des assistants qui agissent à plus grande échelle, l’article sur les agents IA pour PME aborde ces garde-fous au niveau architecture.
✅ Point d’étape — Vous savez désormais distinguer une fonction « lecture » sûre d’une fonction « écriture » à protéger.
🐞 Pièges fréquents
| Symptôme | Cause probable | Correctif |
|---|---|---|
response.function_calls est vide |
Le modèle a jugé qu’aucune fonction n’était nécessaire, ou la description est trop vague. | Clarifier la description de la fonction et des paramètres ; reformuler la question. |
| Le modèle invente un chiffre au lieu d’appeler | Outil non passé dans la configuration, ou docstring absente. | Vérifier tools=[...] ; documenter la fonction. |
| Boucle infinie d’appels de fonction | Le résultat n’est pas renvoyé correctement au modèle. | Renvoyer le tour du modèle puis la Part.from_function_response en rôle tool. |
TypeError à l’exécution de la fonction |
Arguments produits par le modèle non conformes au type attendu. | Valider et convertir les arguments avant l’appel réel. |
| Mauvaise fonction appelée | Deux descriptions trop proches. | Différencier nettement les descriptions et les noms. |
✅ Récapitulatif
Vous avez relié Gemini à votre propre code. Vous maîtrisez le cycle en quatre temps, vous savez l’exécuter aussi bien en automatique qu’en manuel, gérer plusieurs outils, forcer un appel quand il le faut, et surtout encadrer ce que le modèle a le droit de déclencher. C’est la brique qui ouvre la porte des assistants qui agissent : à partir de là, brancher une vraie base de données ou une API tierce n’est qu’une question de remplacer le contenu de la fonction métier.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
tools=[ma_fonction] |
Appel automatique : le SDK gère tout le cycle. |
types.Tool(function_declarations=[decl]) |
Déclaration manuelle d’un outil. |
response.function_calls[0] |
Récupérer l’appel demandé (.name, .args). |
types.Part.from_function_response(name, response) |
Emballer le résultat à renvoyer au modèle. |
types.Content(role="tool", parts=[...]) |
Message portant le résultat de la fonction. |
FunctionCallingConfig(mode="ANY") |
Forcer le modèle à appeler une fonction. |
💪 À vous de jouer
Ajoutez une fonction passer_commande(produit, quantite) qui, pour l’instant, se contente d’afficher la commande et renvoie un numéro fictif. Faites en sorte que l’assistant l’appelle sur une phrase comme « commande-moi trois claviers ». Réfléchissez : cette fonction écrit-elle des données ? Quelle protection lui ajouteriez-vous ?
Voir une solution
def passer_commande(produit: str, quantite: int) -> dict:
"""Enregistre une commande et renvoie un numero de suivi."""
# En production : ecrire en base, apres confirmation humaine.
numero = "CMD-1042"
print(f"[A CONFIRMER] {quantite} x {produit} -- {numero}")
return {"numero": numero, "produit": produit, "quantite": quantite, "statut": "en attente de confirmation"}
Comme cette fonction écrit (elle crée une commande), on ne l’exécute pas aveuglément : le statut « en attente de confirmation » matérialise l’étape humaine décrite à l’étape 7. En conditions réelles, l’écriture en base n’aurait lieu qu’après validation.
Tutoriels suivants
- Sorties structurées JSON — fiabiliser le format des données échangées avec le modèle.
- Analyser images et PDF — donner au modèle des documents en entrée.
Pour aller plus loin
🔝 Retour au guide principal de l’API Gemini. La documentation officielle détaille les modes de FunctionCallingConfig et l’appel parallèle de plusieurs fonctions.
Questions fréquentes
Le modèle exécute-t-il mon code ? Non, jamais. Il demande un appel ; votre programme décide d’exécuter ou non. C’est ce qui rend le mécanisme sûr lorsqu’il est bien encadré.
Automatique ou manuel, lequel choisir ? L’automatique pour prototyper vite, le manuel dès que vous avez besoin de journaliser, valider les arguments ou gérer les erreurs finement — c’est-à-dire en production.
Peut-il appeler plusieurs fonctions d’affilée ? Oui. Le modèle peut enchaîner les appels jusqu’à disposer de toutes les informations ; en manuel, vous bouclez tant que response.function_calls n’est pas vide.
Quels types d’arguments sont supportés ? Les types JSON Schema usuels : chaînes, nombres, booléens, tableaux, objets, avec des valeurs imposées via enum si besoin.