Backoff exponentiel sur HTTP 429
Comprendre l’erreur 429
L’erreur HTTP 429 signifie que vous avez depasse les limites de debit (rate limits) de l’API. Le serveur xAI refuse temporairement vos requetes pour proteger l’infrastructure. Cette erreur est normale dans un contexte de traitement par lots : elle indique simplement que vous envoyez des requetes trop rapidement.
La reaction appropriee n’est pas d’abandonner, mais d’attendre avant de reessayer. Le backoff exponentiel est la strategie standard pour gerer cette situation.
Principe du backoff exponentiel
Au lieu de reessayer immediatement (ce qui provoquerait un nouveau 429), vous attendez un delai croissant entre chaque tentative :
- 1er echec : attendre 1 seconde
- 2e echec : attendre 2 secondes
- 3e echec : attendre 4 secondes
- 4e echec : attendre 8 secondes
- 5e echec : abandonner
Le delai double a chaque tentative, d’ou le terme “exponentiel”. Cela laisse le temps au serveur de retrouver de la capacite.
Implementation manuelle
import asyncio
import random
from openai import RateLimitError
async def request_with_backoff(
prompt: str,
max_retries: int = 5,
base_delay: float = 1.0
) -> str:
for attempt in range(max_retries):
try:
response = await client.chat.completions.create(
model="grok-4",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
except RateLimitError:
if attempt == max_retries - 1:
raise # dernier essai, on propage l'erreur
delay = base_delay * (2 ** attempt)
jitter = random.uniform(0, delay * 0.1)
wait_time = delay + jitter
print(f"Rate limit atteint, tentative {attempt + 1}/{max_retries}. "
f"Attente de {wait_time:.1f}s")
await asyncio.sleep(wait_time)
Le jitter (variation aleatoire) est important. Si plusieurs coroutines recoivent un 429 au meme moment, sans jitter elles retenteraient toutes exactement en meme temps, provoquant un nouveau 429. Le jitter desynchronise les tentatives.
Utilisation avec la bibliotheque tenacity
Pour un usage en production, la bibliotheque tenacity simplifie l’implementation :
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type
)
from openai import RateLimitError
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=1, max=60),
retry=retry_if_exception_type(RateLimitError)
)
async def robust_request(prompt: str) -> str:
response = await client.chat.completions.create(
model="grok-4",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
Le decorateur @retry intercepte les RateLimitError, attend un delai exponentiel (entre 1 et 60 secondes), et reessaye jusqu’a 5 fois. Le code de la fonction reste propre et lisible.
Combiner backoff et semaphore
En pratique, vous combinez le backoff avec un semaphore pour un controle complet :
async def resilient_batch(prompts: list[str], max_concurrent: int = 5) -> list[str]:
sem = Semaphore(max_concurrent)
async def single_request(prompt: str) -> str:
async with sem:
return await request_with_backoff(prompt)
tasks = [single_request(p) for p in prompts]
return await asyncio.gather(*tasks, return_exceptions=True)
Le semaphore empeche de surcharger l’API, et le backoff gere les erreurs 429 residuelles. Cette double protection est la configuration recommandee pour la production.
Adapter le backoff au type d’erreur
Toutes les erreurs ne justifient pas un retry. Voici une classification :
from openai import RateLimitError, APITimeoutError, APIConnectionError, BadRequestError
async def smart_retry(prompt: str, max_retries: int = 5) -> str:
for attempt in range(max_retries):
try:
return await client.chat.completions.create(
model="grok-4",
messages=[{"role": "user", "content": prompt}]
)
except RateLimitError:
delay = 2 ** attempt
await asyncio.sleep(delay)
except (APITimeoutError, APIConnectionError):
delay = 2 ** attempt
await asyncio.sleep(delay)
except BadRequestError:
raise # erreur dans le prompt, retry inutile
Les erreurs 429 (rate limit), les timeouts et les erreurs de connexion meritent un retry. En revanche, une BadRequestError (prompt invalide) ne sera pas resolue en reessayant.
Points cles a retenir
- L’erreur 429 est normale et attendue lors du traitement par lots
- Le backoff exponentiel double le delai entre chaque tentative (1s, 2s, 4s, 8s…)
- Le jitter desynchronise les retentatives pour eviter les collisions
- La bibliotheque
tenacitysimplifie l’implementation en production - Combinez toujours semaphore et backoff pour une resilience maximale
- Ne retentez pas les erreurs de requete invalide (
BadRequestError)