Accueil / Articles

Survivre aux pics de trafic dans les systèmes multi-agents grâce à un contrat à trois niveaux.

Maintenir les systèmes multi-agents opérationnels lorsque le trafic augmente de dix fois : un gestionnaire de files d’attente, un régulateur de charge par outil et des vérifications de politique synchronisées, ainsi que des métriques et une démonstration de pic de charge.

Éditeur : REACTAPP.TOP8 min de lecture1869 mots

Sources: Multi-Agent Orchestration in Production: Handling Traffic Spikes at Scale

Un agent graphique peut fonctionner correctement avec 10 requêtes par seconde, mais il cesse de fonctionner correctement dès 100 requêtes. Les requêtes s’accumulent, les vérifications de politique sont ignorées, les appels aux outils entrent en conflit, et un système qui semblait fiable devient alors une source de problèmes. Les agents ne sont que rarement la cause du problème. Ce qui manque, c’est un accord explicite entre la file d’attente, la couche d’orchestration et les mécanismes de contrôle. La plupart des équipes ne découvrent cette lacune que lorsque le trafic réel commence déjà à en subir les conséquences.

Pourquoi les pics de trafic surviennent plus tôt que prévu

Les systèmes d’agents quittent progressivement la phase de démonstration pour entrer dans un environnement de production réel, ce qui rend les pics de trafic inévitables : une vente flash, une fonctionnalité qui devient virale, un job batch planifié, ou simplement tous les utilisateurs qui accèdent à l’API le mardi matin. Deux choses se produisent en même temps à mesure que la charge augmente.

First, le routage prend du temps en temps réel. Choisir quel agent gérera une requête, ou décider s’il faut la mettre en file d’attente immédiatement, constitue une tâche mesurable.

Deuxièmement, les systèmes ont tendance à échouer de manière ouverte sous pression. Ce n’est généralement pas dû à des ingénieurs paniqués qui contournent les vérifications. Il s’agit plutôt d’un traitement naïf des erreurs qui choisit automatiquement l’option « autoriser » en cas de délai d’attente, ou d’un routage de secours qui ignore silencieusement le middleware de validation. Lorsque l’application des politiques disparaît sous charge, c’est normalement ce schéma architectural erroné qui en est la cause, et non une intervention humaine.

La solution réside dans un accord clair et contraignant entre trois éléments : la file d’attente (ce qui y entre), le graphe d’orchestration (où cela se rend) et le moteur de politiques (ce que cela peut faire). Établissez cet accord dès le début, afin qu’un pic de charge devienne un problème résolu plutôt qu’une urgence.

Un contrat à trois niveaux

Niveau 1 : la file d’attente en tant que gardien

La file d’attente est le premier endroit où les règles doivent être appliquées. Avant que toute demande n’atteigne un agent, elle passe par une vérification du contrat couvrant trois aspects :

  • Priorité. Les demandes ne sont pas toutes égales. Les requêtes interactives des utilisateurs peuvent avoir la priorité sur les tâches en lot, et un client payant peut être placé avant un utilisateur en version gratuite. La file d’attente doit gérer cet ordre de priorité de manière à ce que les agents n’aient jamais besoin d’en être conscients.
  • Limits de vitesse et quotas. Refuser une demande est le moyen le moins coûteux avant même que des ressources ne soient utilisées. Un utilisateur ayant atteint son limite quotidienne d’API doit recevoir un refus clair plutôt que d’être inséré dans la file, ce qui coûte bien moins cher qu’une intervention d’un agent.
  • Conformité au schéma. Les données que le système ne peut pas traiter doivent être rejetées dès l’entrée, et non découvertes à l’intérieur d’un agent.

Cette couche garantit que chaque demande présente dans la file a été validée, classée et approuvée pour consommer des ressources.

Couche 2 : l’orchestration détermine le routage et le rythme

Lorsqu’une demande a été mise en file d’attente, l’orchestrateur choisit sa destination ainsi que les actions qu’elle peut effectuer. C’est ici que sont contrôlées la concurrence et l’accès aux outils.

Le contrat de routage répond à quelques questions : quel groupe d’agents doit gérer la demande, par exemple des agents rapides pour des requêtes simples et des agents plus lents pour des raisonnements complexes ? Combien de demandes chaque agent peut-il traiter en même temps ? Que se passe-t-il lorsque tous les agents sont occupés : mise en file d’attente, rejet ou recours à une solution de secours ?

Le contrat de limitation des outils prend en compte les différences entre eux. Appeler une API externe cent fois par seconde n’a rien à voir avec l’exécution d’un embedding local. Chaque outil dispose de sa propre limite de concurrence. Un outil basé sur une base de données peut autoriser dix appels simultanés, tandis qu’un outil qui fait appel à une API tierce peut n’en autoriser que cinq. Lorsqu’un outil atteint sa limite, la demande attend dans sa propre file d’attente et non dans la file principale.

