\n\n\n\n Je construis des agents IA : mon parcours au-delà de l'ingénierie des prompts - AgntAI Je construis des agents IA : mon parcours au-delà de l'ingénierie des prompts - AgntAI \n

Je construis des agents IA : mon parcours au-delà de l’ingénierie des prompts

📖 10 min read1,884 wordsUpdated Mar 26, 2026

Salut tout le monde, ici Alex d’agntai.net. Nous sommes le 25 mars 2026, et je lutte depuis un moment avec quelque chose de fondamental : comment nous *construisons* réellement ces agents IA. Pas seulement les éléments brillants de LLM, mais toute la structure complexe qui leur permet de faire quoi que ce soit d’utile dans le monde réel. Heureusement, nous avons dépassé la phase du “l’ingénierie des prompts est tout ce dont vous avez besoin”, et maintenant il s’agit de réunir des systèmes fiables et extensibles.

Aujourd’hui, je veux parler de l’architecture des agents, spécifiquement de la manière dont nous pouvons passer de l’exécution de tâches simples et linéaires à quelque chose de plus résilient, capable de gérer des situations inattendues. Mon objectif sera de me concentrer sur une architecture modulaire et réfléchissante – essentiellement, construire des agents capables d’examiner leur propre processus, de comprendre ce qui s’est mal passé (ou bien), et de s’adapter. Ce n’est pas juste de la théorie ; j’ai vu de mes propres yeux à quel point un peu de conscience de soi peut éviter beaucoup de maux de tête.

Le Problème avec les Agents Linéaires : Mon Déboire de Projet du Week-end

Commençons par une histoire. Il y a quelques semaines, j’essayais d’automatiser une tâche d’analyse de données assez simple pour un projet parallèle. Je voulais qu’un agent récupère des données financières, exécute un ensemble spécifique de tests statistiques, puis résume les résultats dans un rapport en markdown. Ma première pensée ? Une chaîne simple :

  • Récupérer des données de l’API.
  • Nettoyer les données.
  • Exécuter des tests statistiques (en utilisant une bibliothèque pré-définie).
  • Générer un rapport.

J’ai assemblé cela avec LangChain, utilisant un appel à GPT-4 pour chaque étape, et je me sentais plutôt satisfait. Puis j’ai appuyé sur “exécuter”.

Le premier problème ? La limite de taux de l’API. Mon agent continuait d’essayer de l’atteindre, échouant, puis passant à l’étape suivante avec un ensemble de données vide. Pas de gestion des erreurs, pas de logique de reprise, juste un poli “Je n’ai pas pu obtenir les données, mais voici un joli rapport sur rien.”

Le deuxième problème ? L’étape de nettoyage des données. Parfois, l’API retournait des noms de colonnes légèrement différents. Mes fonctions de tests statistiques s’attendaient à `close_price`, mais elles ont reçu `closing_price`. Mon agent a juste renvoyé une trace d’erreur Python et s’est arrêté. Encore une fois, pas de reprise élégante, pas d’essai de comprendre pourquoi la fonction a échoué.

Cette expérience, bien que frustrante, a vraiment souligné un point : les conceptions d’agents simples et linéaires, où chaque étape suit aveuglément la dernière, sont fragiles. Elles supposent un monde parfait où les API fonctionnent toujours, les données sont toujours impeccables, et les fonctions échouent rarement. Le monde réel n’est pas comme ça. Nous avons besoin d’agents capables de faire plus que simplement exécuter une séquence ; ils doivent observer, réfléchir et s’adapter.

Présentation de l’Architecture d’Agent Réfléchissant : L’Approche du “Monologue Intérieur”

L’idée principale derrière une architecture d’agent réfléchissant est de donner à l’agent un mécanisme pour observer ses propres actions et résultats, puis d’utiliser cette observation pour éclairer les futures décisions. Pensez-y comme un “monologue intérieur” où l’agent se pose des questions : “Qu’est-ce qui vient de se passer ? Était-ce bien ? Que devrais-je faire ensuite compte tenu de ce que j’ai appris ?”

