Collaboration multi-agents en pratique : guide de choix entre 4 architectures

Les recherches d’Anthropic montrent qu’un système multi-agents surpasse un mono-Agent de 90,2 %. Le mono-Agent a trois faiblesses fatales : limite de contexte (200k tokens saturés, puis sorties incohérentes), compétences dispersées (trop de skills = touche-à-tout), débogage difficile (un Agent en panne, impossible de localiser la source). Le système multi-agents, c’est l’« architecture microservices » de l’IA : chaque Agent ne fait qu’une chose, et collabore via un passage de messages clair.
Quatre architectures clés, chacune avec son cas d’usage : Subagents (orchestration centrale) pour plusieurs domaines indépendants en parallèle, Skills (chargement à la demande) pour un flux mono-thread multi-étapes, Handoffs (piloté par l’état) pour les dialogues multi-phases, Router (distribution parallèle) pour interroger plusieurs sources de données. Cet article couvre la logique de choix, les pièges en production et l’implémentation LangGraph — pour trancher entre Subagents et Skills, et gérer l’état entre Agents.
Pourquoi un système multi-agents
Franchement, le plus gros problème du mono-Agent n’est pas « est-ce utilisable », mais « combien de temps ça tient ».
Vous avez déjà vécu ça : un Agent écrivait du bon code, puis a commencé à produire n’importe quoi ? Ou vous demandiez A, et il partait sur B, puis C, jusqu’à Z ? Ce n’est pas qu’il soit « bête » — sa fenêtre de contexte est saturée.
Le mono-Agent a trois faiblesses fatales.
Limite de contexte. Même puissant, un Agent ne dispose que de 200k tokens de contexte. Demandez-lui revue de code, analyse de sécurité et optimisation des performances en même temps : retenir la moitié, c’est déjà optimiste. J’ai vu un Agent qui, après 20 tours sur Python, basculait en JavaScript au tour 21 — il avait oublié sa mission.
Compétences dispersées. Plus vous empilez de skills, plus il ressemble à un touche-à-tout — un peu de tout, expert en rien. Revue de code ? Il vous écrit des tests unitaires. Documentation ? Il refactorise le code au passage. Sens de l’orientation ? Inexistant.
Débogage difficile. Quand un Agent plante, impossible de savoir où ça coince. Prompt trop long ? Échec d’appel d’outil ? Pollution du contexte ? Chercher, c’est comme une aiguille dans une botte de foin.
Le système multi-agents, en clair, c’est l’architecture microservices de l’IA. Chaque Agent ne fait qu’une chose, et la fait bien. Ils collaborent via des messages clairs, au lieu de tout entasser dans un « super Agent ».
Google, LangChain et Anthropic poussent tous cette approche. Le rapport O’Reilly indique que les publications sur les Agents sont passées de 820 début 2025 à plus de 2 500 — triplées. Pourquoi ? Parce que l’ère du mono-Agent tout-puissant est révolue.
Quatre architectures clés
LangChain et Google ont formalisé les architectures multi-agents dominantes. Après plusieurs projets, chacune a son terrain — mal choisir, c’est tirer au canon sur une mouche ou manger de la soupe aux baguettes.
Subagents (sous-agents) — orchestration centrale
Le mode le plus intuitif : un « Agent principal » chef d’orchestre, entouré de « sous-agents ». Les sous-agents sont les outils du principal, qui décide quand en appeler lequel.
Requête utilisateur → Agent principal (coordinateur) → distribution aux sous-agents A/B/C → agrégation → retour utilisateur
Quand l’utiliser ? Tâches couvrant plusieurs domaines indépendants. Exemple service client : un sous-agent pour les commandes, un pour les remboursements, un pour les réclamations. Chaque domaine a sa base de connaissances et ses outils ; le principal ne fait que router.
Exemple de code (LangGraph) :
from langgraph.prebuilt import create_react_agent
# Définir les sous-agents
order_agent = create_react_agent(
model="claude-3-5-sonnet-20241022",
tools=[query_order, update_order],
prompt="Vous êtes expert commandes, vous ne traitez que les questions liées aux commandes."
)
refund_agent = create_react_agent(
model="claude-3-5-sonnet-20241022",
tools=[check_refund_policy, process_refund],
prompt="Vous êtes expert remboursements, vous ne traitez que les questions de remboursement."
)
# L'Agent principal détient les sous-agents comme outils
main_agent = create_react_agent(
model="claude-3-5-sonnet-20241022",
tools=[order_agent, refund_agent], # Les sous-agents sont des outils
prompt="Vous êtes le responsable du service client, distribuez les questions aux experts adaptés."
)
Avantages : isolation de contexte propre, chaque sous-agent ne voit que son périmètre. Exécution parallèle efficace.
Inconvénients : chaque sous-agent = un appel LLM indépendant, consommation de tokens élevée. Partager l’état entre sous-agents demande un détour.
Skills (compétences) — chargement à la demande
Un Agent, plusieurs « personnalités ». Les Skills sont essentiellement des modèles de prompts chargés dynamiquement. L’Agent change d’« identité » selon la tâche, mais reste le même Agent.
Requête utilisateur → Agent unique → chargement Skill « revue de code » → exécution → chargement Skill « génération doc » → exécution
Quand l’utiliser ? Traitement mono-thread, mais phases nécessitant des expertises différentes. Exemple assistant de programmation : mode « développeur » pour coder, mode « rédacteur technique » pour la doc.
Exemple de code :
# Structure du répertoire Skills
skills/
├── code_review.md # Prompt revue de code
├── doc_writer.md # Prompt génération doc
└── security_audit.md # Prompt audit sécurité
# Chargement dynamique d'un Skill
def load_skill(skill_name: str) -> str:
with open(f"skills/{skill_name}.md") as f:
return f.read()
# Exemple d'utilisation
agent = create_react_agent(
model="claude-3-5-sonnet-20241022",
tools=[...],
prompt=load_skill("code_review") # Bascule à l'exécution
)
Avantages : léger, pas de surcoût de coordination Agent. Consommation de tokens inférieure aux Subagents.
Inconvénients : le contexte s’accumule. Après 10 bascules de Skill, le contenu des 9 précédents reste dans la fenêtre — de plus en plus confus.
Handoffs (transferts) — piloté par l’état
Les Agents se passent la tâche comme un relais. L’Agent A termine, « jette » l’état à l’Agent B, qui continue. Comme un relais.
Requête utilisateur → Agent A (collecte) → transfert → Agent B (analyse) → transfert → Agent C (solution)
Quand l’utiliser ? Dialogues multi-phases. Exemple support technique : collecter le problème → diagnostiquer → proposer une solution → confirmer la résolution. Chaque phase demande une expertise différente.
Exemple de code :
from langchain_core.tools import tool
# Définir les outils de transfert
@tool
def handoff_to_diagnosis(issue_summary: str) -> str:
"""Transférer le problème à l'expert diagnostic."""
return f"Problème reçu : {issue_summary}, début du diagnostic..."
@tool
def handoff_to_solution(diagnosis_result: str) -> str:
"""Transférer le diagnostic à l'expert solution."""
return f"Sur la base du diagnostic : {diagnosis_result}, élaboration de la solution..."
# Chaîne d'Agents
triage_agent = create_react_agent(
tools=[handoff_to_diagnosis],
prompt="Vous êtes trieur, collectez le problème utilisateur et transférez à l'expert diagnostic."
)
diagnosis_agent = create_react_agent(
tools=[handoff_to_solution],
prompt="Vous êtes expert diagnostic, analysez la cause racine et transférez à l'expert solution."
)
Avantages : flux de dialogue naturel, proche de la collaboration humaine. Chaque Agent ne se concentre que sur la phase en cours.
Inconvénients : gestion d’état complexe. Le format des données passées de A à B doit être correct, sinon la chaîne casse.
Router (routage) — distribution parallèle
Un « Agent routeur » analyse la requête, appelle en parallèle plusieurs Agents spécialisés, puis synthétise les résultats.
Requête utilisateur → Router (classification) → appels parallèles Agents A/B/C → synthèse → retour utilisateur
Quand l’utiliser ? Une requête doit interroger plusieurs sources. Exemple Q&R base de connaissances d’entreprise : le Router classe la question, interroge en parallèle docs internes, API externe et base de données, puis fusionne la réponse.
Exemple de code :
from langgraph.graph import StateGraph
# Définir les nœuds d'exécution parallèle
async def query_internal_docs(state):
# Interroger la documentation interne
return {"internal_results": [...]}
async def query_external_api(state):
# Interroger l'API externe
return {"external_results": [...]}
async def query_database(state):
# Interroger la base de données
return {"db_results": [...]}
async def synthesize(state):
# Synthétiser tous les résultats
all_results = state["internal_results"] + state["external_results"] + state["db_results"]
return {"final_answer": summarize(all_results)}
# Construire le graphe parallèle
graph = StateGraph(State)
graph.add_node("internal", query_internal_docs)
graph.add_node("external", query_external_api)
graph.add_node("database", query_database)
graph.add_node("synthesize", synthesize)
# Exécution parallèle
graph.add_edge("router", ["internal", "external", "database"])
graph.add_edge(["internal", "external", "database"], "synthesize")
Avantages : exécution parallèle, vitesse maximale. Sans état, chaque requête est indépendante.
Inconvénients : peu adapté au dialogue multi-tours. Chaque requête repart de zéro — l’Agent n’a pas mémoire du tour précédent.
Cadre de décision pour choisir l’architecture
Bon, lequel choisir ? Voici un flux de décision simplifié :
Quel est votre besoin ?
│
├─→ Plusieurs domaines indépendants en parallèle ?
│ │
│ └─→ Subagents (orchestration centrale)
│
├─→ Mono-Agent, bascule de skills multi-étapes ?
│ │
│ └─→ Skills (chargement à la demande)
│
├─→ Workflow séquentiel, relais étape par étape ?
│ │
│ └─→ Handoffs (piloté par l'état)
│
└─→ Requête multi-sources à synthétiser ?
│
└─→ Router (distribution parallèle)
Le flux seul peut manquer de clarté — voici un tableau comparatif :
| Mode | Dev distribué | Parallélisation | Dialogue multi-sauts | Interaction directe utilisateur | Consommation tokens |
|---|---|---|---|---|---|
| Subagents | Élevé | Élevé | Élevé | Faible | Élevé |
| Skills | Élevé | Moyen | Élevé | Élevé | Faible |
| Handoffs | Non | Non | Élevé | Élevé | Moyen |
| Router | Moyen | Élevé | Non | Moyen | Élevé |
Comment lire ce tableau ?
- Dev distribué : l’équipe développe-t-elle des modules séparés ? Subagents et Skills conviennent — chacun prend un sous-Agent ou un Skill.
- Parallélisation : la vitesse compte ? Router et Subagents exécutent plusieurs Agents en parallèle, le plus efficace.
- Dialogue multi-sauts : l’utilisateur a-t-il besoin de plusieurs tours ? Handoffs et Skills supportent naturellement le flux conversationnel.
- Interaction directe utilisateur : l’utilisateur parle-t-il directement aux sous-Agents ? Skills et Handoffs oui, Router non.
- Consommation tokens : sensible au coût ? Skills est le plus économique, Router et Subagents les plus coûteux.
Mon expérience : partir simple. Validez d’abord un MVP avec Skills ou Handoffs ; si vous touchez un plafond, passez à Subagents ou Router. Ne démarrez pas en architecture distribuée — la sur-ingénierie, je connais.
Points clés pour la production
Entre la démo et la production, il y a un océan. Voici les pièges que j’ai tous frôlés.
Gestion d’état
Quand plusieurs Agents partagent un état, le problème classique est la condition de course — deux Agents écrivent la même variable, qui écrase qui ?
La solution LangGraph : output_key — chaque Agent n’écrit que dans sa clé dédiée.
from langgraph.graph import StateGraph, MessagesState
class GraphState(MessagesState):
security_result: str = "" # Réservé à l'Agent sécurité
style_result: str = "" # Réservé à l'Agent style
perf_result: str = "" # Réservé à l'Agent performance
# L'Agent sécurité n'écrit que security_result
async def security_agent(state: GraphState):
result = await analyze_security(state["messages"])
return {"security_result": result} # Une seule clé
# L'Agent style n'écrit que style_result
async def style_agent(state: GraphState):
result = await analyze_style(state["messages"])
return {"style_result": result}
Ainsi, en parallèle ou en série, chaque Agent ne touche qu’à son périmètre — sans interférence.
Autre problème fréquent : pollution du contexte. La sortie de l’Agent A est lue par B, qui n’en a pas besoin. Ma pratique : ajouter un champ relevant_keys dans l’état, chaque Agent ne lit que les clés utiles.
Optimisation des performances
La consommation de tokens en multi-agents, c’est un gouffre. Quelques astuces :
1. Subagents économise 67 % de tokens vs Skills (scénario multi-domaines)
Données de test LangChain : pour une tâche couvrant 3 domaines indépendants, Subagents consomme un tiers des tokens de Skills. Pourquoi ? Isolation de contexte — chaque sous-agent ne voit que son domaine. Avec Skills, tout le contexte des Skills s’accumule.
2. Mode avec état : 40-50 % d’appels en moins sur les requêtes répétées
Si la tâche comporte beaucoup de requêtes identiques (même question 10 fois), Handoffs avec état permet à l’Agent de mémoriser. Données LangChain : avec état vs sans état, près de la moitié d’appels LLM en moins.
3. Limiter les itérations en mode réflexion
Beaucoup ajoutent une « réflexion » — l’Agent vérifie sa sortie, corrige, regénère. Utile, mais risque de boucle infinie. Je limite généralement à max_iterations=2 ou 3, puis sortie forcée.
from langgraph.checkpoint.memory import MemorySaver
# Plafond d'itérations
graph = create_react_agent(
model="claude-3-5-sonnet-20241022",
tools=[...],
checkpointer=MemorySaver(),
config={"configurable": {"max_iterations": 3}} # Max 3 réflexions
)
Pièges courants
Boucle infinie : l’Agent s’appelle lui-même, encore et encore. Solution : max_iterations et conditions de sortie explicites.
def should_continue(state):
if state["iteration_count"] >= 3:
return "end"
if "done" in state["messages"][-1].content:
return "end"
return "continue"
Gonflement du contexte : l’Agent devient « bête », sorties de plus en plus courtes — contexte surchargé. Solution : mode Blackboard (tableau partagé), ne garder que le contexte nécessaire, nettoyage périodique.
Taxe de coordination : plus d’Agents, surcoût de communication exponentiel. Test perso : de 3 à 10 Agents, le temps de réponse passe de 2 s à 15 s. Solution : fusionner les Agents proches, rester sous 5 Agents.
Exemple d’implémentation complet
Assez de théorie — voici du concret. J’ai monté un système multi-agents de revue de code en mode Router + ParallelAgent.
Architecture : Router détecte langage et type → appels parallèles audit sécurité, style et performance → synthèse en rapport.
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langchain_anthropic import ChatAnthropic
# Définir l'état
class CodeReviewState(TypedDict):
code: str
language: str
security_issues: list
style_issues: list
perf_issues: list
final_report: str
# Initialiser le LLM
llm = ChatAnthropic(model="claude-3-5-sonnet-20241022")
# Router : détecter le langage
async def route_code(state: CodeReviewState) -> dict:
code = state["code"]
# Détection simple ; en prod, classification LLM
if "def " in code or "import " in code:
language = "python"
elif "function" in code or "const " in code:
language = "javascript"
else:
language = "unknown"
return {"language": language}
# Agent audit sécurité
async def security_audit(state: CodeReviewState) -> dict:
code = state["code"]
prompt = f"""Vous êtes expert audit sécurité. Vérifiez le code suivant :
- Risque injection SQL
- Vulnérabilités XSS
- Fuite d'informations sensibles
- Dépendances non sécurisées
Code :
{code}
Sortie en liste JSON, chaque item : line (numéro), severity (gravité), description.
"""
response = await llm.ainvoke(prompt)
# Parser le résultat...
return {"security_issues": []}
# Agent contrôle de style
async def style_check(state: CodeReviewState) -> dict:
code = state["code"]
language = state["language"]
prompt = f"""Vous êtes expert style de code. Vérifiez ce code {language} :
- Conventions de nommage
- Formatage
- Complétude des commentaires
Code :
{code}
Sortie en liste JSON.
"""
response = await llm.ainvoke(prompt)
return {"style_issues": []}
# Agent analyse performance
async def perf_analysis(state: CodeReviewState) -> dict:
code = state["code"]
prompt = f"""Vous êtes expert performance. Vérifiez le code :
- Complexité temporelle excessive
- Boucles inutiles
- Risque fuite mémoire
Code :
{code}
Sortie en liste JSON.
"""
response = await llm.ainvoke(prompt)
return {"perf_issues": []}
# Rapport synthétique
async def generate_report(state: CodeReviewState) -> dict:
security = state.get("security_issues", [])
style = state.get("style_issues", [])
perf = state.get("perf_issues", [])
total_issues = len(security) + len(style) + len(perf)
report = f"""# Rapport de revue de code
## Vue d'ensemble
- Langage : {state['language']}
- Total problèmes : {total_issues}
## Sécurité ({len(security)})
{format_issues(security)}
## Style ({len(style)})
{format_issues(style)}
## Performance ({len(perf)})
{format_issues(perf)}
## Recommandations
Prioriser la correction des problèmes de sécurité...
"""
return {"final_report": report}
# Construire le graphe
graph = StateGraph(CodeReviewState)
graph.add_node("router", route_code)
graph.add_node("security", security_audit)
graph.add_node("style", style_check)
graph.add_node("perf", perf_analysis)
graph.add_node("report", generate_report)
# Flux : Router → trois contrôles en parallèle → rapport
graph.set_entry_point("router")
graph.add_edge("router", "security")
graph.add_edge("router", "style")
graph.add_edge("router", "perf")
graph.add_edge("security", "report")
graph.add_edge("style", "report")
graph.add_edge("perf", "report")
graph.add_edge("report", END)
# Compiler
app = graph.compile()
# Utilisation
async def review_code(code: str):
result = await app.ainvoke({"code": code})
return result["final_report"]
En pratique, ~100 lignes de code, trois Agents en parallèle : résultat en 3-5 secondes. En série, au moins 10 secondes.
C’est une version de base. En production, ajoutez : cache (même code non ré-analysé), revue incrémentale (seulement les changements), retour humain (marquer les faux positifs). Toutes ces extensions s’appuient sur cette architecture.
Conclusion
En résumé, trois idées :
Logique de base : le choix du mode compte plus que le framework. LangGraph, AutoGen, CrewAI sont de bons outils — mais si vous appliquez Router à un problème qui demande Handoffs, aucun framework ne vous sauvera.
Stratégie intermédiaire : partir simple, monter en puissance. Validez d’abord un MVP Skills ou Handoffs ; si vous touchez un plafond, passez à Subagents ou Router. La sur-ingénierie est le plus gros piège — je l’ai vécu, évitez-le.
Mise en production : état, performance et coût. Tokens, boucles infinies, pollution du contexte — maîtrisez ces trois points, et votre système multi-agents tournera de façon stable.
Prochaine étape : ouvrez la doc LangGraph, choisissez un mode, implémentez le système multi-agents le plus simple en 50 lignes. Ne réfléchissez pas trop — lancez-vous.
Construire un système multi-agents
Monter un système multi-agents de revue de code from scratch
- 1
Step 1: Choisir l'architecture
Sélectionner le mode adapté aux caractéristiques de la tâche - 2
Step 2: Définir la structure d'état
Utiliser TypedDict pour l'état partagé entre Agents - 3
Step 3: Créer les nœuds Agent
Une fonction nœud indépendante par Agent - 4
Step 4: Construire le graphe d'exécution
Assembler le flux avec LangGraph StateGraph - 5
Step 5: Ajouter la gestion d'état
Utiliser output_key pour éviter les conditions de course
FAQ
Quelle différence entre Subagents et Skills ?
Quand utiliser le mode Handoffs ?
Comment éviter les conditions de course en multi-agents ?
Comment maîtriser la consommation de tokens en multi-agents ?
Quels cas d'usage pour le mode Router ?
Comment empêcher une boucle infinie Agent ?
13 min de lecture · Publié le: 25 mars 2026 · Mis à jour le: 27 juil. 2026
Guide d'ingénierie AI Agent
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Computer-Use Agent : laisser l'IA piloter votre ordinateur
Guide complet sur Claude Computer Use, de la théorie à la pratique : déploiement Docker, exemples de code, analyse concurrentielle et bonnes pratiques de sécurité pour maîtriser l'automatisation de bureau par l'IA
Partie 6 sur 16
Suivant
Conception de la chaîne d'outils Agent IA : guide d'évolution d'un outil unique vers un écosystème
Guide complet sur la conception de chaîne d'outils Agent IA : protocole MCP, comparaison LangChain, CrewAI, AutoGen et bonnes pratiques de déploiement en entreprise pour un écosystème d'outils extensible
Partie 8 sur 16



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire