Aller au contenu principal

Handoffs : orchestrer plusieurs agents

Mis à jour le 29 juillet 2026

Handoffs : orchestrer plusieurs agents

Un seul agent ne peut pas tout faire. Plus vous empilez de domaines dans un même jeu d’instructions, plus les consignes se contredisent et plus les réponses deviennent tièdes. Les handoffs permettent à un agent de déléguer la conversation à un autre agent spécialisé. C’est le mécanisme clé pour construire des systèmes multi-agents où chaque agent excelle dans son domaine.

Le concept de handoff

Un handoff est un transfert de contrôle d’un agent à un autre. Quand l’agent A fait un handoff vers l’agent B, c’est l’agent B qui prend la main et produit la réponse. Le Runner gère cette transition automatiquement : vous n’écrivez ni aiguillage explicite ni appel manuel au second agent. Dans l’exemple ci-dessous, l’agent de triage reçoit une plainte sur un montant de facture, reconnaît le domaine et passe la main ; le client, lui, ne voit qu’une seule conversation continue.

from agents import Agent, Runner

agent_facturation = Agent(
    name="Agent facturation",
    instructions="""Vous gérez les questions de facturation : factures, paiements,
    remboursements, abonnements. Répondez de manière précise et professionnelle.""",
    model="gpt-5.6-terra",
)

agent_technique = Agent(
    name="Agent technique",
    instructions="""Vous gérez le support technique : bugs, configuration,
    installation, dépannage. Donnez des instructions étape par étape.""",
    model="gpt-5.6-terra",
)

agent_triage = Agent(
    name="Agent de triage",
    instructions="""Vous êtes le premier point de contact.
    Analysez la demande du client et transférez-la à l'agent approprié.
    - Questions de facturation → Agent facturation
    - Questions techniques → Agent technique""",
    handoffs=[agent_facturation, agent_technique],
    model="gpt-5.6-terra",
)

result = Runner.run_sync(
    agent_triage,
    "Ma dernière facture semble incorrecte, le montant est trop élevé."
)
# L'agent de triage fait un handoff vers l'agent facturation
print(f"Répondu par : {result.last_agent.name}")
print(result.final_output)

Personnaliser la description du handoff

Par défaut, le handoff utilise le nom et les instructions de l’agent cible. Cela fonctionne tant que les noms sont parlants, mais devient hasardeux dès que deux agents se ressemblent. Vous pouvez alors personnaliser la description pour guider le routage, en énumérant les cas qui doivent déclencher chaque transfert.

from agents import Handoff

agent_triage = Agent(
    name="Agent de triage",
    instructions="Transférez la demande à l'agent spécialisé.",
    handoffs=[
        Handoff(
            agent=agent_facturation,
            description="Transfert pour les questions de paiement, factures, remboursements et abonnements.",
        ),
        Handoff(
            agent=agent_technique,
            description="Transfert pour les problèmes techniques, bugs et demandes d'aide à la configuration.",
        ),
    ],
)

Handoffs avec contexte

Un transfert n’a d’intérêt que si l’agent qui prend la main sait de quoi on parle. Quand un agent fait un handoff, l’historique de conversation est transmis à l’agent cible : le nouvel agent a donc accès à tout ce qui a été dit, sans que le client ait à répéter son problème. À cela s’ajoute le contexte typé, qui porte les données structurées de la session — identifiant client, plan souscrit, tickets passés — et reste accessible aux tools de l’agent d’arrivée.

from dataclasses import dataclass
from agents import Agent, Runner, RunContextWrapper, function_tool

@dataclass
class ContexteClient:
    client_id: str
    plan: str
    historique_tickets: list[str]

@function_tool
def consulter_facture(ctx: RunContextWrapper[ContexteClient], mois: str) -> str:
    """Consulte la facture d'un mois donné pour le client courant."""
    client_id = ctx.context.client_id
    return f"Facture de {mois} pour {client_id}: 149.99€ (plan {ctx.context.plan})"

agent_facturation = Agent(
    name="Agent facturation",
    instructions="Vous gérez la facturation. Utilisez le contexte client.",
    tools=[consulter_facture],
    model="gpt-5.6-terra",
)

