Accueil / Articles

Créer un routeur multi-agents avec LangGraph et un agent superviseur

Comment diviser un agent surchargé en spécialistes avec un superviseur dans LangGraph, en utilisant un outil prédéfini et un StateGraph manuel, ainsi que la conception des états et les pièges liés aux boucles.

Éditeur : REACTAPP.TOP9 min de lecture2122 mots

Sources: Build a Multi-Agent Router with LangGraph in 30 Minutes

Placer un seul agent devant un produit réel le pousse rapidement à en faire trop. Si l’on demande à un seul agent de gérer le support client, le suivi des commandes et les remboursements, il se révèle médiocre dans les trois domaines. Son prompt système devient un enchevêtrement de 2 000 mots contenant des instructions du type « si l’utilisateur demande au sujet de X, faire Y, mais s’il s’agit aussi de Z, alors... ». La solution n’est pas un agent plus intelligent, mais plutôt plusieurs agents plus petits, accompagnés d’un superviseur qui les coordonne. C’est sans doute le modèle de production le plus utilisé dans l’IA agentielle actuellement, et une version fonctionnelle peut tenir dans un court programme LangGraph.

Les problèmes posés par un agent géant

Imaginons que vous développiez un assistant capable de répondre à des questions générales, d’effectuer des calculs arithmétiques et de rechercher sur Internet des informations à jour. La conception tentante consisterait en un seul agent, une seule instruction détaillée à laquelle seraient associés tous les outils nécessaires. À mesure que les outils et les instructions s’accumulent, le modèle hésite quant au choix de l’outil à utiliser et sa capacité de raisonnement s’affaiblit. Le débogage devient alors un cauchemar, car il est impossible de déterminer pourquoi l’agent a choisi le mauvais outil lorsque tout se trouve dans une seule instruction.

La solution consiste à diviser les responsabilités : un agent pour les calculs mathématiques, un autre pour la recherche, ainsi qu’un superviseur, qui n’est en réalité qu’un routeur chargé de décider quel spécialiste s’occupe de chaque partie d’une demande.

Le modèle du superviseur

Pensez à un centre d’appels. Vous appelez avec une question et vous n’êtes pas mis en relation directement avec l’expert. Une standardiste écoute d’abord, décide si vous avez besoin de services de facturation, de support technique ou de vente, puis vous transfère. La standardiste est la superviseure et les trois équipes représentent les agents spécialisés. La superviseure ne répond jamais à la question elle-même ; son unique rôle est de choisir celui qui le fera.

Visuellement, c’est ainsi :

                    ┌─────────────┐
                    │    User     │
                    │   Request   │
                    └──────┬──────┘
                           │
                           ▼
                   ┌───────────────┐
                   │   Supervisor  │
                   │  (routes task)│
                   └───────┬───────┘
                    ┌──────┴──────┐
                    ▼             ▼
             ┌────────────┐ ┌────────────┐
             │  Research  │ │    Math    │
             │   Agent    │ │   Agent    │
             └──────┬─────┘ └──────┬─────┘
                    │              │
                    └──────┬───────┘
                           ▼
                   ┌───────────────┐
                   │   Supervisor  │
                   │ (final answer)│
                   └───────────────┘

Le superviseur prend la tâche, la transmet au spécialiste compétent, reçoit le résultat, puis décide s’il faut la rediriger ou fournir directement la réponse finale à l’utilisateur.

La méthode de construction recommandée, étape par étape

Un point important à connaître avant d’écrire du code : l’équipe de LangChain recommande désormais de construire le modèle de superviseur directement à partir des appels aux outils, plutôt que de s’appuyer uniquement sur l’ancienne bibliothèque d’aide langgraph-supervisor, car les appels aux outils offrent un contrôle plus grand sur l’ingénierie du contexte. Vous trouverez d’abord une version préconstruite rapide, suivie d’une version manuelle afin de bien comprendre ce qui se passe en arrière-plan.

Étape 1 : installer les paquets

pip install langgraph langchain-openai langgraph-supervisor

Étape 2 : former les spécialistes

Commencez par les importations :

from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

Le premier spécialiste s’occupe des mathématiques, et ses outils sont deux petites fonctions. L’addition vient en premier :

# Specialist 1: handles math
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b

Puis la multiplication :

def multiply(a: float, b: float) -> float:
    """Multiply two numbers together."""
    return a * b

