Rate limiting pour les agents IA : comment contenir les pics de requêtes et les tempêtes de retries

Rate limiting pratique en production : limites per-user/per-tenant/global, contrôle du burst, retry_after, backoff, audit logs et alerting.
Sur cette page
  1. L’idée en 30 secondes
  2. Le problème
  3. La solution
  4. Rate limiting ≠ step limits
  5. Composants du contrôle rate limiting
  6. À quoi ça ressemble dans l’architecture
  7. Exemple
  8. En code, ça ressemble à ça
  9. À quoi ça ressemble pendant l’exécution
  10. Scénario 1 : arrêt sur limite tenant
  11. Scénario 2 : burst-spike
  12. Scénario 3 : exécution normale
  13. Erreurs typiques
  14. Auto-vérification
  15. FAQ
  16. Où se place Rate Limiting dans le système
  17. Pages liées

L’idée en 30 secondes

Rate limiting est un contrôle runtime qui limite la fréquence des appels externes de l’agent pour éviter les pics et les tempêtes de retries en production.

Quand c’est nécessaire : quand un agent appelle souvent des model/tool API, a une logique de retry, et fonctionne sous charge de pointe.

Le problème

Sans rate limiting, un service instable crée vite une chaîne : retry → appel → encore retry. En démo, on le voit peu. En production, ce comportement crée des vagues de 429/5xx, des files d’attente et de la latence.

Le pire : l’incident s’amplifie tout seul :

  • un utilisateur trop actif consomme les quotas
  • un tenant "étouffe" les autres
  • un pic global casse les dépendances pour tout le monde

Et chaque minute sans limite de fréquence ajoute des retries, des files et des délais, jusqu’à ce que le système se DDoS lui-même.

Analogie : c’est comme une rampe de régulation à l’entrée d’une autoroute. Sans dosage du flux, même une bonne route se bloque en quelques minutes.

La solution

La solution est d’ajouter une couche policy rate-limit centralisée dans la runtime et le tool gateway. Chaque appel externe de l’agent est vérifié avec les limites per_user, per_tenant, global et burst_tokens.

La policy renvoie une décision technique : allow ou stop avec raison explicite :

  • rate_limited_user
  • rate_limited_tenant
  • rate_limited_global
  • burst_limited

Quand stop est renvoyé, la runtime retourne retry_after_ms au client et n’exécute pas l’appel. C’est une couche système séparée, pas une partie du prompt ni de la logique du modèle.

Rate limiting ≠ step limits

Ce sont deux niveaux de contrôle différents :

  • Rate limiting limite la fréquence des appels externes.
  • Step limits limitent la longueur et le comportement de la runtime loop.

L’un sans l’autre ne suffit pas :

  • sans rate limiting, les API externes tombent sous les pics et tempêtes de retries
  • sans step limits, un run peut boucler longtemps même avec une fréquence modérée

Exemple :

  • rate limiting : pas plus de per_user=6 appels par 10 secondes
  • step limits : max_steps=18, max_repeat_action=3

Composants du contrôle rate limiting

Ces composants travaillent ensemble sur chaque appel externe de l’agent.

ComposantCe qu’il contrôleMécaniques clésPourquoi
Per-user limitComportement d’un utilisateurper_user quota
sliding window
Empêche un utilisateur de consommer toute la capacité
Per-tenant limitCharge d’un tenantper_tenant quota
tenant-scoped keys
Isole les pics entre clients
Global limitCharge totale du systèmeglobal cap
shared limiter
Protège les dépendances externes contre les pics massifs
Burst controlCourts "spikes" de trafictoken bucket
refill rate
Amortit les sauts immédiats sans arrêt complet
Rate-limit observabilityVisibilité des décisions policyaudit logs
alerts sur les stop spikes
Ne limite pas directement les appels, mais aide à trouver vite la source d’un pic

Exemple d’alerte :

Slack: 🛑 Support-Agent hit rate_limited_tenant. retry_after=1200ms, tenant=t_42.

À quoi ça ressemble dans l’architecture

La couche policy rate-limit se place entre runtime et model/tool API externes. Chaque décision (allow ou stop) est enregistrée dans l’audit log.

Chaque appel externe de l’agent passe par ce flow avant exécution : la runtime n’exécute pas directement l’appel, elle demande d’abord une décision à la policy.

Résumé du flow :

  • Runtime forme un appel externe de l’agent
  • Policy vérifie per_user, per_tenant, global et burst_tokens
  • allow → l’appel est exécuté
  • stopretry_after_ms et réponse partielle sont renvoyés
  • les deux décisions sont écrites dans l’audit log

Exemple

Un agent de support traite beaucoup de requêtes en parallèle et retry crm.search plusieurs fois.

Avec rate limiting :

  • per_user = 6 / 10s
  • per_tenant = 120 / min
  • global = 50 / s
  • burst_tokens = 5

→ le pic est arrêté au niveau policy avant la chute des dépendances et des files.

Rate limiting stoppe l’incident juste avant l’appel externe, pas après une vague de 429.

En code, ça ressemble à ça

Le schéma simplifié ci-dessus montre le flow principal. Point critique : le rate-limit check doit être O(1) et atomique (souvent via Redis/Lua ou équivalent), sinon il devient lui-même un goulot en pic. Après stop(...), la runtime renvoie généralement une réponse partielle au client avec raison explicite et retry_after_ms.

Exemple de configuration rate limit :

