Aller au contenu principal

Client asynchrone et performances

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

Le SDK OpenAI propose une version asynchrone du client, compatible avec l’API xAI :

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,
        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

La vraie puissance de l’asynchrone apparaît lorsque vous lancez plusieurs requêtes simultanément :

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 5 prompts, au lieu d’attendre 5 requêtes séquentielles (environ 25 secondes), toutes les images sont générées en parallèle (environ 5-8 secondes).

Contrôler le parallélisme

Lancer trop de requêtes simultanées peut déclencher les rate limits (300 RPM pour le standard, 30 RPM pour le pro). Utilisez un sémaphore pour limiter la concurrence :

async def generate_with_limit(prompts, max_concurrent=10):
    """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)

Pour le modèle standard (300 RPM), vous pouvez utiliser max_concurrent=20 sans problème. Pour le modèle pro (30 RPM), limitez à max_concurrent=5.

Pipeline asynchrone complet

Voici un pipeline qui gère la génération, le téléchargement et la gestion des erreurs :

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=10)

    # É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:
                filepath = os.path.join(output_dir, f"image_{i:03d}.png")
                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

Pour 20 images avec le modèle standard :

  • Synchrone : ~100 secondes (20 requêtes x ~5s chacune)
  • Asynchrone (max_concurrent=10) : ~10-15 secondes
  • Gain : environ 7-10x plus rapide

Gestion des erreurs

En mode asynchrone, gérez les erreurs individuellement pour éviter qu’une requête échouée n’annule toutes les autres :

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 AsyncOpenAI permet les requêtes parallèles via asyncio
  • Utilisez asyncio.gather pour lancer plusieurs générations simultanément
  • Limitez la concurrence avec un sémaphore pour respecter les rate limits
  • Le mode asynchrone est 7-10x plus rapide pour les lots importants
  • Gérez les erreurs individuellement pour ne pas perdre tout le batch