Il ne s’agit pas seulement d’ajouter des blocs try-except. Il s’agit de rendre le processus de prise de décision de l’agent dynamique et informé par son propre historique d’exécution. Voici comment je décompose généralement cela :

Les Composants Clés d’un Agent Réfléchissant

  1. Module de Perception : C’est ainsi que l’agent “voit” le monde et ses propres actions. Il collecte des observations de son environnement (réponses API, changements du système de fichiers, saisie de l’utilisateur) et, de manière cruciale, des sorties et messages d’erreur de ses propres outils.
  2. Module d’Action : C’est ici que l’agent effectue des tâches en utilisant ses outils disponibles (fonctions, API, autres modèles). C’est ce à quoi la plupart des gens pensent lorsqu’ils construisent des agents.
  3. Module de Mémoire : Stocke les observations passées, les actions et les réflexions. Ce n’est pas juste un contexte à court terme ; pour la réflexion, nous avons souvent besoin d’une mémoire à long terme des stratégies réussies et échouées.
  4. Module de Réflexion : C’est le cerveau du processus réfléchi. Après une action, ce module prend les observations et les souvenirs, et évalue de manière critique le résultat. Il pose des questions comme :
    • La dernière action a-t-elle réussi ?
    • Si non, pourquoi a-t-elle échoué ?
    • Qu’est-ce qui aurait pu être fait différemment ?
    • Quelle devrait être la *prochaine* action, compte tenu de cette nouvelle compréhension ?
    • Devrais-je modifier mon plan ?
  5. Module de Planification/Gestion des Objectifs : Bien que souvent entrelacé avec la réflexion, ce module est responsable de décomposer des objectifs de haut niveau en étapes actionnables et de mettre à jour le plan en fonction des réflexions.

La clé ici est la boucle de rétroaction : Action -> Perception -> Réflexion -> (potentiellement) Mise à jour du Plan -> Nouvelle Action. Ce n’est pas une route à sens unique ; c’est un cycle continu.

Un Exemple Pratique : Agent de Pipeline de Données Auto-Réparateur

Revenons à mon projet de données financières. Comment un agent réfléchissant gérerait ces problèmes ? Au lieu d’une chaîne aveugle, nous introduirions la réflexion à des moments critiques.

Étape 1 : Définir les Outils

Notre agent a besoin d’outils pour interagir avec le monde. Ce sont juste des fonctions Python enveloppées de manière à ce que le LLM puisse les invoquer.


def get_financial_data(symbol: str, start_date: str, end_date: str) -> dict:
 """
 Récupère des données financières historiques pour un symbole boursier donné.
 Lève une exception pour les erreurs d'API ou les limites de taux.
 """
 # Simuler un appel API avec des erreurs potentielles
 import random
 if random.random() < 0.1: # Simuler un échec de l'API / limite de taux de 10%
 raise ConnectionError("Échec de l'appel API : limite de taux dépassée ou service non disponible.")
 if symbol == "FAILCO":
 raise ValueError("Symbole invalide fourni.")
 return {"symbol": symbol, "data": [{"date": "2023-01-01", "close_price": 100.0}]}

def clean_data(raw_data: dict) -> dict:
 """
 Nettoie et standardise les données financières.
 Tente de normaliser les variations de noms de colonnes courantes.
 """
 data = raw_data.get("data", [])
 if not data:
 raise ValueError("Aucune donnée à nettoyer.")
 
 # Simuler une variation des noms de colonnes et tenter de corriger
 if "closing_price" in data[0]:
 for item in data:
 item["close_price"] = item.pop("closing_price")
 
 # Validation de base
 for item in data:
 if "close_price" not in item:
 raise ValueError(f"Missing 'close_price' in item: {item}")
 
 return {"symbol": raw_data["symbol"], "cleaned_data": data}

