Au chapitre précédent, nous avons vu qu'un agent IA se compose de 4 éléments : le cerveau (LLM), les outils, la mémoire et la boucle (le contrôle). Dans ce chapitre, on passe enfin à la pratique. L'objectif est simple : construire un « agent qui fonctionne » en assemblant les 4 éléments dans leur configuration la plus minimale, puis le faire réellement tourner et observer son comportement. Pas encore de fonctionnalités spectaculaires. On veut d'abord savoir faire tourner de sa propre main le cœur battant de l'agent : « faire réfléchir le LLM, appeler un outil, lui renvoyer le résultat, le faire réfléchir à nouveau, et s'arrêter une fois terminé ».
« Assembler soi-même un agent minimal et le faire tourner »
Qu'est-ce qu'un agent minimal ?
Un « agent minimal », c'est le montage des 4 éléments du chapitre précédent dans leur forme la plus petite chacun. Pas besoin de mémoire luxueuse, ni de multiples outils, ni de plusieurs agents. Comme dans le tableau ci-dessous, on part d'une configuration réduite à l'os. Une fois cela compris, il ne reste plus qu'à étoffer chaque élément.
Un seul appel à Claude. Dans le prompt, une ligne de rôle : « tu es un assistant capable d'utiliser des outils ».
Un seul outil. Recherche ou calcul, peu importe. Commencez par une fonction simple au fonctionnement sûr.
Un seul tableau d'historique de conversation suffit. On y empile les échanges dans l'ordre. La persistance, on n'y pense pas encore.
Un seul while. « S'il y a une demande d'outil, l'exécuter ; sinon, c'est terminé. » On fixe seulement un nombre maximal de tours.
💡 La seule différence avec « un appel d'API unique », c'est la boucle. Si vous interrogez le LLM une fois et récupérez sa réponse, ce n'est qu'un simple usage d'API. Dès qu'on ajoute la répétition « exécuter un outil, renvoyer le résultat, refaire réfléchir », cela devient un agent. Ce qui s'ajoute, c'est en somme cette unique boucle. C'est justement pourquoi bien comprendre la boucle d'abord est le chemin le plus court.
L'intérieur de la boucle d'agent
Le cœur battant de l'agent, c'est une seule boucle. On reproduit en programme exactement ce que fait un humain : « chercher, réfléchir, agir, observer le résultat, et recommencer si ce n'est pas fini ». Saisissons d'abord le déroulé d'un tour avec un schéma.
On passe au LLM l'historique de conversation et la liste des outils. Le LLM décide s'il « répond » ou s'il « utilise un outil ».
Si le LLM répond « je veux utiliser cet outil avec ces arguments », c'est nous qui exécutons réellement la fonction.
On ajoute le résultat d'exécution (la valeur de retour) à l'historique et on le repasse au LLM. On revient alors à l'ÉTAPE 1.
Quand le LLM renvoie une réponse finale sans utiliser d'outil, on considère l'objectif atteint et on sort de la boucle.
Le point clé : on tourne en rond entre les ÉTAPES 1 à 3, et on ne sort qu'à l'ÉTAPE 4. Le LLM ne termine pas forcément en un tour. Comme « d'abord chercher, puis, au vu du résultat, chercher à nouveau », il fait plusieurs tours pour se rapprocher du but. En pseudo-code, c'est étonnamment court.
# Boucle d'agent minimale (pseudo-code) messages = [ user("Cherche la température moyenne du mois dernier et résume-la") ] while True: reply = llm.think(messages, tools=[search_tool]) # ÉTAPE 1 : réfléchir if reply.wants_tool: # demande d'outil ? result = run_tool(reply.tool_name, reply.args) # ÉTAPE 2 : appeler messages.append(assistant(reply)) messages.append(tool_result(result)) # ÉTAPE 3 : renvoyer else: return reply.text # ÉTAPE 4 : s'arrêter une fois terminé
⚠️ Toujours mettre un nombre maximal de tours. Le pseudo-code ci-dessus utilise while True à titre d'explication, mais dans la vraie vie on met impérativement un garde-fou de « N tours maximum ». Une boucle infinie où le LLM appelle des outils sans fin est un accident qui arrive vraiment souvent. Nous y reviendrons dans les points de blocage.
Donner un seul outil
Ensuite, on dote le LLM d'un seul outil. L'essentiel à retenir ici : le LLM ne peut pas exécuter de code lui-même. Tout ce qu'il peut faire, c'est demander avec des mots : « je veux utiliser cet outil, avec ces arguments ». Faire réellement tourner la fonction et renvoyer le résultat, c'est toujours notre travail (celui de votre programme).
Pour cela, on passe d'abord au LLM une définition d'outil. Une définition d'outil, c'est la notice qui décrit « ce que fait cet outil et quels arguments il prend ». Le LLM la lit pour décider quand et comment l'utiliser.
Par ex. search_weather « renvoie la température moyenne à partir d'un lieu et d'un mois ». La description sert de matière au jugement du LLM : rédigez-la avec soin.
Quels arguments, dans quels types, sont précisés par un schéma (une définition au format JSON). Par ex. city (chaîne) et month (nombre).
Le corps de code qui tourne réellement. Il reçoit les arguments renvoyés par le LLM et renvoie un résultat. Ce n'est qu'une fonction tout ce qu'il y a d'ordinaire.
De ce trio, l'astuce est de passer ① et ② au LLM, et de garder ③ chez soi. Quand le LLM demande « je veux utiliser search_weather(city=\"Paris\", month=6) », on récupère de notre côté ce nom et ces arguments, on appelle la fonction ③, et on renvoie la valeur de retour à la conversation en tant que résultat d'outil. Voyons cela en pseudo-code.
# ① et ② … la définition d'outil passée au LLM search_tool = { "name": "search_weather", "description": "renvoie la température moyenne (°C) pour une ville et un mois", "input_schema": { "city": "string", "month": "number", }, } # ③ … la fonction concrète gardée chez soi. Une simple fonction def run_tool(name, args): if name == "search_weather": return weather_db.lookup(args["city"], args["month"]) return "unknown tool"
📊 La description fait partie du prompt. La description de l'outil est le seul indice dont dispose le LLM pour décider « quand l'utiliser ». Si elle est vague, il ne l'utilisera pas, ou l'utilisera à contretemps. L'astuce : l'écrire comme si vous expliquiez à un humain « ce qu'il renvoie et quand il faut l'utiliser ». La conception d'outils sera approfondie au chapitre suivant avec MCP.
Vue d'implémentation — boucle à la main et SDK
En assemblant les pièces vues jusqu'ici, votre premier agent fonctionne. Il y a globalement deux façons d'implémenter. La boucle à la main pour apprendre les mécanismes, le SDK pour se simplifier la vie en production — dans les deux cas, c'est la même boucle à l'intérieur. Clarifions d'abord la différence.
On fait tourner appels d'API et exécution d'outils dans son propre while. Tout ce qui se passe à l'intérieur est visible, ce qui est idéal pour apprendre. Contrôle total.
Quand l'utiliser : pour comprendre les mécanismes / contrôler finement.
On confie à la boucle d'exécution d'outils fournie par les SDK (Claude et autres) les définitions et fonctions d'outils, et on la laisse faire. On évite d'écrire la partie répétitive ; c'est robuste.
Quand l'utiliser : pour construire vite / déléguer le traitement standard en production.
La A. boucle à la main se résume à relier honnêtement les pièces vues jusqu'ici. On tient un historique de conversation, on appelle le LLM, on exécute l'outil si demandé et on renvoie le résultat, sinon on sort — rien de plus.
# A. Boucle à la main (pseudo-code / style messages de Claude) messages = [ user(goal) ] for step in range(MAX_STEPS): # ← garde-fou par plafond reply = client.messages.create( model="claude-…", messages=messages, tools=[search_tool], ) log(step, reply) # ← toujours journaliser, pour observer if reply.stop_reason == "tool_use": out = run_tool(reply.tool_name, reply.tool_args) messages += [ assistant(reply), tool_result(out) ] else: break # réponse finale obtenue → fin
Avec le B. tool runner du SDK, c'est la bibliothèque qui prend en charge cette boucle for elle-même. Vous n'écrivez que « la définition d'outil », « la fonction concrète » et « l'objectif initial ». La boucle, la gestion de l'historique et le renvoi des résultats d'outils sont traités en interne.
# B. Déléguer au tool runner du SDK (pseudo-code) runner = ToolRunner( model="claude-…", tools=[search_tool], # définition handlers={"search_weather": run_tool}, # fonction concrète ) answer = runner.run(goal) # la boucle tourne en interne
💡 Les noms exacts des méthodes sont dans la doc officielle. Ici, client.messages.create et ToolRunner sont du pseudo-code destiné à donner l'idée. Les noms d'arguments réels, la forme des valeurs de retour et les identifiants de modèle diffèrent selon les SDK et changent souvent : vérifiez toujours la dernière version dans la doc officielle. Saisir d'abord les concepts, combler les détails avec la doc — c'est la voie qui évite les détours. Pour une initiation pas à pas, voyez aussi Comment construire un agent IA (initiation).
La recommandation : « écrire une fois à la main avec A, puis se simplifier la vie avec B ». Une fois qu'on a fait tourner soi-même la boucle, le SDK n'est plus une boîte noire, et en cas de blocage on peut imaginer ce qui se passe à l'intérieur.
Le faire tourner et l'observer
Une fois assemblé, on le lance. Ce qui compte ici, ce n'est pas simplement de le faire tourner, mais d'observer « où il a réfléchi à quoi, et où il a échoué ». En développement d'agents, c'est la qualité de cette observation qui décide du résultat. C'est pourquoi on met en place des logs dès le départ.
Au minimum, journalisez ce qui suit à chaque tour. En boucle à la main, la ligne log(step, reply) suffit.
À ce tour, le LLM a-t-il jugé « utiliser un outil » ou opté pour une réponse finale. Le point de bifurcation de la décision.
Le nom de l'outil et les arguments. Si c'est erroné ici, le résultat déraille. C'est là que se voit le plus souvent un décalage de compréhension du LLM.
La valeur de retour de l'outil. Trop volumineuse, vide, en erreur… c'est là que se cache ce qui fausse la décision suivante.
En le faisant réellement tourner, des comportements intéressants (et pénibles) apparaissent. Par exemple — le LLM appelle un outil alors que ce n'est pas nécessaire. À l'inverse, il n'appelle pas là où il le faudrait et répond de mémoire. Il se trompe subtilement sur le nom de la ville dans les arguments. Il répète plusieurs fois la même recherche là où une seule suffirait. Ces « tics de décision », on ne les remarque qu'en regardant les logs.
✅ Les logs ne sont pas un « luxe » : ils sont indispensables dès le départ. Comme l'intérieur d'un agent est difficile à voir, sans logs on ne comprend absolument pas « pourquoi il a agi ainsi ». Pour l'instant, une ligne de print suffit. L'évaluation et l'observabilité, qui systématisent cette observation, seront traitées en profondeur au chapitre 5. Prenez d'abord ici l'habitude de « regarder ».
Les points de blocage
Même en configuration minimale, dès qu'on le fait tourner on se heurte forcément à quelques murs. Ce sont tous des blocages classiques du développement d'agents, que nous résoudrons au fil des chapitres. Ici, on saisit « la nature du problème » et « le pansement possible pour l'instant », en confiant le vrai traitement aux chapitres à venir.
Il appelle sans fin le même outil et ne termine jamais. Il tourne à vide, incapable de juger que l'objectif est atteint.
Pansement : mettre impérativement un plafond de tours. Détecter et arrêter les appels consécutifs aux mêmes arguments.
L'outil renvoie un JSON gigantesque ou une page entière, qu'on réinjecte tel quel dans la conversation, gonflant coût et latence.
Pansement : côté outil, ne garder que les champs utiles / résumer avant de renvoyer. Ne pas passer le tout brut.
Plus les tours s'accumulent, plus l'historique enfle et se rapproche du plafond, devenant lent, coûteux et instable.
Pansement : résumer et compresser les vieux échanges / jeter les résultats d'outils inutiles. Vrai traitement dans les chapitres suivants.
📊 Les trois sont des problèmes de « conception du contexte ». Boucle infinie, valeur de retour surdimensionnée, explosion du contexte : à la racine, tout remonte à la conception du contexte — « quoi, et en quelle quantité, passer au LLM ». Cette approche est systématisée sous le nom de context engineering et forme l'épine dorsale du développement d'agents. Pour commencer, retenez le mot d'ordre : « ne pas trop passer, ne pas trop accumuler ».
- Un agent minimal = les 4 éléments montés au plus petit. La seule différence avec un usage d'API unique, c'est la boucle.
- La boucle répète un tour de « réfléchir → appeler l'outil → renvoyer le résultat → réfléchir à nouveau → s'arrêter une fois terminé ». Un garde-fou de plafond de tours est indispensable.
- Pour l'outil, on passe la définition (nom, description, arguments) au LLM, et on exécute la fonction concrète chez soi. Le LLM ne fait que demander.
- Deux implémentations : boucle à la main (pour apprendre) et tool runner du SDK (pour la production). L'intérieur est identique.
- Observer dès le départ avec des logs, et repérer les blocages classiques : boucle infinie, valeur de retour surdimensionnée, explosion du contexte.
Une fois votre premier agent en marche, il est temps d'étoffer sérieusement ses outils. Au chapitre 3, « MCP et connexion d'outils », on relie les outils aux services et données externes par une méthode standard (MCP), et on élargit d'un coup la main de l'agent. Pour revoir la vue d'ensemble du chapitre précédent, revenez au chapitre 1.