Aller au contenu principal

Semaphore pour limiter les requetes simultanees

Le probleme de la concurrence non controlee

Envoyer toutes vos requetes en parallele sans limitation est une erreur courante. Si vous avez 200 questions a traiter et que vous les lancez toutes simultanement, vous allez saturer les rate limits de l’API, recevoir des erreurs HTTP 429, et potentiellement voir votre cle API temporairement bloquee.

Semaphore pour limiter les requetes concurrentes

Le semaphore est la solution standard pour controler le nombre de requetes simultanees. C’est un compteur qui autorise un nombre maximal de coroutines a s’executer en parallele.

5
Requetes paralleles recommandees
429
Erreur HTTP rate limit
RPM
Requetes par minute
TPM
Tokens par minute

Fonctionnement du semaphore

asyncio.Semaphore agit comme un verrou a compteur. Quand une coroutine entre dans le bloc async with semaphore, le compteur diminue. Quand elle en sort, le compteur remonte. Si le compteur est a zero, les coroutines suivantes attendent qu’une place se libere.

import asyncio
from asyncio import Semaphore

semaphore = Semaphore(5)  # maximum 5 requetes simultanees

async def process_request(request: str):
    async with semaphore:
        # Cette section est limitee a 5 executions paralleles
        chat = client.chat.create(
            model="grok-4.20-reasoning",
            max_tokens=100
        )
        chat.append(user(request))
        return await chat.sample()

Dans cet exemple, meme si vous lancez 100 coroutines avec asyncio.gather, seules 5 s’executeront simultanement. Les 95 autres attendront leur tour.

Pattern complet avec le SDK OpenAI

Voici le meme pattern avec AsyncOpenAI, qui est souvent prefere pour sa compatibilite :

from openai import AsyncOpenAI
import httpx

client = AsyncOpenAI(
    api_key=os.getenv("XAI_API_KEY"),
    base_url="https://api.x.ai/v1",
    timeout=httpx.Timeout(3600.0)
)

async def send_request(sem: Semaphore, request: str) -> dict:
    async with sem:
        return await client.chat.completions.create(
            model="grok-4.20-reasoning",
            messages=[{"role": "user", "content": request}]
        )

async def process_all(requests: list[str], max_concurrent: int = 5):
    sem = Semaphore(max_concurrent)
    tasks = [send_request(sem, r) for r in requests]
    return await asyncio.gather(*tasks)

Le parametre max_concurrent est configurable. Vous le definissez en fonction de vos rate limits (lecon 7).

Choisir la bonne valeur de semaphore

La valeur optimale du semaphore depend de trois facteurs :

  • Vos rate limits RPM : si votre tier autorise 60 RPM (requetes par minute), un semaphore de 5 genere au maximum 5 requetes simultanees. Si chaque requete dure 10 secondes, vous envoyez environ 30 requetes par minute, ce qui reste sous la limite.
  • La duree moyenne des requetes : les modeles de raisonnement prennent plus de temps. Ajustez le semaphore a la baisse pour ces modeles.
  • Votre tolerance aux erreurs : commencez bas (3-5) et augmentez progressivement jusqu’a trouver le bon equilibre.

Calcul approximatif

Requetes par minute = semaphore * (60 / duree_moyenne_secondes)

Exemple : semaphore=5, duree=10s → 5 * (60/10) = 30 RPM
Exemple : semaphore=10, duree=5s → 10 * (60/5) = 120 RPM

Erreur courante : semaphore global vs local

# MAUVAIS : nouveau semaphore a chaque appel
async def bad_request(request: str):
    sem = Semaphore(5)  # chaque appel cree son propre semaphore
    async with sem:
        return await client.chat.completions.create(...)

# BON : semaphore partage entre tous les appels
sem = Semaphore(5)

async def good_request(request: str):
    async with sem:
        return await client.chat.completions.create(...)

Le semaphore doit etre partage entre toutes les coroutines. S’il est cree localement dans chaque fonction, chaque coroutine aura son propre semaphore et la limitation ne fonctionnera pas.

Points cles a retenir

  • Le semaphore limite le nombre de requetes API simultanees pour respecter les rate limits
  • Utilisez async with semaphore pour proteger les appels API
  • Commencez avec une valeur de 3 a 5 et ajustez selon vos limites RPM
  • Le semaphore doit etre partage (global ou passe en parametre), jamais cree localement
  • La formule RPM = semaphore * (60 / duree_moyenne) aide a dimensionner la valeur