def run_statistical_tests(cleaned_data: dict) -> dict:
 """
 Exécute des tests statistiques pré-définis sur des données financières nettoyées.
 """
 if not cleaned_data.get("cleaned_data"):
 raise ValueError("Aucune donnée nettoyée à analyser.")
 
 # Simuler une certaine analyse statistique
 avg_price = sum(item["close_price"] for item in cleaned_data["cleaned_data"]) / len(cleaned_data["cleaned_data"])
 return {"analysis_results": f"Prix de clôture moyen : {avg_price:.2f}"}

def generate_report(analysis_results: dict) -> str:
 """
 Génère un rapport en markdown à partir des résultats d'analyse.
 """
 return f"# Rapport d'Analyse Financière\n\n{analysis_results.get('analysis_results', 'Aucune analyse effectuée.')}"

tools = [get_financial_data, clean_data, run_statistical_tests, generate_report]

Ces outils sont assez standard. La magie réside dans la façon dont l’agent les utilise et réfléchit sur leurs résultats.

Étape 2 : La Boucle de l’Agent Réfléchissant

C’est ici que nous introduisons les étapes d’observation et de réflexion. Je simplifie les appels LLM ici pour des raisons de concision, mais imaginez un message système qui guide le LLM pour agir en tant que “Module de Réflexion.”


from typing import List, Dict, Any
import time

