Aller au contenu principal

Handoffs : orchestrer plusieurs agents

Handoffs : orchestrer plusieurs agents

Un seul agent ne peut pas tout faire. 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.

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.3",
)

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.3",
)

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.3",
)

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. Vous pouvez personnaliser la description pour guider le routage :

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

Quand un agent fait un handoff, l’historique de conversation est transmis à l’agent cible. Cela signifie que le nouvel agent a accès à tout ce qui a été dit :

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.3",
)

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

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

Voici un pattern production avec trois niveaux de support :

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.4",  # 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.3",
)

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.3",
)

Handoffs bidirectionnels

Deux agents peuvent se transférer mutuellement le contrôle :

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é.

Suivre les handoffs

Le RunResult vous indique quel agent a finalement répondu :

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é

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