Pour maintenir ces files d’outils en bon état et empêcher que les pannes ne se propagent, les primitives de niveau production aident :

  • Les disjoncteurs et les buckets de tokens détectent rapidement les pannes au niveau de l’outil, évitant ainsi des vagues de tentatives de réessai lorsque une dépendance est saturée.
  • Les files de lettres mortes stockent les charges utiles d’outils mal formatées ou endommagées, de sorte qu’une seule demande défectueuse ne bloque jamais tout le cycle de l’agent.
  • Les buckets fuyants spécifiques à chaque outil appliquent leur propre limite de débit, protégeant ainsi les exécutions parallèles des agents contre des contraintes externes.

Avec ces mécanismes en place, les goulets d’étranglement au niveau de l’outil restent maîtrisés et visibles, sans se propager en instabilité à l’échelle du système. La promesse de cette couche : une demande est envoyée à un agent disposant de capacité disponible, chaque outil respecte sa limite de concurrence, et si un outil est saturé, la demande attend cet outil plutôt que l’agent.

Couche 3 : des politiques inébranlables

La difficulté réside dans le fait que les politiques doivent s’exécuter à chaque fois, y compris sous charge. Il n’existe ni option « vérifier plus tard » ni possibilité de penser que « celle-ci semble sûre ».

Le contrat de politique comporte quatre clauses :

  • Une vérification de la politique a lieu avant toute appel d’outil, de manière synchrone et bloquante.
  • La vérification est rapide, ne prenant au maximum que quelques millisecondes. Sinon, la conception est erronée.
  • Lorsque la vérification échoue, la demande échoue également. L’agent n’a aucune chance de réessayer ou de négocier.
  • Les échecs sont enregistrés et surveillés, et une augmentation du nombre d’échecs de politique constitue en soi un signal.

Les politiques typiques se présentent ainsi : l’agent ne peut utiliser que des outils marqués comme « lecture seule » pour cet utilisateur ; un outil ne peut fonctionner que pour un appelant authentifié ; un agent ne peut effectuer au maximum cinq appels d’outils par demande. Cette couche garantit qu’une vérification a lieu avant chaque appel d’outil, et que les politiques sont rapides, déterministes et absolues.

La structure du pipeline

Dans un pseudo-graphique minimal, une demande entre par le gardien de file d’attente, est transmise au routeur, parvient à un agent, et chaque appel d’outil passe par l’application des politiques ainsi que par un mécanisme de limitation par outil avant d’être exécuté.

[ Incoming Request ]
         │
         ▼
[ Queue Gatekeeper ]
  • Validate Schema
  • Enforce Rate Limits
  • Assign Priority
         │ (Pass)
         ▼
[ Orchestration Router ]
  • Check Agent Capacity
  • Route to Target Node
         │
         ▼
[ Tool-Level Gate ]
  • Sync Policy Check
  • Per-Tool Concurrency Limits
  • Circuit Breaker / DLQ
         │ (Pass)
         ▼
[ Tool Execution ]

Le détail important concerne l’endroit où s’appliquent les politiques. Elles s’appliquent au niveau de l’outil et non au niveau de l’agent, ce qui empêche un agent de les contourner par erreur en utilisant un outil d’une manière inattendue.

Un scénario de pic, avec et sans contrat

Imaginons un agent de support client capable de rechercher des commandes, de traiter des remboursements et d’envoyer des e-mails, et que le trafic augmente soudainement dix fois.

Sans contrat, l’échec se déroule par étapes :

  • Les demandes entrantes épuisent le pool d’agents disponibles.
  • Les agents commencent à faire la file pour utiliser les outils, et l’outil send_email accumule 500 appels en attente.
  • Le service d’e-mails commence à expirer.
  • Les agents tentent à nouveau, ce qui fait que 1 000 appels sont maintenant en attente.
  • Les chemins de secours activés en cas d’échec permettent de contourner l’application des politiques afin de maintenir le système opérationnel.
  • Un agent envoie un e-mail de remboursement au mauvais client.

Avec un contrat, la même situation se déroule différemment :

  • Le gestionnaire de file régule l’accès en fonction de la priorité et des quotas.
  • Le routeur constate que l’outil d’envoi d’e-mails a atteint sa limite de concurrence de dix, et il met en file les demandes d’e-mails supplémentaires pour cet outil plutôt que pour l’agent.
  • Les vérifications de politique se poursuivent à chaque appel à l’outil. Si un agent tente d’envoyer un remboursement au mauvais client, la vérification échoue et la demande est rejetée.
  • Le système reste stable. La latence augmente, mais la fiabilité ne diminue pas.