agent_triage = Agent(
    name="Triage",
    instructions="Transférez au bon agent.",
    handoffs=[agent_facturation],
    model="gpt-5.6-terra",
)

contexte = ContexteClient(
    client_id="cli_456",
    plan="Pro",
    historique_tickets=["Ticket #001: Connexion lente"]
)

result = Runner.run_sync(
    agent_triage,
    "Pouvez-vous vérifier ma facture de mars ?",
    context=contexte,
)

Pattern : triage à trois niveaux

Les handoffs ne se limitent pas à un aiguillage horizontal. Ils reproduisent aussi la structure d’escalade d’un vrai service support. Voici un pattern production avec trois niveaux : le L1 traite le tout-venant, transfère au L2 ce qui relève de la configuration avancée, et le L2 escalade au L3 les sujets d’architecture. Notez que le niveau 3 s’appuie sur un modèle plus capable, puisqu’il ne voit que les cas déjà filtrés par les deux étages précédents.

agent_niveau3 = Agent(
    name="Expert technique L3",
    instructions="""Vous êtes un expert technique de niveau 3.
    Vous traitez les problèmes complexes d'architecture et d'infrastructure.
    Si vous ne pouvez pas résoudre, indiquez qu'une escalade humaine est nécessaire.""",
    model="gpt-5.6-sol",  # Le modèle le plus capable pour les cas complexes
)

agent_niveau2 = Agent(
    name="Support technique L2",
    instructions="""Vous êtes un technicien de niveau 2.
    Vous traitez les problèmes de configuration avancée.
    Si le problème est trop complexe, transférez au niveau 3.""",
    handoffs=[agent_niveau3],
    model="gpt-5.6-terra",
)

agent_niveau1 = Agent(
    name="Support client L1",
    instructions="""Vous êtes le premier contact du support client.
    Traitez les questions simples (mot de passe, compte, navigation).
    Transférez les problèmes techniques au niveau 2.""",
    handoffs=[agent_niveau2],
    model="gpt-5.6-terra",
)

Handoffs bidirectionnels

La chaîne peut aussi être circulaire : deux agents peuvent se transférer mutuellement le contrôle. C’est le cas typique d’un prospect qui pose une question technique pendant une conversation de vente, puis revient sur le tarif une fois rassuré.

agent_vente = Agent(
    name="Agent vente",
    instructions="""Vous vendez des produits. Si le client demande
    du support technique, transférez à l'agent technique.""",
)

agent_support = Agent(
    name="Agent support",
    instructions="""Vous faites du support technique. Si le client
    veut acheter quelque chose, transférez à l'agent vente.""",
)

# Handoffs bidirectionnels
agent_vente.handoffs = [agent_support]
agent_support.handoffs = [agent_vente]

Attention aux boucles infinies : un guardrail ou un max_turns est recommandé. Sans cette limite, une demande ambiguë peut faire rebondir la conversation d’un agent à l’autre jusqu’à épuisement de votre quota.

Suivre les handoffs

Enfin, il faut savoir qui a réellement répondu — pour vérifier votre routage comme pour vos statistiques de support. Le RunResult vous indique quel agent a finalement produit la réponse.

result = Runner.run_sync(agent_triage, "J'ai un bug critique")

print(f"Agent initial : agent_triage")
print(f"Agent final : {result.last_agent.name}")
# Affiche "Agent technique" si le triage a bien fonctionné

Prenez l’habitude de journaliser cette valeur : si un agent de triage garde la main sur des demandes qu’il aurait dû transférer, la distribution des last_agent le révèle bien avant les retours clients.

Points clés à retenir

  • Les handoffs permettent de déléguer la conversation à un agent spécialisé
  • L’historique de conversation est transmis à l’agent cible
  • Utilisez Handoff(agent=..., description=...) pour guider le routage
  • Le contexte partagé (RunContextWrapper) est accessible par tous les agents
  • result.last_agent.name indique quel agent a finalement répondu
  • Protégez les handoffs bidirectionnels avec max_turns contre les boucles