Pourquoi un seul agent LLM casse à l'échelle production
L'agent monolithique — un LLM pour retrieval, raisonnement, code et validation — se démo facilement et se casse en production. Les problèmes sont structurels. En 2026, la plupart des équipes qui ont tenté de scaler un seul agent ont heurté les mêmes quatre murs.
Plafond de contexte : L'état intermédiaire remplit la fenêtre ; la qualité du raisonnement chute sur les workflows longs.
Dilution jack-of-all-trades : Un agent qui fait retrieval, génération et audit ne fait rien de tout cela correctement.
Pas de concurrence : Étapes séquentielles = latence totale = somme de chaque étape.
Point de défaillance unique : Un mauvais appel modèle ou timeout outil stoppe toute la pipeline.
Preuves terrain : L'Agent Bake-Off interne Google (MLflow 2026) a réduit le traitement d'une heure à dix minutes — gain ×6 après décomposition. AdaptOrch (2026) : la topologie d'orchestration bat le choix du modèle, +12–23 % sur SWE-bench.
Un système multi-agents (MAS) regroupe des agents indépendants qui collaborent via protocoles et orchestration définis pour accomplir ce qu'un seul agent ne peut pas faire efficacement. Chaque agent doit avoir une responsabilité unique, ses outils, un état isolé et être remplaçable indépendamment.
| Propriété | Signification en production |
|---|---|
| Responsabilité unique | Un rôle clair : retrieval, raisonnement, génération ou validation |
| Outils dédiés | Accès aux outils nécessaires via MCP Server |
| État isolé | Contexte et mémoire propres ; pas de pollution croisée |
| Remplaçable | Échanger un worker sans recâbler tout le graphe |
Trois topologies de contrôle : Centralisée — orchestrateur unique (auditable, goulot). Décentralisée — réseau peer-to-peer (résiliente, difficile à déboguer). Hiérarchique — orchestrateur → team leads → workers (équilibre contrôle/échelle).
| Mode | Structure | Avantages | Inconvénients |
|---|---|---|---|
| Centralisé | Orchestrateur pilote A/B/C | Auditable, contrôlable | Goulot central |
| Décentralisé | Mesh peer-to-peer | Haute élasticité, faible latence | Debug difficile, non déterministe |
| Hiérarchique | Orchestrateur → Team Lead → Worker | Contrôle et échelle équilibrés | Complexité de design moyenne |
En production, l'architecture multi-agents est presque toujours le bon choix. La vraie question est le pattern d'orchestration — pas le modèle de fondation.
LangGraph vs CrewAI vs AutoGen : comparatif et choix
Le bon framework évite des mois de code d'orchestration maison. Matrice : paradigme, langages, courbe d'apprentissage, état, HITL, observabilité, production, prototypage, Azure et cas d'usage.
| Dimension | LangGraph | CrewAI | AutoGen (Microsoft) |
|---|---|---|---|
| Paradigme | Graphe d'états | Équipes par rôles | Multi-agents conversationnels |
| Langages | Python / JS/TS | Python | Python / .NET |
| Courbe d'apprentissage | Raide | Douce | Moyenne |
| État | Natif | À implémenter | Limité |
| Human-in-the-Loop | Natif interrupt() | À implémenter | Pris en charge |
| Observabilité | LangSmith | Limité | Azure Monitor |
| Maturité prod | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Prototype rapide | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Azure | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Idéal pour | Workflows stateful complexes | Pipelines de contenu par rôles | Collaboration et débat |
Recommandations :
LangGraph :Fiabilité production (finance, santé, conformité), persistance d'état, HITL fin, branches conditionnelles.
CrewAI :Prototype en 1–2 jours ; équipe qui pense en rôles ; contenu et rapports.
AutoGen :Stack Microsoft/Azure ; débat multi-tours ; recherche et modes dialogués.
La topologie d'orchestration compte plus que le modèle — d'abord le pattern et le framework, puis le modèle.
Six modèles d'orchestration et protocoles MCP+A2A
Ces six modèles couvrent plus de 95 % des déploiements. Modèle 1 — Pipeline séquentielle : sortie de A → entrée de B ; linéaire ; idéal pour contenu, revue de code.
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class PipelineState(TypedDict):
query: str
retrieved_docs: str
analysis: str
final_report: str
def retrieval_agent(state: PipelineState):
docs = search_knowledge_base(state["query"])
return {"retrieved_docs": docs}
def analysis_agent(state: PipelineState):
result = llm.invoke(f"Analyze the following:{state['retrieved_docs']}")
return {"analysis": result.content}
def writer_agent(state: PipelineState):
report = llm.invoke(f"Write report from analysis:{state['analysis']}")
return {"final_report": report.content}
builder = StateGraph(PipelineState)
builder.add_node("retriever", retrieval_agent)
builder.add_node("analyzer", analysis_agent)
builder.add_node("writer", writer_agent)
builder.add_edge(START, "retriever")
builder.add_edge("retriever", "analyzer")
builder.add_edge("analyzer", "writer")
builder.add_edge("writer", END)
pipeline = builder.compile()
Modèle 2 — Fan-out/fan-in parallèle : sous-tâches indépendantes ; latence = max(T1…Tn). Send API LangGraph + reducer Annotated[list, operator.add].
from langgraph.types import Send
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
class ResearchState(TypedDict):
query: str
research_results: Annotated[list, operator.add]
final_synthesis: str
def supervisor(state: ResearchState):
subtasks = [
{"query": state["query"], "source": "academic"},
{"query": state["query"], "source": "industry"},
{"query": state["query"], "source": "news"},
]
return [Send("research_worker", task) for task in subtasks]
def research_worker(state: dict):
result = search_by_source(state["query"], state["source"])
return {"research_results": [result]}
def synthesizer(state: ResearchState):
combined = "\n".join(state["research_results"])
synthesis = llm.invoke(f"Synthesize research results:{combined}")
return {"final_synthesis": synthesis.content}
builder = StateGraph(ResearchState)
builder.add_node("research_worker", research_worker)
builder.add_node("synthesizer", synthesizer)
builder.add_conditional_edges(START, supervisor, ["research_worker"])
builder.add_edge("research_worker", "synthesizer")
builder.add_edge("synthesizer", END)
graph = builder.compile()
Modèle 3 — Supervisor-worker : routage d'intention ; workers spécialisés. Fast-path mots-clés + routage LLM.
KEYWORD_ROUTING = {
"code": "code_agent", "code": "code_agent",
"search": "search_agent", "query": "search_agent",
"data": "data_agent",
}
def supervisor_with_fast_path(state):
query = state["query"].lower()
for keyword, agent_name in KEYWORD_ROUTING.items():
if keyword in query:
return {"next": agent_name}
routing_prompt = f"""
User request:{state['query']}
Available agents:code_agent, search_agent, data_agent
Return best agent name only。
"""
decision = llm.invoke(routing_prompt)
return {"next": decision.content.strip()}
Modèle 4 — Swarm : peer-to-peer sans coordinateur ; limites de rounds obligatoires. Exemple AutoGen GroupChat :
import autogen
reviewer_1 = autogen.AssistantAgent(
name="SecurityReviewer",
system_message="Security expert focused on vulnerabilities."
)
reviewer_2 = autogen.AssistantAgent(
name="PerformanceReviewer",
system_message="Performance expert focused on efficiency."
)
human_proxy = autogen.UserProxyAgent(
name="CodeAuthor",
human_input_mode="NEVER",
max_consecutive_auto_reply=2,
is_termination_msg=lambda x: "APPROVED" in x.get("content", "")
)
groupchat = autogen.GroupChat(
agents=[human_proxy, reviewer_1, reviewer_2],
messages=[],
max_round=6
)
manager = autogen.GroupChatManager(groupchat=groupchat)
Modèle 5 — Blackboard : espace partagé ; jobs async longs. Modèle 6 — Hybride : routeur d'intention + supervisor + fan-out + QA — typique enterprise.
MCP + A2A (Agentic AI Foundation) : MCP vertical Agent ↔ outils/API ; A2A horizontal — délégation et discovery.
from mcp.server import Server
from mcp.types import Tool, TextContent
app = Server("data-agent-mcp")
@app.list_tools()
async def list_tools():
return [
Tool(
name="query_customer_db",
description="Query customer DB by id, name, or email",
inputSchema={
"type": "object",
"properties": {
"field": {"type": "string", "enum": ["id", "name", "email"]},
"value": {"type": "string"}
},
"required": ["field", "value"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "query_customer_db":
result = db.query(arguments["field"], arguments["value"])
return [TextContent(type="text", text=str(result))]
Agent Card A2A sur /.well-known/agent.json ; délégation JSON-RPC 2.0 :
import httpx
async def discover_and_delegate(agent_url: str, task: str):
card_response = await httpx.get(f"{agent_url}/.well-known/agent.json")
agent_card = card_response.json()
available_skills = [s["id"] for s in agent_card["skills"]]
if "web_research" not in available_skills:
raise ValueError(f"Agent {agent_card['name']} lacks web_research skill")
payload = {
"jsonrpc": "2.0",
"method": "message/send",
"id": "task-001",
"params": {
"message": {
"role": "user",
"parts": [{"type": "text", "text": task}]
}
}
}
response = await httpx.post(agent_card["url"], json=payload)
return response.json()
À lire : Créer un MCP Server ; MCP, le HTTP de l'ère IA.
Runbook production en six étapes
Choix et topologie :Arbre de décision pipeline/fan-out/hiérarchie/blackboard/hybride ; 3–8 agents.
Persistance :PostgresSaver, recovery et thread_id par session.
Human-in-the-loop :interrupt() sur actions à risque — secteurs régulés et EU AI Act.
Circuit breaker :CircuitBreaker sur appels externes.
Budget tokens :TokenBudgetManager avant chaque appel.
Observabilité :OpenTelemetry, MONITORING_METRICS, échantillonnage LLM-as-Judge.
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.types import interrupt
with PostgresSaver.from_conn_string("postgresql://user:pass@localhost/agentdb") as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-session-12345"}}
result = graph.invoke({"query": "Analyze Q2 report"}, config)
def high_risk_action_agent(state):
proposed_action = plan_action(state)
human_decision = interrupt({
"proposed_action": proposed_action,
"risk_level": "HIGH",
"message": "This modifies production DB. Confirm?"
})
if human_decision["approved"]:
return execute_action(proposed_action)
return {"status": "cancelled", "reason": human_decision.get("reason")}
import time
from functools import wraps
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.state = "CLOSED"
self.last_failure_time = None
def __call__(self, func):
@wraps(func)
async def wrapper(*args, **kwargs):
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "HALF_OPEN"
else:
raise Exception("Circuit breaker OPEN - Agent temporarily unavailable")
try:
result = await func(*args, **kwargs)
if self.state == "HALF_OPEN":
self.state = "CLOSED"
self.failure_count = 0
return result
except Exception:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "OPEN"
raise
return wrapper
class TokenBudgetManager:
def __init__(self, total_budget: int = 100_000):
self.total_budget = total_budget
self.used_tokens = 0
self.agent_usage = {}
def check_budget(self, agent_name: str, estimated_tokens: int) -> bool:
remaining = self.total_budget - self.used_tokens
if estimated_tokens > remaining:
raise BudgetExceededException(
f"Agent {agent_name} requests {estimated_tokens} tokens,"
f"il ne reste que {remaining} tokens dans le budget"
)
return True
def record_usage(self, agent_name: str, actual_tokens: int):
self.used_tokens += actual_tokens
self.agent_usage[agent_name] = self.agent_usage.get(agent_name, 0) + actual_tokens
Données d'observabilité, pièges et tendances 2026
MAST a analysé 1642 traces multi-agents :
| Type | Part | Description |
|---|---|---|
| Conception système | 41.77% | Étapes dupliquées, mauvais outils, overflow, pas de terminaison |
| Désalignement | 36.94% | Perte de contexte ; hallucinations comme faits |
| Validation | 21.30% | Arrêt prématuré, validation incomplète |
57 % vs 8 % :57 % en prod, 8 % avec observabilité LLM — HTTP 200 avec sorties fausses.
Google Bake-Off ×6 : Architecture multi-agents distribuée : traitement réduit d'une heure à dix minutes ; sous-agents upgradeables indépendamment.
AdaptOrch 12–23 % :Bonne topologie +12–23 % sur SWE-bench vs changement de modèle.
from opentelemetry import trace
import uuid
tracer = trace.get_tracer("multi-agent-system")
def traced_agent_call(agent_name: str, task: dict, correlation_id: str = None):
if not correlation_id:
correlation_id = str(uuid.uuid4())
with tracer.start_as_current_span(f"agent.{agent_name}") as span:
span.set_attribute("agent.name", agent_name)
span.set_attribute("correlation.id", correlation_id)
span.set_attribute("task.type", task.get("type", "unknown"))
try:
result = agent_registry[agent_name].run(task)
span.set_attribute("agent.tokens_used", result.get("tokens", 0))
span.set_attribute("agent.status", "success")
return result
except Exception as e:
span.set_attribute("agent.status", "error")
span.set_attribute("error.message", str(e))
raise
MONITORING_METRICS = {
"task_success_rate": "task_success_rate target >85%",
"e2e_latency_p95": "e2e_latency_p95 target <30s",
"total_cost_per_task": "avg token cost per task",
"agent_error_rate": "agent_error_rate target <5%",
"agent_retry_count": "retry count high needs investigation",
"tool_call_budget_usage": "tool call budget ratio",
"output_quality_score": "output quality score",
"goal_alignment_score": "goal alignment score",
"hallucination_rate": "hallucination rate",
}
def evaluate_agent_output(original_task: str, agent_output: str) -> dict:
evaluation_prompt = f"""
Original task: {original_task}
Agent output: {agent_output}
Rate completeness, accuracy, relevance, hallucination (1-5 each).
Return JSON only: {{"completeness": x, "accuracy": x, "relevance": x,
"hallucination_detected": true/false, "comments": "..."}}
"""
evaluation = llm.invoke(evaluation_prompt)
return json.loads(evaluation.content)
Cinq pièges et parades :
Pollution de contexte :Hallucinations propagées. Fix : validate_agent_output à chaque handoff.
Boucles infinies :Retries incontrôlés. Fix : plafonds stricts.
Sur-ingénierie :Deux étapes → huit agents. Fix : pipeline d'abord ; 3–8 agents.
Fossé démo-prod :Entrées edge en prod. Fix : ProductionGuardrails, PII.
Sync parallèle :Supervisor trop tôt. Fix : defer=True.
def validate_agent_output(output: dict, schema: dict) -> bool:
jsonschema.validate(output, schema)
if output.get("confidence_score", 1.0) < 0.7:
raise LowConfidenceError(f"Agent output confidence too low: {output['confidence_score']}")
required_fields = schema.get("required", [])
missing = [f for f in required_fields if not output.get(f)]
if missing:
raise MissingFieldsError(f"Missing required output fields: {missing}")
return True
MAX_ITERATIONS = 10
MAX_TOOL_CALLS_PER_AGENT = 20
MAX_TOTAL_TOKENS = 50_000
class ProductionGuardrails:
def validate_input(self, user_input: str) -> str:
if len(user_input) > 10000:
raise InputTooLongError("Input exceeds 10000 chars")
injection_patterns = ["ignore previous instructions", "forget everything"]
for pattern in injection_patterns:
if pattern.lower() in user_input.lower():
raise PromptInjectionError("Prompt injection detected")
return user_input.strip()
def validate_output(self, output: str) -> str:
output = self.pii_filter.redact(output)
if self.content_classifier.is_harmful(output):
raise HarmfulContentError("Harmful content in output")
return output
builder.add_node("supervisor", supervisor_node, defer=True)
La tâche a-t-elle des dépendances linéaires claires ?
├─ Oui → Sous-tâches parallélisables ?
│ ├─ Non → [Pipeline séquentielle]
│ └─ Oui → [Fan-out + pipeline hybride]
└─ Non → Un agent décisionnaire ?
├─ Oui → Besoin de sous-équipes ?
│ ├─ Non → [Supervisor-worker]
│ └─ Oui → [Hiérarchique]
└─ Non → Tâche async longue ?
├─ Oui → [Blackboard]
└─ Non → ≤ 5 agents ?
├─ Oui → [Swarm avec terminaison]
└─ Non → [Repasser en hiérarchique]
Cinq points clés :① Topologie > modèle ; ② pipeline d'abord ; ③ MCP+A2A standard ; ④ observabilité obligatoire ; ⑤ 3–8 agents.
Tendances 2026 :Orchestration fédérée, agents multimodaux, topologie adaptive, EU AI Act et pistes d'audit.
Attention : L'orchestration multi-agents sur laptop échoue au sleep, manque de RAM et jitter réseau ; un VPS Linux bon marché ne fait ni Xcode ni l'inférence Apple Silicon nativement. L'architecture multi-agents est un atout clé en 2026 — la production exige un hôte stable.
Pour orchestration 7×24, PostgresSaver, MCP et Cursor/Claude Code, la location Mac Mini cloud MESHLAUNCH convient : Apple Silicon dédié, facturation flexible. Voir tarifs et centre d'aide.
État, HITL, conformité → LangGraph ; prototype 1–2 j → CrewAI ; Azure → AutoGen. Hébergement : page tarifs.
MCP vertical ; A2A horizontal avec Agent Cards et JSON-RPC 2.0. Adoptez les deux en greenfield.
PostgreSQL, OpenTelemetry, budget tokens, circuit breakers, hôte 7×24. Mac Mini pour LangGraph, MCP, vecteurs. Déploiement : centre d'aide.