YAML
rate_limits:
  per_user_10s: 6
  per_tenant_min: 120
  global_rps: 50
  burst_tokens: 5
  refill_per_second: 2
PYTHON
action = planner.next(state)
action_key = make_action_key(action.name, action.args)

if not action.is_external_call():
    # execute_local — helper conditionnel pour actions locales sans API externe.
    # Decision.allow — helper conditionnel pour garder un modèle unique outcome/reason.
    local_result = execute_local(action)
    local_decision = Decision.allow(reason=None)
    audit.log(
        run_id,
        decision=local_decision.outcome,
        reason=local_decision.reason,
        scope="local",
        action=action.name,
        action_key=action_key,
        result=local_result.status,
    )
    return local_result

decision = rate_limit.check(
    user_id=state.user_id,
    tenant_id=state.tenant_id,
    action=action.name,
    now_ms=clock.now_ms(),
)

if decision.outcome == "stop":
    audit.log(
        run_id,
        decision=decision.outcome,
        reason=decision.reason,
        scope=decision.scope,
        retry_after_ms=decision.retry_after_ms,
        action=action.name,
        action_key=action_key,
    )
    alerts.notify_if_needed(run_id, decision.reason, scope=decision.scope)
    return stop(decision.reason, retry_after_ms=decision.retry_after_ms)

result = executor.execute(action)

audit.log(
    run_id,
    decision=decision.outcome,
    reason=decision.reason,
    scope=decision.scope,
    action=action.name,
    action_key=action_key,
    result=result.status,
)

return result

À quoi ça ressemble pendant l’exécution

Scénario 1 : arrêt sur limite tenant

  1. Runtime forme l’appel externe crm.search.
  2. Policy détecte dépassement du quota per_tenant.
  3. Décision : stop (reason=rate_limited_tenant).
  4. Runtime renvoie retry_after_ms.
  5. L’appel n’est pas exécuté, l’événement est écrit dans l’audit log.

Scénario 2 : burst-spike

  1. Plusieurs runs créent un court pic d’appels au même moment.
  2. Policy épuise burst_tokens.
  3. Décision : stop (reason=burst_limited).
  4. Une partie des appels est rejetée avec retry_after_ms.
  5. Le système reste stable sans cascade-failure.

Scénario 3 : exécution normale

  1. Runtime forme un appel externe.
  2. Policy vérifie les limites : tout est dans les bornes.
  3. Décision : allow.
  4. L’appel est exécuté.
  5. Décision et résultat sont écrits dans l’audit log.

Erreurs typiques

  • ne mettre qu’une limite globale sans isolation per_user/per_tenant
  • ne pas renvoyer retry_after en cas de stop
  • retry sans backoff ni jitter
  • vérifier le rate limit dans une seule couche (seulement runtime ou seulement gateway)
  • ne pas logger les décisions stop (reason, scope, retry_after_ms)
  • pas d’alerting sur les pics rate_limited_*

Résultat : le système semble contrôlé, mais il se dégrade vite sous un vrai pic.

Auto-vérification

Vérification rapide du rate limiting avant lancement en production :

Progression: 0/8

⚠ Les contrôles de governance de base manquent

Avant la production, il faut au minimum le contrôle d'accès, des limites, des audit logs et un arrêt d'urgence.

FAQ

Q : Avec quelles limites commencer ?
A : Minimum : per_user, per_tenant, global + un petit contrôle de burst. Ajuste ensuite selon les événements stop réels.

Q : Si l’API externe renvoie déjà 429, faut-il notre rate limiting ?
A : Oui. Le rate limiting interne protège la runtime avant le 429 externe et fournit des stop reasons, retry_after et audit contrôlés.

Q : Au dépassement de limite, mieux vaut stop ou queue ?
A : En sync-run, stop + retry_after est généralement préférable. En async-pipeline, on peut ajouter une queue, mais toujours avec limites explicites et timeout.

Q : Rate limiting remplace budget controls ?
A : Non. Rate limiting contrôle la fréquence d’appels, budget controls contrôle la dépense totale du run.

Q : Où stocker les compteurs ?
A : Dans un stockage partagé low-latency avec opérations atomiques (souvent Redis). Sans cela, les limites deviennent incohérentes entre instances.

Où se place Rate Limiting dans le système

Rate limiting est une des couches d’Agent Governance. Avec RBAC, budgets, step limits, approval et audit, il forme un système unifié de contrôle d’exécution.

Pages liées

Suite du sujet :

⏱️ 7 min de lectureMis à jour 27 mars 2026Difficulté: ★★★
Implémenter dans OnceOnly
Budgets + permissions you can enforce at the boundary.
Utiliser dans OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
writes:
  require_approval: true
  idempotency: true
controls:
  kill_switch: { enabled: true }
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Permissions outils (allowlist / blocklist)
  • Kill switch & arrêt incident
  • Idempotence & déduplication
  • Audit logs & traçabilité
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.

Auteur

Nick — ingénieur qui construit une infrastructure pour des agents IA en production.

Focus : patterns d’agents, modes de défaillance, contrôle du runtime et fiabilité des systèmes.

🔗 GitHub: https://github.com/mykolademyanov


Note éditoriale

Cette documentation est assistée par l’IA, avec une responsabilité éditoriale humaine pour l’exactitude, la clarté et la pertinence en production.

Le contenu s’appuie sur des défaillances réelles, des post-mortems et des incidents opérationnels dans des systèmes d’agents IA déployés.