class ReflectiveAgent:
 def __init__(self, llm, tools: List[Any]):
 self.llm = llm # Cela serait votre client LLM (par exemple, OpenAI, Anthropic)
 self.tools = {tool.__name__: tool for tool in tools}
 self.memory: List[Dict[str, Any]] = [] # Stocke l'historique des actions, observations, réflexions
 self.current_plan: List[str] = ["get_financial_data", "clean_data", "run_statistical_tests", "generate_report"]
 self.current_step_index = 0
 self.max_retries = 3

 def _call_llm(self, prompt: str) -> str:
 # Dans un système réel, ceci appellerait votre LLM
 # Pour cet exemple, nous allons simuler une réponse simple du LLM basée sur des mots-clés
 print(f"LLM Prompt: {prompt}\n---")
 if "Error" in prompt or "failed" in prompt:
 if "API call failed" in prompt:
 return "Réflexion : L'appel de l'outil précédent a échoué en raison d'une erreur API. Je devrais réessayer l'outil 'get_financial_data'. Il s'agit d'un problème temporaire. Je vais attendre un peu avant de réessayer."
 elif "Missing 'close_price'" in prompt or "No data to clean" in prompt:
 return "Réflexion : L'outil 'clean_data' a échoué en raison d'un format de données inattendu ou de données manquantes. Je dois réévaluer les données brutes ou ajuster ma stratégie de nettoyage. Peut-être que l'outil 'get_financial_data' n'a pas fourni de bonnes données. Je devrais essayer d'appeler à nouveau 'get_financial_data' pour voir si la structure des données a changé, ou je pourrais avoir besoin de demander des clarifications à l'utilisateur si j'avais un outil d'interaction avec l'utilisateur."
 elif "Invalid symbol" in prompt:
 return "Réflexion : L'outil 'get_financial_data' a échoué en raison d'un symbole invalide. Il s'agit d'une erreur d'entrée fondamentale. Je devrais informer l'utilisateur ou m'arrêter."
 else:
 return "Réflexion : Une erreur inattendue est survenue. Je dois réexaminer la dernière action et essayer de comprendre la cause profonde. Mon plan actuel pourrait être défectueux."
 elif "succeeded" in prompt and "next step" in prompt:
 return "Réflexion : La dernière action a réussi. Je devrais passer à l'étape suivante de mon plan."
 else:
 return "Réflexion : Je réfléchis actuellement à la tâche. Quel est l'état actuel et quelle devrait être ma prochaine action basée sur le plan ?"


 def run(self, symbol: str):
 context = {"symbol": symbol, "raw_data": None, "cleaned_data": None, "analysis_results": None}

 while self.current_step_index < len(self.current_plan):
 current_tool_name = self.current_plan[self.current_step_index]
 tool_func = self.tools.get(current_tool_name)

 if not tool_func:
 print(f"Erreur : Outil '{current_tool_name}' non trouvé. Arrêt en cours.")
 break

 print(f"\n--- Tentative d'exécution : {current_tool_name} ---")
 
 observation = {"status": "started", "tool": current_tool_name}
 retries = 0

 while retries <= self.max_retries:
 try:
 if current_tool_name == "get_financial_data":
 result = tool_func(symbol=symbol, start_date="2023-01-01", end_date="2023-12-31")
 context["raw_data"] = result
 elif current_tool_name == "clean_data":
 result = tool_func(context["raw_data"])
 context["cleaned_data"] = result
 elif current_tool_name == "run_statistical_tests":
 result = tool_func(context["cleaned_data"])
 context["analysis_results"] = result
 elif current_tool_name == "generate_report":
 result = tool_func(context["analysis_results"])
 print(result) # Sortie du rapport final
 context["final_report"] = result
 
 observation["status"] = "succeeded"
 observation["output"] = result
 break # Action réussie, quitter la boucle de réessai
 except Exception as e:
 observation["status"] = "failed"
 observation["error"] = str(e)
 print(f"L'outil '{current_tool_name}' a échoué : {e}")
 retries += 1
 if retries <= self.max_retries:
 print(f"Réessayer '{current_tool_name}' (Tentative {retries}/{self.max_retries})...")
 time.sleep(1) # Simuler un délai
 else:
 print(f"Nombre maximum de réessais atteint pour '{current_tool_name}'.")
 break # Nombre maximum de réessais atteint, quitter la boucle de réessai

 self.memory.append({"action": observation})

 # --- Étape de réflexion ---
 reflection_prompt = f"""
 Contexte actuel : {context}
 Dernière action : {observation}
 Objectif : Compléter l'analyse des données financières et le rapport.

 Réfléchir au résultat de la dernière action.
 - A-t-elle réussi ?
 - Si ce n'est pas le cas, quelle était l'erreur ?
 - Quelle devrait être la prochaine étape ? Devrais-je réessayer, changer de stratégie ou m'arrêter ?
 - Si une erreur s'est produite indiquant une mauvaise entrée, que devrais-je faire ?
 """
 reflection = self._call_llm(reflection_prompt)
 print(f"Réflexion : {reflection}")
 self.memory.append({"reflection": reflection})

 # En fonction de la réflexion, décider de la prochaine action
 if observation["status"] == "failed":
 if "API call failed" in observation["error"] and retries <= self.max_retries:
 # La logique de réessai est déjà traitée par la boucle while interne,
 # mais la réflexion confirme la stratégie. Si nous voulions ajuster
 # le nombre de tentatives ou la stratégie en fonction du LLM, nous le ferions ici.
 print("La réflexion suggère de réessayer, ce qui a été tenté.")
 # Si tous les réessais échouent, nous avons besoin d'une nouvelle stratégie ou de nous arrêter.
 if retries > self.max_retries:
 print("La réflexion indique que tous les réessais ont échoué pour une erreur transitoire. Arrêt ou escalade.")
 break
 # Si des réessais sont toujours en cours, le 'break' de la boucle interne n'a pas été atteint,
 # donc nous devrions en réalité rester à la même étape pour la prochaine itération de la boucle externe,
 # continuant effectivement le réessai, ou, si nous voulons vraiment que la réflexion guide,
 # le LLM devrait produire une nouvelle action. Pour la simplicité,
 # si le nombre maximum de réessais a été atteint et a échoué, nous nous arrêtons.
 if retries > self.max_retries:
 print("Tous les réessais pour une erreur transitoire ont échoué. Arrêt.")
 break

 elif "Invalid symbol" in observation["error"]:
 print("La réflexion indique une erreur d'entrée critique. Impossible de continuer. Informer l'utilisateur.")
 # Dans un véritable agent, cela déclencherait un outil d'interaction avec l'utilisateur.
 break
 elif "No data to clean" in observation["error"] or "Missing 'close_price'" in observation["error"]:
 print("La réflexion indique un problème de qualité des données. Réévaluation de l'étape précédente ou arrêt.")
 # Ici, une réflexion plus avancée pourrait suggérer de revenir à 'get_financial_data'
 # ou même de modifier les paramètres de l'outil 'clean_data'.
 # Pour l'instant, nous nous arrêterons si c'est un problème de données persistant après les réessais.
 if retries > self.max_retries:
 print("Problème persistant de nettoyage des données. Arrêt.")
 break
 # Si l'erreur concernait l'absence de données, cela implique que get_financial_data a échoué silencieusement ou a fourni de mauvaises données.
 # Un agent solide pourrait réfléchir et décider de rappeler get_financial_data avant de réessayer clean_data.
 # Pour cet exemple, nous nous arrêterons simplement si les réessais n'ont pas résolu le problème.
 print("La réflexion suggère un problème de données. Arrêt pour le moment.")
 break # Arrêt pour des problèmes de données non résolvables après les réessais

 else:
 print("Échec non traité après réflexion. Arrêt.")
 break # Erreurs non traitées

 self.current_step_index += 1 # Passer à l'étape suivante si celle actuelle a réussi ou a été traitée.

 print("\n--- Exécution de l'agent terminée ---")
 return context

