Module 2 — Boucle de raisonnement et d'action
Le module précédent définissait un agent par la délégation du contrôle. Cette délégation prend une forme concrète : une boucle qui alterne pensée, action et observation jusqu'à ce que le modèle décide de conclure. C'est le schéma ReAct proposé par Yao et al. en 2022, et c'est le cœur mécanique de tous les agents modernes, y compris ceux qui se cachent derrière LangGraph ou CrewAI.
Le cycle en trois temps
Le modèle produit d'abord une pensée — une phrase qui décrit ce qu'il compte faire et pourquoi. Il émet ensuite une action — l'appel d'un outil avec ses arguments. L'environnement renvoie une observation — le résultat de l'outil, texte brut. La boucle réinjecte cette observation dans la fenêtre de contexte et redonne la main au modèle, qui produit une nouvelle pensée, et ainsi de suite. Le cycle s'arrête quand le modèle choisit l'action spéciale finish, ou quand la limite d'itérations est atteinte.
Cette structure paraît triviale et c'est sa force : elle rend l'agent lisible. À tout moment, on peut relire la trace ligne à ligne et pointer du doigt la mauvaise décision. Un agent qui n'exprime pas ses pensées à voix haute est ingérable — c'est le premier enseignement.
Un agent en soixante lignes
Voici l'ossature minimale du fil rouge. Elle utilise l'API d'appel d'outils du modèle plutôt qu'un parsing manuel de la sortie textuelle, parce que le parsing manuel est la première cause de casse en production.
import json
from openai import OpenAI
client = OpenAI()
OUTILS = {
"chercher_web": lambda requete: rechercher(requete),
"lire_url": lambda url: telecharger(url),
"chercher_base": lambda requete: base_interne(requete),
"finish": lambda reponse: {"__fin__": reponse},
}
SCHEMAS = charger_schemas_outils() # module 3
def executer_agent(question: str, iterations_max: int = 12) -> str:
messages = [
{"role": "system", "content": SYSTEME},
{"role": "user", "content": question},
]
for etape in range(iterations_max):
reponse = client.chat.completions.create(
model="gpt-4o", messages=messages,
tools=SCHEMAS, tool_choice="auto", temperature=0.2,
)
message = reponse.choices[0].message
messages.append(message)
if not message.tool_calls:
return message.content # sortie sans outil
for appel in message.tool_calls:
nom = appel.function.name
args = json.loads(appel.function.arguments)
resultat = OUTILS[nom](**args)
if isinstance(resultat, dict) and "__fin__" in resultat:
return resultat["__fin__"]
messages.append({
"role": "tool", "tool_call_id": appel.id,
"content": json.dumps(resultat, ensure_ascii=False),
})
raise RuntimeError(f"Budget d'iterations depasse ({iterations_max})")
Trois choses méritent d'être notées. D'abord, la trace est portée par messages : c'est cette liste qui sera relue au module 9. Ensuite, finish est un outil comme les autres, ce qui uniformise le format de sortie. Enfin, la limite d'itérations est dure — elle lève une exception plutôt que de retourner une réponse partielle. Le module 7 discutera si cette dureté est le bon choix, mais il faut au minimum qu'elle existe.
La consigne système, sobrement
Le prompt système ne doit pas raconter la vie de l'agent. Il doit dire ce que l'agent fait, quels outils il a, et — c'est le point clé — quand il doit s'arrêter.
Tu es un agent de veille documentaire. Tu réponds à une question de
recherche en interrogeant le web et la base interne, puis tu écris
une note courte avec les sources. Tu appelles l'outil `finish` dès
que tu as trois sources concordantes ou après avoir lu au maximum
huit pages. Chaque pensée est une phrase en français.
Une consigne plus longue ne rend pas l'agent plus fiable ; elle augmente le contexte, la latence et surtout la probabilité que le modèle en oublie une partie. Les modules 3 et 7 ajouteront des instructions ciblées, pas des paragraphes.
La condition d'arrêt : le point qui casse en premier
Un agent sans condition d'arrêt claire tourne en rond. On observe deux échecs symétriques. L'arrêt trop tôt : le modèle appelle finish après une seule recherche parce que la première page semble suffire. L'arrêt trop tard : le modèle relit la même page trois fois en pensant chaque fois qu'il lui manque une information — c'est la boucle du module 8.
La règle qui fonctionne le mieux est quantitative : « trois sources concordantes ou huit pages lues ». Elle est vérifiable par le modèle, elle est vérifiable par un humain qui relit la trace, et elle borne le coût. Une règle qualitative — « quand tu es sûr » — ne borne rien du tout.
Première exécution du fil rouge
Voici les six premières étapes observées sur la question « quels concurrents ont ajouté un tableau Kanban en 2025 ? » :
Etape 1 pensee : lister trois concurrents connus puis chercher leur roadmap 2025
Etape 1 action : chercher_web(requete="tableau Kanban roadmap 2025 SaaS gestion projet")
Etape 2 pensee : quatre URL prometteuses, je lis la premiere
Etape 2 action : lire_url(url="https://exemple-a.com/changelog-2025")
Etape 3 pensee : Kanban confirme chez A, avec integration Slack ; source solide
Etape 3 action : lire_url(url="https://exemple-b.com/blog/kanban")
L'agent atteint la condition d'arrêt à l'étape 7 avec trois sources vérifiées, pour un coût de 0,28 dollar. C'est ce déroulé lisible que le reste du cours va rendre robuste.