ITSkillsCenter
Intelligence Artificielle

Function calling avec Gemini : connecter le modèle à vos fonctions

13 min de lecture

📍 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-genai installé, 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’étapeappel.name vaut "trouver_stock" et appel.args contient le produit. Si response.function_calls est 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 append sont 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

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.

Partager
Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité