La boucle agent-outils

Première leçon du cours Agents IA et automatisation, en accès libre : environ 20 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Expliquer ce qui distingue un agent d'un simple appel de modèle
  • Écrire une boucle qui alterne décision du modèle et exécution d'outils
  • Borner la boucle et traiter les appels d'outils invalides

Boucle agent-outilsOutils et schémasConditions d'arrêt

D’un appel de modèle à un agent

Un appel de modèle classique est une fonction : un texte entre, un texte sort. Le modèle ne connaît que ce qui figure dans sa requête et dans ses données d’entraînement. Il ne peut ni consulter votre base clients, ni calculer de façon fiable, ni envoyer un courriel.

Un agent ajoute une boucle autour du modèle. À chaque tour, le modèle reçoit l’historique de la conversation et choisit entre deux actions :

  1. appeler un outil (une fonction de votre programme) avec des arguments ;
  2. donner la réponse finale.

Quand le modèle demande un outil, votre programme l’exécute, ajoute le résultat à l’historique, puis rappelle le modèle. La boucle s’arrête quand le modèle répond, ou quand une limite est atteinte.

Point essentiel : le modèle n’exécute rien lui-même. Il produit une demande structurée, par exemple {"tool": "meteo", "args": {"ville": "Lyon"}}. C’est votre code qui décide de l’exécuter ou non. Cette séparation est la base de tous les garde-fous vus plus loin.

Décrire les outils

Les API de modèles qui gèrent l’appel d’outils (tool calling ou function calling) attendent pour chaque outil un nom, une description en langage naturel et un schéma des arguments, souvent au format JSON Schema. La description compte autant que le code : c’est elle qui permet au modèle de choisir le bon outil.

TOOLS_SPEC = [
    {"name": "meteo",
     "description": "Température actuelle en °C pour une ville française.",
     "parameters": {"type": "object",
                    "properties": {"ville": {"type": "string"}},
                    "required": ["ville"]}},
]

Le modèle peut se tromper : inventer un outil qui n’existe pas, oublier un argument, passer un texte à la place d’un nombre. Votre code doit donc valider chaque appel avant de l’exécuter.

La boucle, pas à pas

def run_agent(question, model, tools, max_steps=5):
    messages = [{"role": "user", "content": question}]
    for step in range(max_steps):
        decision = model(messages)              # le modèle propose
        if "answer" in decision:
            return decision["answer"]           # réponse finale
        name, args = decision["tool"], decision.get("args", {})
        if name not in tools:
            result = f"ERREUR: outil inconnu {name}"
        else:
            try:
                result = tools[name](**args)    # votre code exécute
            except Exception as e:
                result = f"ERREUR: {e}"
        messages.append({"role": "tool", "name": name, "content": str(result)})
    return None                                 # limite atteinte

Trois remarques :

  • l’historique (messages) est la seule mémoire du modèle d’un tour à l’autre : tout ce qu’il doit savoir doit y figurer ;
  • les erreurs sont transformées en messages lisibles : le modèle peut alors corriger son appel au tour suivant ;
  • la boucle est bornée par max_steps. Sans limite, un modèle qui rappelle sans fin le même outil consommerait du temps et de l’argent indéfiniment.

Simuler le modèle pour tester

Pour mettre au point la boucle, inutile d’appeler un vrai modèle : une fonction locale qui renvoie des décisions prévisibles suffit. Elle rend les tests reproductibles, gratuits et exécutables sans réseau.

def fake_model(messages):
    resultats = [m for m in messages if m["role"] == "tool"]
    if not resultats:
        return {"tool": "meteo", "args": {"ville": "Lyon"}}
    return {"answer": f"Il fait {resultats[-1]['content']} °C."}

Vous testez ainsi le chemin nominal, mais aussi les cas difficiles : un modèle qui boucle, qui invente un outil ou qui passe de mauvais arguments. Le vrai modèle ne sera branché qu’une fois ces comportements maîtrisés.

Quand ne pas utiliser d’agent

Un agent est utile quand l’enchaînement des étapes dépend des résultats intermédiaires. Si les étapes sont toujours les mêmes (extraire, classer, enregistrer), un workflow codé en dur est plus simple, moins cher et plus facile à tester. Commencez par la solution la plus déterministe qui répond au besoin, et réservez l’autonomie aux parties qui en ont réellement besoin.

À retenir

  • Agent = boucle « le modèle propose, le code exécute ».
  • Chaque appel d’outil est validé avant exécution ; les erreurs reviennent au modèle sous forme de texte.
  • La boucle est toujours bornée.
  • Un modèle simulé permet de tester la boucle de façon déterministe.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 2Python dans le navigateur2 indices

    Écrire la boucle d'agent

    Complétez run_agent(question, model, tools, max_steps=5).

    • L’historique commence par {"role": "user", "content": question}.
    • À chaque tour, appelez model(messages). S’il renvoie une clé answer, retournez {"answer": ..., "steps": n} où n est le nombre d’outils exécutés.
    • Sinon, exécutez l’outil decision["tool"] avec decision["args"] et ajoutez {"role": "tool", "name": ..., "content": str(resultat)} à l’historique.
    • Outil inconnu : le contenu ajouté est "ERREUR: outil inconnu <nom>" (cela compte comme une étape).
    • Après max_steps appels du modèle sans réponse, retournez {"answer": None, "steps": max_steps, "error": "max_steps"}.
    def meteo(ville):
        donnees = {"Lyon": 18, "Lille": 12}
        return donnees.get(ville, "inconnue")
    
    
    def convertir_f(celsius):
        return round(celsius * 9 / 5 + 32, 1)
    
    
    TOOLS = {"meteo": meteo, "convertir_f": convertir_f}
    
    
    def fake_model(messages):
        """Modèle simulé : il décide d'après les résultats d'outils déjà présents."""
        resultats = [m for m in messages if m["role"] == "tool"]
        if not resultats:
            return {"tool": "meteo", "args": {"ville": "Lyon"}}
        if len(resultats) == 1:
            return {"tool": "convertir_f", "args": {"celsius": int(resultats[0]["content"])}}
        return {"answer": f"Il fait {resultats[-1]['content']} °F à Lyon."}
    
    
    def run_agent(question, model, tools, max_steps=5):
        messages = [{"role": "user", "content": question}]
        # à compléter
        ...
  2. CodeDifficulté 2Python dans le navigateur2 indices

    Valider un appel d'outil

    Écrivez validate_call(call, schemas) qui renvoie la liste des erreurs d’un appel {"tool": ..., "args": {...}} (liste vide si l’appel est valide). schemas associe à chaque outil un dictionnaire argument -> type attendu (un type ou un tuple de types).

    • Outil absent de schemas : renvoyer uniquement ["outil inconnu: <nom>"].
    • Pour chaque argument attendu absent : "argument manquant: <arg>".
    • Pour chaque argument non prévu : "argument inattendu: <arg>".
    • Pour chaque argument présent du mauvais type : "type invalide: <arg>".

    L’ordre des erreurs n’a pas d’importance.

    SCHEMAS = {
        "meteo": {"ville": str},
        "convertir_f": {"celsius": (int, float)},
    }
    
    
    def validate_call(call, schemas):
        errors = []
        # à compléter
        return errors
  3. QCMDifficulté 11 indice

    Qui exécute l'outil ?

    Dans une boucle agent-outils, que se passe-t-il quand le modèle « appelle » l’outil rembourser ?

    • Le modèle exécute lui-même le remboursement sur le serveur du fournisseur
    • Le modèle renvoie une demande structurée ; votre programme décide de l'exécuter après vérification
    • L'API du fournisseur exécute la fonction Python à distance

Quiz de fin de leçon

  1. Pourquoi borner la boucle d’un agent ?

    • Pour accélérer le modèle
    • Pour éviter les boucles infinies et maîtriser coût et durée
    • Parce que les API refusent plus de 5 appels
  2. Que faire quand un outil lève une exception ?

    • Arrêter l'agent avec une trace d'erreur
    • Renvoyer au modèle un message d'erreur lisible comme résultat
    • Ignorer l'appel et recommencer la même requête
  3. Quel est l’intérêt d’un modèle simulé (fonction locale) ?

    • Il est plus intelligent que le vrai modèle
    • Il rend les tests de la boucle déterministes, gratuits et sans réseau
    • Il remplace définitivement le modèle en production

La suite du cours

  1. Planification et mémoire20 min
  2. Garde-fous et validation humaine20 min
  3. Orchestration de tâches20 min
  4. Échecs et reprise20 min

Agents IA et automatisation · Avancé · 1 h 40