# --- Exécution de l'agent ---
# Remplacez par votre client LLM réel
class MockLLM:
 def chat(self, messages):
 return {"choices": [{"message": {"content": "Réponse Mock LLM"}}]}

mock_llm = MockLLM()
agent = ReflectiveAgent(mock_llm, tools)

print("\n--- Exécution avec un bon symbole ---")
agent.run("AAPL")

print("\n--- Exécution avec un symbole susceptible d'échouer API ---")
# Réinitialiser l'état de l'agent pour une nouvelle exécution
agent = ReflectiveAgent(mock_llm, tools)
agent.run("GOOG")

print("\n--- Exécution avec un symbole délibérément invalide ---")
agent = ReflectiveAgent(mock_llm, tools)
agent.run("FAILCO")

Que se passe-t-il ici ?

  • Le `ReflectiveAgent` dispose d’une `memory` pour suivre son parcours.
  • Après chaque exécution d’outil, il enregistre l’`observation` (succès, échec, sortie, erreur).
  • Essentiellement, il appelle ensuite `_call_llm` (simulant notre LLM pour la réflexion) avec une invite qui inclut le contexte actuel et le résultat de la `last_action`.
  • La « réflexion » du LLM informe alors le prochain mouvement de l’agent. Si l’API a échoué, le LLM suggère de réessayer. S’il s’agit d’un symbole invalide, il suggère d’arrêter. Si le nettoyage des données a échoué en raison d’un format inattendu, il suggérerait idéalement de réexaminer les données ou d’ajuster l’approche de nettoyage (bien que ma réponse mock LLM soit simplifiée).
  • La boucle `while` extérieure se poursuit jusqu’à ce que le plan soit complet ou qu’une erreur critique et irrécupérable se produise après réflexion.

Ceci est un exemple simplifié, mais il démontre la boucle centrale. Un système réel aurait une invite beaucoup plus sophistiquée pour le `Module de Réflexion` et potentiellement un LLM capable de produire directement des commandes structurées comme `RETRY_TOOL(tool_name, delay)` ou `MODIFY_PLAN(new_step, index)`. Ma fonction `_call_llm` est un espace réservé qui renvoie des réponses standardisées basées sur des mots-clés, mais dans une configuration de production, ce serait là où votre chaîne LLM réelle réside, conçue pour produire des actions spécifiques basées sur sa réflexion.

Mon expérience dans la création de ceux-ci

