Client asynchrone et performances
Sur cette page
Leçon corrigée le 3 novembre 2026 d’après la page Rate limits de xAI et la référence de l’API Images : la limite des modèles Imagine se compte en requêtes par seconde — 6 au palier d’entrée, 100 au palier le plus haut —, et non « 30 par minute » ; un sémaphore borne les requêtes simultanées, pas le débit ; les images sortent en JPEG par défaut ; et les temps chiffrés de l’ancienne version, qui ne venaient d’aucune mesure publiée, laissent place à un calcul. Sources : Rate limits · Images API.
Optimiser les performances avec l’asynchrone
Lorsque vous devez générer ou éditer de nombreuses images, les appels séquentiels deviennent un goulot d’étranglement. Le client asynchrone de l’API xAI vous permet d’exécuter plusieurs requêtes en parallèle, réduisant considérablement le temps de traitement total.
Le client AsyncOpenAI
L’asynchrone ne rend pas une génération plus rapide : une image demandée seule mettra exactement le même temps. Ce qu’il supprime, c’est l’attente inutile entre les requêtes — pendant que la première image se calcule côté serveur, votre programme peut en demander dix autres au lieu de rester bloqué. C’est pour cela que le gain est nul sur une image et spectaculaire sur cinquante.
from openai import AsyncOpenAI
import asyncio
import os
async_client = AsyncOpenAI(
api_key=os.getenv("XAI_API_KEY"),
base_url="https://api.x.ai/v1"
)
async def generate_image(prompt):
"""Génère une image de manière asynchrone."""
response = await async_client.images.generate(
model="grok-imagine-image",
prompt=prompt,
extra_body={"resolution": "1k"},
)
return response.data[0].url
async def main():
url = await generate_image("Un phare sur une falaise, style aquarelle")
print(url)
asyncio.run(main())
Requêtes parallèles avec asyncio.gather
asyncio.gather lance toutes les requêtes puis attend que l’ensemble soit terminé. La durée totale devient celle de la plus lente, non la somme de toutes — c’est toute la différence, et elle se voit dès trois ou quatre images.
async def generate_batch(prompts):
"""Génère des images pour plusieurs prompts en parallèle."""
tasks = [generate_image(prompt) for prompt in prompts]
results = await asyncio.gather(*tasks)
return results
async def main():
prompts = [
"Portrait d'un ingénieur, style corporate",
"Vue panoramique d'une ville futuriste",
"Diagramme isométrique d'un data center",
"Logo abstrait bleu et argent",
"Illustration d'une équipe en réunion"
]
urls = await generate_batch(prompts)
for prompt, url in zip(prompts, urls):
print(f"{prompt[:40]}... → {url}")
asyncio.run(main())
Avec cinq prompts, au lieu d’attendre la fin de chaque requête avant de lancer la suivante, les cinq images se calculent en même temps : le temps total se rapproche de celui de la requête la plus lente.
Contrôler le parallélisme
Sans limite, gather lance tout d’un coup — et cinquante requêtes simultanées sur un modèle plafonné à 6 requêtes par seconde au palier d’entrée produisent une avalanche de 429 qui vous coûtera plus de temps que l’exécution séquentielle. Le sémaphore n’est donc pas une précaution optionnelle : il est ce qui rend le parallélisme utilisable. Calibrez-le sur le rate limit du modèle, jamais sur la capacité de votre machine.
async def generate_with_limit(prompts, max_concurrent=5):
"""Génère des images avec un nombre maximum de requêtes parallèles."""
semaphore = asyncio.Semaphore(max_concurrent)
async def limited_generate(prompt):
async with semaphore:
return await generate_image(prompt)
tasks = [limited_generate(prompt) for prompt in prompts]
return await asyncio.gather(*tasks)
Les fiches officielles de grok-imagine-image et de grok-imagine-image-2.0 indiquent 6 requêtes par seconde au palier d’entrée, une limite qui grandit avec le palier de votre équipe jusqu’à 100. Attention : un sémaphore borne les requêtes simultanées, pas le débit. Avec max_concurrent=5, vous restez sous 6 requêtes par seconde tant que chaque génération dure plus d’une seconde ; si vos réponses arrivent plus vite, espacez aussi les soumissions. Vérifiez les limites de votre équipe dans la console xAI avant d’augmenter.
Pipeline asynchrone complet
Ce pipeline enchaîne les trois opérations qui doivent aller ensemble : générer, télécharger immédiatement — les URL expirent — et isoler les échecs. Sur un lot important, c’est le troisième point qui fait la différence entre un incident et une perte sèche.
import aiohttp
import aiofiles
async def download_image(session, url, filepath):
"""Télécharge une image de manière asynchrone."""
async with session.get(url) as resp:
if resp.status == 200:
async with aiofiles.open(filepath, "wb") as f:
await f.write(await resp.read())
return filepath
return None
async def pipeline(prompts, output_dir="output"):
"""Pipeline complet : génération + téléchargement."""
os.makedirs(output_dir, exist_ok=True)
# Étape 1 : générer toutes les images en parallèle
urls = await generate_with_limit(prompts, max_concurrent=5)
# Étape 2 : télécharger toutes les images en parallèle
async with aiohttp.ClientSession() as session:
download_tasks = []
for i, url in enumerate(urls):
if url:
# JPEG par défaut (image/jpeg), sauf réglage de qualité produisant du PNG
filepath = os.path.join(output_dir, f"image_{i:03d}.jpg")
download_tasks.append(download_image(session, url, filepath))
saved_files = await asyncio.gather(*download_tasks)
count = sum(1 for f in saved_files if f)
print(f"{count}/{len(prompts)} images générées et sauvegardées")
Comparaison synchrone vs asynchrone
Le gain se calcule sur le papier. Si une génération dure T secondes, vingt images demandées l’une après l’autre prennent environ 20 × T. Avec cinq requêtes simultanées, elles partent par vagues de cinq : environ 4 × T, soit un gain proche de cinq, le nombre de requêtes simultanées — à condition de rester sous la limite de débit. Mesurez T sur votre propre compte avant de promettre un délai : les pages de xAI sur les images ne donnent pas de temps de génération, et il varie avec le modèle, la résolution et la charge.
Gestion des erreurs
Le comportement par défaut de gather est brutal : une exception dans une tâche fait remonter l’erreur et vous perdez les résultats des autres, y compris ceux déjà payés. return_exceptions=True change cela — chaque tâche rend soit son résultat, soit son exception, et vous décidez quoi faire des échecs sans sacrifier les réussites.
async def safe_generate(prompt):
"""Génère une image avec gestion d'erreur."""
try:
return await generate_image(prompt)
except Exception as e:
print(f"Erreur pour '{prompt[:30]}...' : {e}")
return None
Points clés à retenir
- Le client
AsyncOpenAIpermet les requêtes parallèles viaasyncio - Utilisez
asyncio.gatherpour lancer plusieurs générations simultanément - Limitez la concurrence avec un sémaphore pour respecter les rate limits
- Le gain approche le nombre de requêtes simultanées, dans la limite du débit autorisé (6 requêtes par seconde au palier d’entrée)
- Gérez les erreurs individuellement pour ne pas perdre tout le batch