Aller au contenu principal

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.

En résumé

  • Le schéma pensée → action → observation est la structure canonique d'un agent ; il rend chaque décision auditable.
  • Une limite d'itérations dure est non négociable ; sans elle, le budget explose au premier bogue.
  • La condition d'arrêt doit être quantitative pour être vérifiable ; les formulations qualitatives boucle ou concluent trop tôt.
  • finish traité comme un outil normalise la sortie et clarifie la trace.

Module suivant : décrire des outils que le modèle sache utiliser sans les confondre, avec des schémas JSON pensés comme des interfaces.