Leçon 2 — Comprendre le function calling

Appel automatique ou boucle manuelle

5 min de lecture

Appel automatique ou boucle manuelle

Deux modes, une même mécanique

Dans les deux cas, le cycle est celui de la leçon précédente. La seule question est : qui exécute la fonction entre deux appels au modèle ?

flowchart TB
  subgraph AUTO["Mode automatique (AFC)"]
    A1["chat.send_message(question)"] --> A2["Le SDK reçoit function_call"]
    A2 --> A3["Le SDK appelle votre fonction Python"]
    A3 --> A4["Le SDK renvoie le résultat au modèle"]
    A4 --> A5["Vous recevez le texte final"]
  end
  subgraph MANUEL["Boucle manuelle"]
    M1["generate_content(...)"] --> M2["Vous lisez response.function_calls"]
    M2 --> M3["Vous validez, confirmez, journalisez"]
    M3 --> M4["Vous exécutez et renvoyez function_response"]
    M4 --> M1
  end

AFC signifie Automatic Function Calling : c'est le nom du mode automatique dans le SDK.

Le mode automatique : 10 lignes

On passe directement des fonctions Python dans tools. Le SDK déduit les schémas (signature + docstring), exécute les fonctions et relance le modèle jusqu'à obtenir du texte.

files/auto.py (extrait)
client = genai.Client()
chat = client.chats.create(
    model=os.environ.get("MODEL", "gemini-3.5-flash"),
    config=types.GenerateContentConfig(
        tools=[chercher_employe, consulter_solde],  # le SDK déduit les schémas
        automatic_function_calling=types.AutomaticFunctionCallingConfig(
            maximum_remote_calls=5)))
reponse = chat.send_message("Combien de RTT reste-t-il à Karim Benali ?")
print(reponse.text)

maximum_remote_calls plafonne le nombre d'appels de fonctions que le SDK enchaîne : c'est votre ceinture de sécurité contre une boucle infinie.

La boucle manuelle : le contrôle total

On désactive l'AFC, on reçoit les function_calls et on décide. Voici le cœur de la boucle du lab :

files/assistant.py (extrait)
config = types.GenerateContentConfig(
    system_instruction=SYSTEME.format(utilisateur=utilisateur, aujourdhui=...),
    tools=[types.Tool(function_declarations=outils_rh.DECLARATIONS)],
    automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)
reponse = generer(client, historique, config)
historique.append(reponse.candidates[0].content)   # garde les thought signatures
appels = reponse.function_calls or []
if not appels:
    return reponse.text
parts = [types.Part(function_response=types.FunctionResponse(
             id=fc.id, name=fc.name, response=executer(fc, utilisateur, confirmer)))
         for fc in appels]
historique.append(types.Content(role="user", parts=parts))

Trois détails qui comptent :

  1. On ajoute le contenu complet renvoyé par le modèle (candidates[0].content) à l'historique, pas une copie reconstruite. Les modèles Gemini récents y glissent des thought signatures (signatures de raisonnement) qu'il faut renvoyer telles quelles pour que le modèle retrouve le fil de son raisonnement.
  2. On recopie fc.id dans la FunctionResponse : avec plusieurs appels simultanés, c'est ce qui associe chaque résultat à sa demande.
  3. Tout passe par executer(...) : c'est le point de contrôle où l'on valide, confirme et journalise (module 3).
CritèreAutomatique (AFC)Manuel
Code à écrireminimalune boucle de 30 lignes
Valider les arguments avant exécutiondans chaque fonctionune fois, au point de contrôle
Demander une confirmation humainedifficile (la fonction bloque le SDK)naturel
Journal d'audit centraliséà la main dans chaque fonctionune ligne dans executer
Identité de l'utilisateurvariable globale ou fermetureparamètre explicite
Bon usageprototype, outils en lecture seuletoute action qui écrit ou coûte

Votre outil crée une demande de congés et débite un solde. Quel mode choisir ?

Pourquoi recopier fc.id dans la FunctionResponse ?

Exercice : afficher les appels faits par l'AFC

En mode automatique, vous ne voyez que le texte final. Comment afficher la liste des fonctions réellement appelées par le SDK pendant chat.send_message ?

Voir la solution

L'historique de la conversation contient tous les tours, y compris les function_call intermédiaires :

for contenu in chat.get_history():
    for part in contenu.parts:
        if part.function_call:
            print("appel :", part.function_call.name, dict(part.function_call.args))

C'est ce que fait la fin de auto.py. Avec client.models.generate_content, on lirait reponse.automatic_function_calling_history à la place.

Résultat obtenu

python auto.py exécuté le 2026-10-15 avec gemini-3.5-flash, API RH locale lancée :

  appel : chercher_employe {'nom': 'Karim Benali'}
  appel : consulter_solde {'employe_id': 'E002'}
Il reste **9 jours de RTT** à Karim Benali (ainsi que 7 jours de congés payés).

Le SDK a enchaîné seul deux fonctions. Pratique, mais rien ne s'est intercalé entre la décision du modèle et l'appel HTTP : c'est exactement ce qu'on ne veut pas pour une écriture.

Je sais faire

  • Activer le mode automatique en passant des fonctions Python dans tools
  • Désactiver l'AFC et lire response.function_calls
  • Renvoyer un résultat avec FunctionResponse(id=..., name=..., response=...)
  • Expliquer pourquoi on garde candidates[0].content tel quel dans l'historique
Suivant