En résumé

Commencer par LangGraph

Une équipe déjà présente sur LangGraph possède la plupart des primitives. Les agents et les outils sont des nœuds, les arêtes représentent les choix de routage, et les arêtes conditionnelles effectuent le routage en fonction de l’état. Ce qui reste à ajouter, c’est une file d’attente en amont, qu’il s’agisse de Redis, de RabbitMQ ou d’une simple file mémoire, ainsi qu’un middleware de politique enveloppant les appels aux outils.

L’enveloppe de politique elle-même peut être un petit décorateur qui effectue les vérifications et génère une erreur de violation avant même que la fonction de l’outil ne s’exécute.

from functools import wraps
from typing import Callable, Any

class PolicyViolation(Exception):
    """Raised when policy check fails."""
    pass

def enforce_tool_policy(
    policy_fn: Callable[[str, dict], bool]
):
    """Sync tool execution guardrail."""
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            tool_name = func.__name__
            context = kwargs.get(
                "request_context", {}
            )

            # Must fail closed
            if not policy_fn(tool_name, context):
                raise PolicyViolation(
                    f"Denied: {tool_name}"
                )

            return func(*args, **kwargs)
        return wrapper
    return decorator

# Example Usage
def strict_policy_check(tool_name: str, context: dict) -> bool:
    if not context.get("is_authenticated"):
        return False

    if (context.get("role") != "admin" and tool_name.startswith("execute_")):
        return False

    return True

@enforce_tool_policy(strict_policy_check)
def execute_refund(
    order_id: str,
    amount: float,
    request_context: dict = None
):
    # Downstream API call
    return f"Refund ${amount} sent: {order_id}"

Le mesurer

Il ne suffit pas de mettre en œuvre cette solution en espérant que cela fonctionne. Il faut l’instrumenter et observer :

  • La profondeur de la file d’attente à chaque niveau.
  • La fréquence à laquelle, et quels, outils atteignent leurs limites.
  • Le fait que des politiques rejettent des requêtes, et pourquoi.
  • Les latences p50, p95 et p99, suivies séparément pour différents types de requêtes.
  • Le coût par requête sous charge. S’il augmente, cela indique l’existence d’inefficacités.

Une vision sceptique

On peut légitimement penser qu’il s’agit d’une infrastructure excessive pour un problème qui n’existe pas encore. Un système qui gère dix requêtes par seconde et qui restera à ce niveau n’en a pas besoin. En revanche, une fonctionnalité susceptible de devenir virale ou des clients entreprises ayant un trafic imprévisible relèvent d’un cas différent, et la conception doit être finalisée avant le lancement.

Building it in advance costs little next to retrofitting it after a spike has broken the system. It also shouldn't be overbuilt. A simple queue, a policy check function and per-tool concurrency limits resolve most spike problems. Neither a distributed consensus algorithm nor a machine-learning load predictor is needed. What matters is clarity and constraints.

Main points

  1. Spikes expose design problems. When an agent system breaks under load, the agents are not too slow. Nobody set clear rules for how the queue, the routing and the policies fit together.
  2. The queue doubles as an enforcement point. It can validate, prioritize and reject requests so compute is not wasted.
  3. Policies are absolute and non-negotiable. They run at the tool level, synchronously, every time, with no exceptions.
  4. Réguler les outils avant les agents. Les goulets d’étranglement se trouvent généralement en aval, dans des API externes ou des bases de données qui alimentent le contexte de l’agent, et non chez le fournisseur de LLM.
  5. Mesurer tout : la profondeur de la file d’attente, la concurrence des outils, les échecs des politiques, les percentiles de latence et le coût par requête. Ce qui n’est pas mesuré ne peut pas être amélioré.

Où commencer

  1. Écrire le contrat de la file d’attente : ce qui est accepté et ce qui est rejeté.
  2. Écrire le contrat de routage : quels agents s’occupent de quelles tâches, et quels sont les limites de concurrence.
  3. Écrire le contrat de la politique : quels contrôles s’exécutent avant chaque appel à un outil.
  4. Le construire à l’aide d’outils comme LangGraph, ajouter une couche de file d’attente et envelopper les outils dans des contrôles de politique.
  5. Faire des tests de charge. Simuler une augmentation de dix fois et observer ce qui tombe en panne.

Tout cela se fait le mieux avant la mise en production, tant qu’il reste encore du temps pour y réfléchir calmement.

Lectures complémentaires

  • Pourquoi les agents IA en production ont besoin de plus qu’un bon prompt — Contexte, outils, mémoire, gestion des pannes, traçage, tests et autonomie basée sur les risques : l’architecture autour d’un LLM qui rend un agent fiable.