Architecture et composants fondamentaux des agents IA
Les agents IA représentent une évolution majeure des modèles de langage : là où un LLM classique se contente de générer du texte, un agent IA agit de manière autonome pour accomplir des tâches complexes. Cette autonomie repose sur une architecture bien définie qui sépare clairement les responsabilités et permet une orchestration efficace.
Dans cet article, nous allons décortiquer l’architecture complète d’un agent IA production-ready, en explorant chaque composant en détail avec des implémentations concrètes en Python. Que vous construisiez un assistant personnel, un système d’automatisation ou un agent de recherche, cette architecture modulaire constitue la fondation nécessaire.

Pourquoi une architecture modulaire ?
Avant de plonger dans les détails techniques, comprenons pourquoi l’architecture modulaire est essentielle pour les agents IA :
1. Testabilité : Chaque module peut être testé indépendamment. Vous pouvez vérifier que votre module de perception parse correctement les entrées sans avoir à tester tout l’agent.
2. Maintenabilité : Modifier le système de mémoire n’impacte pas le raisonnement. Ajouter un nouvel outil n’affecte pas la perception. Cette séparation des préoccupations est cruciale pour des projets complexes.
3. Évolutivité : Vous pouvez commencer avec un agent simple (perception + raisonnement basique + 2 outils) puis enrichir progressivement chaque module sans tout réécrire.
4. Réutilisabilité : Un module d’action bien conçu peut être réutilisé dans plusieurs agents différents. Idem pour les stratégies de raisonnement.
5. Débogage : Quand un agent échoue, l’architecture modulaire permet d’identifier rapidement quel composant pose problème (perception mal configurée ? outil défaillant ? LLM qui hallucine ?).
Les 4 piliers des agents IA
Tout agent IA performant repose sur 4 composants fondamentaux :
┌─────────────────────────────────────────────────┐
│ 🧠 AGENT IA │
├─────────────────────────────────────────────────┤
│ │
│ 👁️ PERCEPTION → 🤔 RAISONNEMENT │
│ ↑ ↓ │
│ 💾 MÉMOIRE ← 🛠️ ACTION │
│ │
└─────────────────────────────────────────────────┘
Perception (Input)
L’agent observe son environnement :
- Requêtes utilisateur
- Résultats d’outils
- État du système
- Données externes
Raisonnement (Brain)
L’agent réfléchit :
- Analyse de la situation
- Planification des étapes
- Choix de la prochaine action
- Évaluation des résultats
Action (Output)
L’agent agit :
- Appels d’APIs
- Exécution de code
- Manipulation de données
- Communication avec l’utilisateur
Mémoire (State)
L’agent se souvient :
- Conversation en cours
- Faits appris
- Historique des actions
- Résultats précédents
Architecture complète d’un agent
Schéma global
"""
┌──────────────────────────────────────────────────────────────┐
│ UTILISATEUR │
└───────────────────────┬──────────────────────────────────────┘
│
↓ Query
┌───────────────────────────────────────────────────────────────┐
│ ORCHESTRATEUR │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. Parse la requête │ │
│ │ 2. Initialise la mémoire │ │
│ │ 3. Lance la boucle agent │ │
│ │ 4. Gère les erreurs et timeouts │ │
│ └──────────────────────────────────────────────────────┘ │
└───────┬───────────────────────────────────────────────────────┘
│
↓
┌───────────────────────────────────────────────────────────────┐
│ PERCEPTION MODULE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Input Parser│ │ Sensors │ │ Tool Results│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────┬───────────────────────────────────────────────────────┘
│
↓ Observations
┌───────────────────────────────────────────────────────────────┐
│ REASONING MODULE │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM (GPT-4, Claude) │ │
│ │ - Analyze observations │ │
│ │ - Plan next steps │ │
│ │ - Decide on action │ │
│ └─────────────────────────────────────────────────────┘ │
└───────┬───────────────────────────────────────────────────────┘
│
↓ Decision
┌───────────────────────────────────────────────────────────────┐
│ ACTION MODULE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ API Calls │ │Code Executor│ │ Database │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────┬───────────────────────────────────────────────────────┘
│
↓ Results
┌───────────────────────────────────────────────────────────────┐
│ MEMORY MODULE │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Short-term │ │ Long-term │ │
│ │ (Conversation) │ │ (Vector DB) │ │
│ └─────────────────┘ └──────────────────┘ │
└───────────────────────────────────────────────────────────────┘
Implémentation modulaire
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from abc import ABC, abstractmethod
import openai
# ============= 1. PERCEPTION =============
@dataclass
class Observation:
"""Une observation de l'environnement"""
source: str # "user", "tool", "system"
content: str
metadata: Dict[str, Any] = None
class PerceptionModule:
"""Collecte et structure les observations"""
def __init__(self):
self.sensors = []
def observe(self, raw_input: Any) -> Observation:
"""Transforme un input brut en observation structurée"""
if isinstance(raw_input, str):
return Observation(source="user", content=raw_input)
elif isinstance(raw_input, dict):
return Observation(
source=raw_input.get("source", "unknown"),
content=raw_input.get("content", ""),
metadata=raw_input.get("metadata", {})
)
else:
return Observation(source="system", content=str(raw_input))
def add_sensor(self, sensor_fn):
"""Ajoute un capteur (fonction qui retourne des observations)"""
self.sensors.append(sensor_fn)
def gather_context(self) -> List[Observation]:
"""Collecte toutes les observations des capteurs"""
observations = []
for sensor in self.sensors:
try:
obs = sensor()
observations.append(obs)
except Exception as e:
observations.append(
Observation(source="system", content=f"Sensor error: {e}")
)
return observations
# ============= 2. RAISONNEMENT =============
class ReasoningStrategy(ABC):
"""Interface pour les stratégies de raisonnement"""
@abstractmethod
def think(self, observations: List[Observation], memory: 'Memory') -> Dict[str, Any]:
"""Analyse la situation et décide de la prochaine action"""
pass
class ReActReasoning(ReasoningStrategy):
"""Stratégie ReAct : Thought → Action → Observation"""
def __init__(self, llm_model="gpt-4"):
self.model = llm_model
def think(self, observations: List[Observation], memory: 'Memory') -> Dict[str, Any]:
"""
Génère une pensée (thought) et décide d'une action
"""
# Construire le contexte
context = self._build_context(observations, memory)
# Appel LLM pour raisonner
response = openai.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": self._get_system_prompt()},
{"role": "user", "content": context}
],
functions=self._get_available_functions(),
function_call="auto"
)
message = response.choices[0].message
# Extraire la pensée et l'action
thought = message.content or "Réflexion en cours..."
action = None
if message.function_call:
action = {
"name": message.function_call.name,
"arguments": json.loads(message.function_call.arguments)
}
return {
"thought": thought,
"action": action,
"finish": action is None # Si pas d'action, c'est fini
}
def _build_context(self, observations: List[Observation], memory: 'Memory') -> str:
"""Construit le contexte pour le LLM"""
context_parts = []
# Historique de la conversation
for msg in memory.short_term:
context_parts.append(f"{msg['role']}: {msg['content']}")
# Observations récentes
context_parts.append("\n=== Observations récentes ===")
for obs in observations[-5:]: # Dernières 5 observations
context_parts.append(f"[{obs.source}] {obs.content}")
return "\n".join(context_parts)
def _get_system_prompt(self) -> str:
return """Tu es un agent IA qui résout des problèmes étape par étape.
Pour chaque requête :
1. PENSE : Analyse la situation
2. AGIS : Choisis un outil à utiliser (ou réponds directement)
3. OBSERVE : Analyse le résultat
4. Répète jusqu'à résolution
Sois concis et méthodique."""
def _get_available_functions(self) -> List[Dict]:
"""Retourne la liste des fonctions disponibles pour le LLM"""
return [
{
"name": "search_web",
"description": "Recherche sur le web",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Requête de recherche"}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "Effectue un calcul mathématique",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "Expression mathématique"}
},
"required": ["expression"]
}
}
]
# ============= 3. ACTION =============
class Tool(ABC):
"""Interface pour un outil"""
@abstractmethod
def execute(self, **kwargs) -> Any:
"""Exécute l'outil avec les arguments fournis"""
pass
@property
@abstractmethod
def name(self) -> str:
"""Nom de l'outil"""
pass
class CalculatorTool(Tool):
"""Outil de calcul"""
@property
def name(self) -> str:
return "calculate"
def execute(self, expression: str) -> str:
try:
result = eval(expression) # ⚠️ Dangereux en prod
return f"Résultat : {result}"
except Exception as e:
return f"Erreur de calcul : {str(e)}"
class WebSearchTool(Tool):
"""Outil de recherche web (simulé)"""
@property
def name(self) -> str:
return "search_web"
def execute(self, query: str) -> str:
# En production, utiliser une vraie API (Tavily, Serper, etc.)
return f"Résultats de recherche pour '{query}': [Simulé] Top 3 résultats..."
class ActionModule:
"""Gère l'exécution des actions"""
def __init__(self):
self.tools: Dict[str, Tool] = {}
def register_tool(self, tool: Tool):
"""Enregistre un nouvel outil"""
self.tools[tool.name] = tool
def execute(self, action: Dict[str, Any]) -> Observation:
"""Exécute une action et retourne l'observation résultante"""
tool_name = action["name"]
arguments = action["arguments"]
if tool_name not in self.tools:
return Observation(
source="system",
content=f"Outil inconnu : {tool_name}"
)
try:
result = self.tools[tool_name].execute(**arguments)
return Observation(
source="tool",
content=result,
metadata={"tool": tool_name, "arguments": arguments}
)
except Exception as e:
return Observation(
source="system",
content=f"Erreur lors de l'exécution de {tool_name} : {str(e)}"
)
# ============= 4. MÉMOIRE =============
class Memory:
"""Gère la mémoire court-terme et long-terme"""
def __init__(self):
self.short_term: List[Dict[str, str]] = [] # Conversation actuelle
self.long_term: Dict[str, Any] = {} # Faits persistants
self.episodic: List[Dict[str, Any]] = [] # Historique des actions
def add_message(self, role: str, content: str):
"""Ajoute un message à la mémoire court-terme"""
self.short_term.append({"role": role, "content": content})
def remember_fact(self, key: str, value: Any):
"""Stocke un fait en mémoire long-terme"""
self.long_term[key] = value
def recall_fact(self, key: str) -> Optional[Any]:
"""Récupère un fait de la mémoire long-terme"""
return self.long_term.get(key)
def add_episode(self, action: str, result: str, success: bool):
"""Ajoute un épisode à la mémoire épisodique"""
self.episodic.append({
"action": action,
"result": result,
"success": success,
"timestamp": datetime.now().isoformat()
})
def get_recent_episodes(self, n: int = 5) -> List[Dict]:
"""Récupère les N derniers épisodes"""
return self.episodic[-n:]
def clear_short_term(self):
"""Réinitialise la mémoire court-terme"""
self.short_term = []
# ============= 5. ORCHESTRATEUR =============
class Agent:
"""Orchestrateur principal de l'agent"""
def __init__(
self,
reasoning_strategy: ReasoningStrategy,
max_iterations: int = 10
):
self.perception = PerceptionModule()
self.reasoning = reasoning_strategy
self.action = ActionModule()
self.memory = Memory()
self.max_iterations = max_iterations
def run(self, query: str) -> str:
"""
Exécute l'agent sur une requête
"""
# 1. Perception : Observer la requête
initial_observation = self.perception.observe(query)
self.memory.add_message("user", query)
observations = [initial_observation]
iteration = 0
while iteration < self.max_iterations:
print(f"\n{'='*60}")
print(f"🔄 Itération {iteration + 1}/{self.max_iterations}")
print(f"{'='*60}")
# 2. Raisonnement : Analyser et décider
decision = self.reasoning.think(observations, self.memory)
thought = decision["thought"]
action = decision["action"]
finish = decision["finish"]
print(f"\n💭 Pensée : {thought}")
# Sauvegarder la pensée
self.memory.add_message("assistant", f"[THOUGHT] {thought}")
# 3. Action : Exécuter si nécessaire
if finish:
print(f"\n✅ Réponse finale : {thought}")
return thought
if action:
print(f"\n🔧 Action : {action['name']}({action['arguments']})")
# Exécuter l'action
result_observation = self.action.execute(action)
observations.append(result_observation)
print(f"📊 Résultat : {result_observation.content}")
# Sauvegarder dans la mémoire
self.memory.add_message("tool", result_observation.content)
self.memory.add_episode(
action=action["name"],
result=result_observation.content,
success="Erreur" not in result_observation.content
)
iteration += 1
return "⚠️ Limite d'itérations atteinte"
# ============= EXEMPLE D'UTILISATION =============
if __name__ == "__main__":
# Créer l'agent
agent = Agent(
reasoning_strategy=ReActReasoning(llm_model="gpt-4"),
max_iterations=10
)
# Enregistrer les outils
agent.action.register_tool(CalculatorTool())
agent.action.register_tool(WebSearchTool())
# Exécuter
result = agent.run("""
Cherche sur le web le PIB de la France en 2023,
puis calcule combien ça fait par habitant (67 millions d'habitants)
""")
print(f"\n{'='*60}")
print(f"📝 Résultat final : {result}")
print(f"{'='*60}")
Sortie exemple
============================================================
🔄 Itération 1/10
============================================================
💭 Pensée : Je dois d'abord rechercher le PIB de la France en 2023
🔧 Action : search_web({'query': 'PIB France 2023'})
📊 Résultat : Résultats de recherche pour 'PIB France 2023': Le PIB de la France en 2023 est estimé à 2 950 milliards d'euros...
============================================================
🔄 Itération 2/10
============================================================
💭 Pensée : Maintenant je dois calculer le PIB par habitant : 2950000000000 / 67000000
🔧 Action : calculate({'expression': '2950000000000 / 67000000'})
📊 Résultat : Résultat : 44029.85
============================================================
🔄 Itération 3/10
============================================================
✅ Réponse finale : Le PIB de la France en 2023 est d'environ 2 950 milliards d'euros. Avec 67 millions d'habitants, cela fait environ 44 030 € par habitant.
Patterns d’architecture avancés
Au-delà de l’architecture de base, plusieurs patterns avancés permettent de gérer des cas d’usage complexes. Chaque pattern répond à des besoins spécifiques et présente des trade-offs différents en termes de performance, coût et complexité.
Comparaison des patterns architecturaux
| Pattern | Cas d’usage idéal | Avantages | Inconvénients | Coût LLM |
|---|---|---|---|---|
| Simple (ReAct) | Tâches linéaires, recherche web, Q&A | Simple, rapide, facile à déboguer | Limité pour tâches complexes | Faible |
| Hiérarchique | Tâches multi-domaines (recherche + analyse + synthèse) | Spécialisation, parallélisation possible | Complexité de coordination | Moyen-Élevé |
| Plan-and-Execute | Tâches avec étapes claires (analyses, rapports) | Prévisible, optimisé | Rigide, difficulté avec imprévu | Moyen |
| Self-Reflection | Tâches critiques nécessitant qualité maximale | Haute qualité, auto-correction | Lent, coûteux en appels LLM | Très Élevé |
| Collaborative Multi-Agent | Problèmes nécessitant expertises multiples | Créativité, robustesse | Très complexe, difficile à contrôler | Très Élevé |
Pattern recommandé : L’architecture hiérarchique avec un superviseur est idéale pour des tâches complexes. Le superviseur décompose le problème et délègue à des agents spécialisés. Utilisé par AutoGPT, BabyAGI et les systèmes multi-agents de LangChain.
Agent hiérarchique
class HierarchicalAgent(Agent):
"""Agent avec délégation à des sous-agents"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sub_agents = {}
def register_sub_agent(self, name: str, agent: Agent):
"""Enregistre un sous-agent spécialisé"""
self.sub_agents[name] = agent
def delegate(self, task: str, sub_agent_name: str) -> str:
"""Délègue une tâche à un sous-agent"""
if sub_agent_name not in self.sub_agents:
return f"Sous-agent {sub_agent_name} introuvable"
return self.sub_agents[sub_agent_name].run(task)
# Exemple
supervisor = HierarchicalAgent(reasoning_strategy=ReActReasoning())
# Agents spécialisés
data_agent = Agent(reasoning_strategy=ReActReasoning())
data_agent.action.register_tool(SQLTool())
research_agent = Agent(reasoning_strategy=ReActReasoning())
research_agent.action.register_tool(WebSearchTool())
# Enregistrement
supervisor.register_sub_agent("data", data_agent)
supervisor.register_sub_agent("research", research_agent)
Agent avec Plan-and-Execute
class PlanAndExecuteAgent(Agent):
"""Agent qui planifie d'abord, puis exécute"""
def run(self, query: str) -> str:
# 1. Planification : Décomposer en sous-tâches
plan = self._create_plan(query)
print(f"📋 Plan créé : {len(plan)} étapes")
results = []
# 2. Exécution : Traiter chaque étape
for i, step in enumerate(plan):
print(f"\n▶️ Étape {i+1}/{len(plan)}: {step}")
step_result = super().run(step)
results.append(step_result)
# 3. Synthèse finale
return self._synthesize(query, results)
def _create_plan(self, query: str) -> List[str]:
"""Utilise le LLM pour créer un plan"""
response = openai.chat.completions.create(
model="gpt-4",
messages=[{
"role": "user",
"content": f"""Décompose cette tâche en étapes simples et séquentielles :
{query}
Réponds uniquement avec une liste numérotée."""
}]
)
plan_text = response.choices[0].message.content
# Parser la liste
steps = [line.strip() for line in plan_text.split('\n') if line.strip()]
return steps
def _synthesize(self, original_query: str, step_results: List[str]) -> str:
"""Synthétise les résultats de toutes les étapes"""
synthesis_prompt = f"""Requête originale : {original_query}
Résultats des étapes :
{chr(10).join(f'{i+1}. {r}' for i, r in enumerate(step_results))}
Fournis une réponse finale synthétique."""
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": synthesis_prompt}]
)
return response.choices[0].message.content
Agent avec self-reflection
class ReflectiveAgent(Agent):
"""Agent qui critique et améliore ses propres résultats"""
def run(self, query: str, max_reflections: int = 3) -> str:
current_answer = super().run(query)
for i in range(max_reflections):
# Auto-critique
critique = self._critique(query, current_answer)
if critique["is_satisfactory"]:
print(f"✅ Réponse satisfaisante après {i} réflexions")
break
print(f"\n🔍 Réflexion {i+1} : {critique['issues']}")
# Amélioration
improved_answer = self._improve(query, current_answer, critique)
current_answer = improved_answer
return current_answer
def _critique(self, query: str, answer: str) -> Dict:
"""Critique la réponse actuelle"""
response = openai.chat.completions.create(
model="gpt-4",
messages=[{
"role": "user",
"content": f"""Évalue cette réponse :
Question : {query}
Réponse : {answer}
La réponse est-elle :
1. Correcte factuellement ?
2. Complète ?
3. Bien structurée ?
Réponds en JSON : {{"is_satisfactory": bool, "issues": str}}"""
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
def _improve(self, query: str, current: str, critique: Dict) -> str:
"""Améliore la réponse basée sur la critique"""
response = openai.chat.completions.create(
model="gpt-4",
messages=[{
"role": "user",
"content": f"""Améliore cette réponse :
Question : {query}
Réponse actuelle : {current}
Problèmes identifiés : {critique['issues']}
Fournis une réponse améliorée."""
}]
)
return response.choices[0].message.content
Optimisations de performance
Caching des décisions
from functools import lru_cache
import hashlib
class CachedAgent(Agent):
"""Agent avec cache des décisions récurrentes"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.decision_cache = {}
def run(self, query: str) -> str:
# Créer une clé de cache
cache_key = hashlib.md5(query.encode()).hexdigest()
if cache_key in self.decision_cache:
print("⚡ Réponse en cache")
return self.decision_cache[cache_key]
result = super().run(query)
self.decision_cache[cache_key] = result
return result
Exécution parallèle d’outils
import asyncio
from typing import List
class AsyncActionModule(ActionModule):
"""Module d'action avec exécution parallèle"""
async def execute_parallel(self, actions: List[Dict]) -> List[Observation]:
"""Exécute plusieurs actions en parallèle"""
tasks = [self._execute_async(action) for action in actions]
return await asyncio.gather(*tasks)
async def _execute_async(self, action: Dict) -> Observation:
"""Version asynchrone de execute()"""
# Simuler une opération async
await asyncio.sleep(0.1)
return self.execute(action)
# Exemple d'utilisation
async def main():
agent = Agent(reasoning_strategy=ReActReasoning())
agent.action = AsyncActionModule()
actions = [
{"name": "search_web", "arguments": {"query": "météo Paris"}},
{"name": "search_web", "arguments": {"query": "météo Lyon"}},
{"name": "search_web", "arguments": {"query": "météo Marseille"}}
]
# Exécution parallèle = 3x plus rapide
results = await agent.action.execute_parallel(actions)
Streaming des résultats
class StreamingAgent(Agent):
"""Agent qui stream ses résultats en temps réel"""
def run_stream(self, query: str):
"""Version streaming de run()"""
observations = [self.perception.observe(query)]
for iteration in range(self.max_iterations):
decision = self.reasoning.think(observations, self.memory)
# Stream la pensée
yield {"type": "thought", "content": decision["thought"]}
if decision["finish"]:
yield {"type": "final", "content": decision["thought"]}
break
if decision["action"]:
# Stream l'action
yield {"type": "action", "content": decision["action"]}
result = self.action.execute(decision["action"])
observations.append(result)
# Stream le résultat
yield {"type": "observation", "content": result.content}
# Utilisation
agent = StreamingAgent(reasoning_strategy=ReActReasoning())
for event in agent.run_stream("Quelle est la météo à Paris ?"):
if event["type"] == "thought":
print(f"💭 {event['content']}")
elif event["type"] == "action":
print(f"🔧 {event['content']}")
elif event["type"] == "observation":
print(f"📊 {event['content']}")
elif event["type"] == "final":
print(f"✅ {event['content']}")
Gestion des erreurs et robustesse
Production-ready : Un agent production doit gérer les erreurs gracieusement avec retry automatique, fallbacks et timeouts. Sans cela, une API down peut bloquer tout votre système.
class RobustAgent(Agent):
"""Agent avec gestion avancée des erreurs"""
def __init__(self, *args, retry_on_error=True, **kwargs):
super().__init__(*args, **kwargs)
self.retry_on_error = retry_on_error
self.error_count = 0
self.max_errors = 3
def run(self, query: str) -> str:
try:
return self._run_with_fallback(query)
except Exception as e:
return self._handle_critical_error(e)
def _run_with_fallback(self, query: str) -> str:
"""Exécution avec fallback sur erreur"""
try:
return super().run(query)
except ToolExecutionError as e:
if self.retry_on_error and self.error_count < self.max_errors:
self.error_count += 1
print(f"⚠️ Erreur outil, retry {self.error_count}/{self.max_errors}")
return self._run_with_fallback(query)
else:
# Fallback : réponse sans outils
return self._generate_fallback_response(query)
def _handle_critical_error(self, error: Exception) -> str:
"""Gestion des erreurs critiques"""
return f"Désolé, une erreur s'est produite : {str(error)}. Veuillez réessayer."
def _generate_fallback_response(self, query: str) -> str:
"""Génère une réponse sans utiliser d'outils"""
response = openai.chat.completions.create(
model="gpt-4",
messages=[{
"role": "user",
"content": f"{query}\n\nRéponds sans utiliser d'outils externes."
}]
)
return response.choices[0].message.content
Métriques et observabilité
from dataclasses import dataclass
from datetime import datetime
import time
@dataclass
class AgentMetrics:
"""Métriques d'exécution d'un agent"""
iterations: int
tools_called: int
total_duration_s: float
llm_calls: int
tokens_used: int
success: bool
error_message: Optional[str] = None
class ObservableAgent(Agent):
"""Agent avec collecte de métriques"""
def run(self, query: str) -> str:
start_time = time.time()
metrics = AgentMetrics(
iterations=0,
tools_called=0,
total_duration_s=0.0,
llm_calls=0,
tokens_used=0,
success=False
)
try:
result = super().run(query)
metrics.success = True
return result
except Exception as e:
metrics.error_message = str(e)
raise
finally:
metrics.total_duration_s = time.time() - start_time
self._log_metrics(metrics)
def _log_metrics(self, metrics: AgentMetrics):
"""Log les métriques (vers un système de monitoring)"""
print(f"\n📊 Métriques d'exécution:")
print(f" - Itérations : {metrics.iterations}")
print(f" - Outils appelés : {metrics.tools_called}")
print(f" - Durée : {metrics.total_duration_s:.2f}s")
print(f" - Appels LLM : {metrics.llm_calls}")
print(f" - Succès : {metrics.success}")
Exercices pratiques
Implémenter un nouvel outil
Créez un outil TranslationTool qui utilise l’API de traduction de votre choix :
class TranslationTool(Tool):
@property
def name(self) -> str:
return "translate"
def execute(self, text: str, target_language: str) -> str:
# TODO: Implémenter
pass
Mémoire vectorielle
Ajoutez une mémoire vectorielle à la classe Memory pour stocker et récupérer des faits par similarité sémantique.
Agent multi-modal
Créez un agent capable de traiter des images en ajoutant un ImagePerceptionModule.
Best practices de production
Construire un agent qui fonctionne en démo est une chose. Le faire tourner en production de manière fiable en est une autre. Voici les principes essentiels :
Gestion des timeouts et limites
Problème : Un outil bugué peut bloquer indéfiniment votre agent.
Solution : Imposez des timeouts stricts à tous les niveaux :
# Timeout par outil (3 secondes max)
@timeout(3)
def execute_tool(tool, args):
return tool.execute(**args)
# Timeout global de l'agent (30 secondes max)
@timeout(30)
def agent.run(query):
# ...
Limites recommandées :
- Timeout par outil : 2-5 secondes
- Timeout global agent : 30-60 secondes
- Max itérations : 10-15
- Max appels LLM consécutifs : 5
Validation des entrées/sorties
Tous les outils doivent valider leurs entrées ET leurs sorties :
class SafeTool(Tool):
def execute(self, **kwargs):
# 1. Valider entrées
self._validate_inputs(kwargs)
# 2. Exécuter
result = self._execute(**kwargs)
# 3. Valider sortie
self._validate_output(result)
return result
Rate limiting et gestion des coûts
Problème : Un agent qui boucle peut générer des coûts LLM astronomiques.
Solutions :
- Circuit breaker : Arrête l’agent si >X appels/minute
- Budget par conversation : Limite le coût total d’une session
- Retry exponentiel : Attend de plus en plus longtemps entre retries
- Caching intelligent : Stocke les résultats d’appels identiques
class RateLimitedAgent(Agent):
def __init__(self, *args, max_cost_usd=0.50, **kwargs):
super().__init__(*args, **kwargs)
self.max_cost = max_cost_usd
self.current_cost = 0.0
def run(self, query):
if self.current_cost >= self.max_cost:
raise BudgetExceededError(
f"Budget dépassé : ${self.current_cost:.2f}"
)
return super().run(query)
Logging et traçabilité
Chaque action doit être loggée avec un ID de trace unique pour faciliter le débogage :
import uuid
import logging
class TracedAgent(Agent):
def run(self, query):
trace_id = str(uuid.uuid4())
logger = logging.getLogger(f"agent.{trace_id}")
logger.info(f"[START] Query: {query}")
try:
result = super().run(query)
logger.info(f"[SUCCESS] Result: {result}")
return result
except Exception as e:
logger.error(f"[ERROR] {str(e)}", exc_info=True)
raise
Fallbacks et dégradation gracieuse
Définissez toujours un comportement de secours :
Niveau 1 : Outil principal échoue → Essayer outil alternatif Niveau 2 : Tous les outils échouent → Réponse LLM sans outils Niveau 3 : LLM échoue → Réponse pré-définie d’erreur
Erreur courante : Ne jamais retourner d’erreur brute à l’utilisateur. Un agent production doit TOUJOURS retourner une réponse formatée, même en cas d’échec total. Message recommandé : ‘Je rencontre des difficultés techniques. Veuillez réessayer dans quelques instants.’
Monitoring et alertes
Surveillez ces métriques en temps réel :
- Taux de succès : % de requêtes complétées sans erreur (cible : >95%)
- Latence P50/P95/P99 : Temps de réponse (cible P95 : <5s)
- Coût moyen par requête : Budget LLM ($0.01-$0.10 selon complexité)
- Taux d’erreur par outil : Identifier outils défaillants
- Distribution des itérations : Détecter agents qui bouclent
Alertes critiques :
- Taux d’erreur >10% pendant 5 minutes → Page l’équipe
- Latence P95 >10s → Investigation requise
- Coût moyen >$0.50 → Agent potentiellement en boucle
Cas d’usage réels et architectures adaptées
Customer support agent (Zendesk, Intercom)
Architecture : Simple ReAct avec 4-6 outils
Outils :
search_knowledge_base: Recherche dans la docget_user_info: Récupère infos client (CRM)check_order_status: Statut commandecreate_ticket: Escalade vers humain
Métriques de succès :
- Résolution sans escalade : 60-70%
- Temps de réponse : <3s
- Satisfaction utilisateur : >4.2/5
Coût : $0.02-0.05 par conversation
Data analysis agent (Business Intelligence)
Architecture : Plan-and-Execute + Self-Reflection
Outils :
execute_sql: Requêtes sur data warehousegenerate_chart: Visualisations (matplotlib, plotly)statistical_test: Tests statistiques
Workflow :
- Planification : Décomposer la question analytique
- Exécution : Requêtes SQL + calculs
- Réflexion : Vérifier cohérence des résultats
- Génération : Rapport avec graphiques
Métriques :
- Exactitude des analyses : 85-90%
- Temps moyen : 15-30s
- Coût : $0.10-0.30 par analyse
Code review agent (GitHub, GitLab)
Architecture : Hiérarchique avec 3 sous-agents
Sous-agents :
- Security Agent : Détecte vulnérabilités (SQL injection, XSS)
- Quality Agent : Code smells, complexité, duplication
- Performance Agent : Optimisations possibles
Workflow :
- Superviseur répartit le code entre sous-agents
- Chaque agent analyse sa partie
- Superviseur agrège et priorise les feedbacks
Métriques :
- Vrais positifs : 70-80% (vs 40-50% pour linters)
- Faux positifs : <15%
- Temps review : 2-5 min pour 500 lignes
Research assistant (Perplexity-style)
Architecture : ReAct avec mémoire vectorielle longue
Outils :
web_search: Recherche multi-sources (Google, Bing, Scholar)fetch_url: Récupère contenu pageextract_citations: Identifie sources citablesvector_store: Stocke/récupère faits par similarité
Particularités :
- Mémoire vectorielle pour éviter recherches redondantes
- Citations systématiques avec URLs
- Fact-checking multi-sources
Métriques :
- Exactitude factuelle : 85-92% (validé manuellement)
- Citations pertinentes : >90%
- Coût : $0.08-0.15 par recherche approfondie
Prochaines étapes
Vous maîtrisez maintenant l’architecture fondamentale des agents IA. Dans le prochain article, nous plongerons en profondeur dans le pattern ReAct (Reasoning + Acting) qui alimente les agents les plus performants actuels.
Ce que vous allez apprendre :
- Implémentation détaillée de ReAct avec prompts optimisés
- Comparaison avec Chain-of-Thought, Plan-and-Execute et Reflexion
- Techniques d’optimisation pour réduire coûts et latence
- Gestion avancée des boucles et des erreurs
- Intégration avec LangChain et frameworks modernes
Points clés à retenir de cet article :
✅ Architecture modulaire : Séparation Perception/Reasoning/Action/Memory pour testabilité et maintenabilité ✅ Orchestrateur central : Gère la boucle principale et coordonne les modules ✅ Stratégies interchangeables : Différents patterns de raisonnement selon le cas d’usage ✅ Robustesse production : Timeouts, validation, rate limiting, monitoring indispensables ✅ Patterns avancés : Hiérarchique, Plan-and-Execute, Self-Reflection pour tâches complexes ✅ Observabilité critique : Logs, traces, métriques pour debug et optimisation
Ressources et liens
Articles connexes de la série
Fondamentaux :
- Introduction aux Agents IA - Concepts de base et premier agent
- Pattern ReAct - Raisonnement et action combinés
- Function Calling - Intégration d’outils
- Mémoire des Agents - Gestion du contexte long-terme
Frameworks et implémentation :
- LangChain pour Agents - Framework Python pour agents
- Systèmes Multi-Agents - Coordination d’agents multiples
- AutoGPT et BabyAGI - Agents autonomes avancés
Production :
- Évaluation et Testing - Mesurer performance d’agents
- Déploiement en Production - Mise en production robuste
Concepts IA connexes
- Transformers - Architecture sous-jacente aux LLM
- Function Calling - Appel de fonctions par LLMs
- RAG - Augmentation par récupération pour mémoire externe
- Prompt Engineering - Optimisation des prompts agents
Frameworks et outils
Python :
- LangChain - Framework complet pour agents
- AutoGPT - Agent autonome open-source
- CrewAI - Framework multi-agents collaboratifs
- Semantic Kernel - SDK Microsoft pour agents
JavaScript/TypeScript :
- LangChain.js - Port JavaScript
- Vercel AI SDK - SDK pour agents web
Documentation officielle
- OpenAI Assistants API - Agents natifs OpenAI
- Anthropic Claude Tools - Function calling Claude
- Google Gemini Function Calling - Outils Gemini
Navigation
← Retour à la série Agents IA | Module suivant : Pattern ReAct →