Enveloppez-les dans un agent ReAct doté d’un nom et d’une instruction qui limite son rôle :

math_agent = create_react_agent(
    model="openai:gpt-4o",
    tools=[add, multiply],
    name="math_expert",
    prompt="You are a math specialist. Solve numeric problems precisely."
)

Le deuxième spécialiste mène des recherches. Un de ses outils est une fonction de recherche, présentée ici de manière simplifiée :

# Specialist 2: handles research
def web_search(query: str) -> str:
    """Search the web for current information."""
    # In production, replace this with a real search API call
    return f"Search results for '{query}': [your real search integration here]"

Enveloppez aussi ceci dans un agent :

research_agent = create_react_agent(
    model="openai:gpt-4o",
    tools=[web_search],
    name="research_expert",
    prompt="You are a research specialist. Find accurate, current information."
)

Chaque agent dispose d’un seul travail et uniquement des outils requis pour ce travail, ce qui évite toute confusion et tout prompt surchargé. Les exemples utilisent des modèles OpenAI, bien qu’une alternative gratuite comme Groq ou OpenRouter fonctionne également.

Étape 3 : créer le superviseur

Importez l’outil d’aide :

from langgraph_supervisor import create_supervisor

Ensuite, fournissez-lui les deux spécialistes, un modèle et une instruction indiquant quand utiliser chacun :

workflow = create_supervisor(
    [research_agent, math_agent],
    model=ChatOpenAI(model="gpt-4o"),
    prompt=(
        "You are a team supervisor managing a research expert and a math expert. "
        "For questions requiring current information or web lookups, use research_expert. "
        "For anything involving calculation, use math_expert. "
        "Route each request to exactly the right specialist."
    )
)

Compilez le flux de travail en une application exécutable :

app = workflow.compile()

Étape 4 : l’exécuter

Envoyez une question de mathématiques :

result = app.invoke({
    "messages": [
        {"role": "user", "content": "What's 1,847 multiplied by 293?"}
    ]
})

Et afficher le message final :

print(result["messages"][-1].content)

C’est ce qui se passe en arrière-plan :

  1. Le superviseur lit la demande.
  2. Il constate qu’il s’agit d’un calcul et non d’une question de recherche.
  3. Il transmet la tâche à math_expert.
  4. math_expert calcule la réponse à l’aide de son outil multiply.
  5. Le superviseur reprend le contrôle et renvoie la réponse.

Pour une question de recherche, comme le nombre actuel d’employés des grandes entreprises technologiques, le même superviseur l’envoie à research_expert. Le code est identique, seule la décision de routage diffère, et le LLM prend cette décision seul.

Qu’est-ce que sont réellement les outils de transfert

Cette partie élimine l’aspect magique. Le superviseur ne passe pas par un mécanisme caché de LangGraph ; il utilise des appels d’outils, exactement comme n’importe quel agent qui effectue des appels d’outils ordinaires. En coulisses, create_supervisor crée un outil de transfert pour chaque spécialiste, dont les noms sont tels que transfer_to_research_expert et transfer_to_math_expert. Le modèle de superviseur considère ces outils comme des éléments pouvant être appelés, sans différence avec une recherche météorologique ; ainsi, lorsqu’il détermine qu’une recherche est nécessaire, il appelle simplement la fonction de transfert. La conséquence est rassurante : le superviseur n’est pas du tout un concept nouveau. Il s’agit simplement du cycle habituel des agents, où certains outils sont en fait d’autres agents plutôt que des appels API.

Le construire manuellement

Si vous souhaitez un contrôle total sur le routage, ce qui est recommandé en phase de production, voici la version manuelle basée sur un StateGraph brut. Commencez par les importations :

from typing import Annotated, Sequence, TypedDict
import operator
from langchain_core.messages import BaseMessage
from langgraph.graph import StateGraph, END

Définissez l’état partagé, qui contient la liste des messages et le nom du prochain nœud :

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next: str

Le nœud superviseur consulte le dernier message :

def supervisor_node(state: AgentState):
    """Decide which specialist should act next."""
    last_message = state["messages"][-1].content

Dans un environnement de production, la décision serait prise à partir d’une appel à un LLM. Ici, cela se réduit à de simples vérifications de mots-clés afin que la logique de routage reste visible :

