Lesson 5 — Maîtriser l'appel

Gérer les erreurs et réessayer

4 min read

This content is not translated yet. Showing the original version.

Gérer les erreurs et réessayer

Les exceptions du SDK

Quand l'API répond par une erreur, google-genai lève une exception de google.genai.errors :

ExceptionCodes HTTPExemples
errors.ClientError4xx : votre requête pose problème400 clé invalide, 404 modèle inconnu, 429 quota
errors.ServerError5xx : le service a un souci500 erreur interne, 503 surcharge, 504 délai dépassé
errors.APIErrorclasse mère des deuxattribut e.code (le code HTTP)
flowchart TD
  A["Appel"] --> E{"Exception ?"}
  E -->|"non"| OK["Résultat"]
  E -->|"code 429, 500, 502, 503, 504"| T{"Essais restants ?"}
  T -->|"oui"| W["Attendre 5 s, 10 s, 20 s..."] --> A
  T -->|"non"| R["Relancer l'exception"]
  E -->|"autre code (400, 403, 404)"| R

Seul 429 parmi les 4xx est temporaire : le quota se reconstitue avec le temps. Tous les autres 4xx demandent une correction (clé, nom de modèle, format de requête).

La fonction de réessai

files/reunion.py
def avec_reessais(appel, essais=4, attente=5):
    """Réessaie sur 429 (quota) et 5xx (serveur), avec attente exponentielle."""
    for essai in range(essais):
        try:
            return appel()
        except errors.APIError as e:
            if e.code not in (429, 500, 502, 503, 504) or essai == essais - 1:
                raise
            pause = attente * 2 ** essai
            print(f"[erreur {e.code}, nouvel essai dans {pause} s]", file=sys.stderr)
            time.sleep(pause)

On lui passe une fonction sans argument (lambda: client.models.count_tokens(...)) : la même logique protège tous les appels du CLI. L'attente exponentielle (5, 10, 20 s) laisse au service le temps de récupérer, au lieu de le marteler.

Le délai maximal

Sans délai, une requête qui ne répond jamais bloque le programme indéfiniment. Le client du lab fixe une limite :

files/reunion.py
def make_client() -> genai.Client:
    """Le même code sert les deux modes : le SDK lit les variables d'environnement.
    Le délai (en millisecondes) évite qu'une requête bloquée fige le programme."""
    return genai.Client(http_options=types.HttpOptions(timeout=90_000))

Quelles erreurs avec_reessais réessaie-t-elle ?

Pourquoi l'attente double-t-elle à chaque essai ?

Résultat obtenu

Les tests sans réseau simulent les trois situations (extrait de pytest -v, 2026-10-15) :

pytest -v (extrait)
test_reunion.py::test_reessais_sur_503_puis_succes PASSED
test_reunion.py::test_pas_de_reessai_sur_cle_invalide PASSED
test_reunion.py::test_abandon_apres_n_essais PASSED

Et deux vraies erreurs provoquées volontairement en mode clé d'API, pour reconnaître les messages :

sortie
400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'API key not valid. Please pass a valid API key.', ...}}
404 NOT_FOUND. {'error': {'code': 404, 'message': 'models/gemini-modele-inexistant is not found for API version v1beta, ...'}}

Exercice

Écrivez le test qui vérifie que avec_reessais attend bien 5 puis 10 secondes avant de réussir au troisième essai, sans vraiment attendre.

Voir la solution
files/test_reunion.py (ajout)
def test_attentes_exponentielles(monkeypatch):
    pauses = []
    monkeypatch.setattr(reunion.time, "sleep", pauses.append)  # enregistre au lieu d'attendre
    panne = errors.ServerError(503, {"error": {"message": "x", "status": "UNAVAILABLE"}})
    client = FakeClient(panne, panne, NS(total_tokens=1))
    assert reunion.tokens(client, "x") == 1
    assert pauses == [5, 10]

monkeypatch.setattr remplace time.sleep par pauses.append le temps du test : chaque « attente » ajoute sa durée à la liste. On vérifie ainsi le calcul sans perdre 15 secondes.

Je sais faire

  • Distinguer ClientError et ServerError et lire e.code
  • Réessayer seulement les erreurs temporaires, avec attente exponentielle et nombre d'essais maximal
  • Fixer un délai maximal avec HttpOptions(timeout=...)
  • Tester une logique de réessai sans réseau ni attente réelle
Next