Lorsque j’ai commencé à intégrer ces boucles réflexives, la configuration initiale était un peu plus complexe. Vous devez créer de bonnes invites pour l’étape de réflexion, en vous assurant que le LLM comprend son rôle dans l’évaluation des résultats. Vous devez également structurer vos observations clairement afin que le LLM ait de bonnes entrées.

Mais le retour sur investissement a été significatif. Mes agents sont passés de la chute à la première difficulté à gérer avec aisance les erreurs réseau transitoires, à s’adapter aux légers changements de schéma de données, et même parfois à identifier des problèmes plus profonds que je n’avais pas anticipés. C’est comme donner un peu de bon sens à votre agent.

Un défi auquel j’ai été confronté a été la conception des invites pour le module de réflexion. Vous ne voulez pas qu’il se limite à répéter l’erreur. Vous voulez qu’il analyse, infère et propose. J’ai trouvé que les invites réussies demandaient explicitement :

  • « Étant donné l’échec observé, quelle est la cause la plus probable ? »
  • « Quelle action spécifique devrait être prise pour y remédier ? Considérez les réessais, les outils alternatifs ou la modification du plan. »
  • « Si ce problème persiste, comment devrais-je procéder à une escalade ou à une terminaison en douceur ? »

De plus, ne sous-estimez pas l’importance du `Memory Module`. Pour des tâches complexes, l’agent doit se souvenir *pourquoi* il a essayé quelque chose et quels ont été les résultats sur plusieurs étapes. Les fenêtres de contexte à court terme ne suffisent pas pour une véritable réflexion.

Points à Retenir

  1. Concevez pour l’Échec, Pas Seulement pour le Succès : En planifiant le flux de travail de votre agent, réfléchissez activement à ce qui pourrait mal tourner à chaque étape. Cela vous prépare à déterminer où placer vos points d’observation et de réflexion.
  2. Observation Explicite est Essentielle : Assurez-vous que vos outils renvoient des sorties claires et structurées et, de manière critique, propagent efficacement les erreurs. Le Reflection Module ne peut fonctionner qu’avec ce qu’il “voit.”
  3. Traitez la Réflexion comme une Priorité : Ne vous contentez pas d’ajouter une gestion des erreurs. Intégrez un Reflection Module dédié (même s’il s’agit simplement d’un appel LLM spécifique) dans la boucle principale de votre agent.
  4. Commencez Simple, Itérez : Vous n’avez pas besoin d’un système de réflexion super complexe dès le premier jour. Commencez avec une logique de réessai de base basée sur la réflexion LLM, puis ajoutez progressivement une prise de décision plus sophistiquée pour la modification de plan ou le changement d’outil.
  5. Incitez le Reflection Module avec Précaution : Guidez votre LLM pour qu’il effectue une pensée analytique, pas seulement un résumé. Posez des questions ouvertes sur les causes profondes et les solutions proposées.
  6. Considérez la Mémoire à Long Terme : Pour les agents qui fonctionnent sur de longues périodes ou gèrent des tâches complexes et multidimensionnelles, un système de mémoire qui stocke plus que le contexte du tour actuel est crucial pour une réflexion efficace et un apprentissage.

Créer des agents capables de réfléchir sur leurs propres performances les rend significativement plus solides et utiles. Cela nous rapproche de systèmes véritablement autonomes capables de fonctionner de manière fiable dans des environnements imprévisibles. C’est un peu plus de travail au début, mais c’est un investissement qui rapporte beaucoup en fiabilité des agents et en réduction des maux de tête liés à la maintenance. Essayez sur votre prochain projet !

Articles Connexes

🕒 Published:

🧬
Written by Jake Chen

Deep tech researcher specializing in LLM architectures, agent reasoning, and autonomous systems. MS in Computer Science.

Learn more →
Browse Topics: AI/ML | Applications | Architecture | Machine Learning | Operations

Recommended Resources

AgntapiAgent101AgntzenAgnthq
Scroll to Top