# In production, this decision comes from an LLM call —
    # simplified here to show the routing logic clearly
    if any(word in last_message.lower() for word in ["calculate", "multiply", "add", "sum"]):
        return {"next": "math_agent"}
    else:
        return {"next": "research_agent"}

Chaque spécialiste reçoit un nœud qui invoque son agent puis restitue le contrôle au superviseur. D’abord les mathématiques :

def math_node(state: AgentState):
    result = math_agent.invoke(state)
    return {"messages": [result], "next": "supervisor"}

Puis effectuez des recherches :

def research_node(state: AgentState):
    result = research_agent.invoke(state)
    return {"messages": [result], "next": "supervisor"}

Ensuite, connectez le graphe en enregistrant les nœuds :

# Wire the graph together
workflow = StateGraph(AgentState)
workflow.add_node("supervisor", supervisor_node)
workflow.add_node("math_agent", math_node)
workflow.add_node("research_agent", research_node)

Définir le point d’entrée :

workflow.set_entry_point("supervisor")

Ajoutez les arêtes conditionnelles qui découlent de la décision du superviseur :

workflow.add_conditional_edges(
    "supervisor",
    lambda state: state["next"],
    {"math_agent": "math_agent", "research_agent": "research_agent", "end": END}
)

Envoyez chaque spécialiste auprès du superviseur par la suite :

workflow.add_edge("math_agent", "supervisor")
workflow.add_edge("research_agent", "supervisor")

Et compiler :

app = workflow.compile()

C’est plus long, mais rien n’est caché. Vous pouvez voir comment l’état évolue, comment chaque décision de routage est prise, et où ajouter votre propre logique telle que l’enregistrement des données, des contrôles ou des conditions de terminaison personnalisées.

Le choix de conception de l’état qui se dégrade le plus avec le temps

Un avertissement tiré de l’expérience en production : le schéma de l’état est la décision la plus difficile à modifier ultérieurement. Il est tentant de mettre tous les champs possibles dans un seul dictionnaire plat dès le début, en y incluant les messages, le contexte de l’utilisateur et les résultats intermédiaires. Cela fonctionne bien avec deux agents. Mais avec huit ou dix nœuds qui lisent et écrivent tous dans le même état partagé, cela devient problématique, car la vérification de types de LangGraph est stricte et dénouer un état surchargé signifie devoir modifier chaque nœud. Concevez le schéma de manière intentionnelle dès le premier jour, même pour un système à deux agents. Déterminez ce que chaque agent a réellement besoin de lire et d’écrire, et gardez tout le reste en dehors de l’état partagé.

Une échec auquel vous ferez face

Les systèmes de supervision mis en production présentent souvent ce schéma. Imaginez que le superviseur délègue une tâche à un spécialiste. Celui-ci l’achève, mais le superviseur juge le résultat insuffisant et le renvoie. Le spécialiste le révise, et la nouvelle version est en réalité pire que la première. Le superviseur reste insatisfait et le renvoie à nouveau ; à moins de prendre des mesures préventives, cela peut se répéter indéfiniment.

La solution consiste à imposer un plafond aux allers-retours du superviseur, ce qui peut se faire grâce à un compteur simple dans l’état du système, associé à une condition d’arrêt claire et déterministe. L’opinion d’un LLM sur le fait qu’une chose est suffisamment bonne ne constitue pas une règle d’arrêt fiable et unique.

Quand ne pas l’utiliser

Soyez honnête quant à l’adaptabilité. Si la tâche consiste en une seule récupération suivie d’une seule génération, comme dans un système de Q&R classique de type RAG, un superviseur est superflu. La gestion supplémentaire de l’état et les surcoûts liés au routage ne servent à rien lorsqu’il n’y a pas de véritable décision à prendre pour diriger le flux. Adoptez le pattern du superviseur uniquement lorsque vous disposez de spécialistes véritablement distincts et d’un choix de routage réel, et n’adoptez pas ce modèle par défaut.

Résumé

  • Un agent surchargé échoue à mesure que la complexité augmente.
  • Un superviseur est un agent routage qui délègue les tâches aux spécialistes.
  • create_supervisor représente la solution rapide prédéfinie.
  • Au fond, les transferts de responsabilités se font par des appels d’outils ordinaires.
  • Un StateGraph manuel offre un contrôle total sur le routage.
  • Deux leçons tirées de la pratique : concevez le schéma d’état tôt dans le processus et limitez les boucles de révision.