Aller au contenu principal

Gestion d'erreurs et codes HTTP

Mis à jour le 29 juillet 2026

Gestion d’erreurs et codes HTTP

En production, les erreurs sont inévitables. Le réseau tombe, la clé expire, un collègue déploie un paramètre invalide, l’API elle-même connaît une panne de trois minutes. Comprendre les codes HTTP retournés par l’API et savoir les gérer correctement est essentiel pour construire des applications robustes. Toute la difficulté tient en une question que vous devrez poser à chaque exception : celle-ci va-t-elle disparaître si je réessaie, ou est-ce que je vais boucler indéfiniment sur un problème que seul un humain peut corriger ?

Les codes HTTP de l’API OpenAI

L’API retourne des codes HTTP standards pour indiquer le résultat de chaque requête. Le SDK Python ne vous laisse pas manipuler ces codes à la main : il traduit chacun d’eux en une exception typée que vous pouvez intercepter séparément. C’est ce qui vous permet d’écrire des blocs except précis plutôt qu’un except Exception fourre-tout qui masquerait la nature du problème.

from openai import OpenAI
from openai import (
    APIError,
    AuthenticationError,
    BadRequestError,
    RateLimitError,
    APIConnectionError,
    InternalServerError,
    PermissionDeniedError,
    NotFoundError,
    UnprocessableEntityError,
)

client = OpenAI()

401 — AuthenticationError

Le 401 signale que la clé API est invalide ou manquante. C’est typiquement ce que vous voyez au premier déploiement, quand la variable d’environnement n’a pas été propagée au conteneur. Réessayer ne sert strictement à rien : la clé sera tout aussi invalide dans dix secondes.

try:
    # Clé API invalide ou manquante
    bad_client = OpenAI(api_key="sk-invalid")
    bad_client.responses.create(
        model="gpt-5.6-terra",
        input="Test"
    )
except AuthenticationError as e:
    print(f"Code : {e.status_code}")  # 401
    print(f"Message : {e.message}")
    # Action : vérifier la clé API, la régénérer si nécessaire

400 — BadRequestError

Le 400 vous dit que la requête est mal formée. Ici, une température de 5.0 alors que le maximum accepté est 2.0. Le message d’erreur nomme presque toujours le paramètre fautif, ce qui en fait l’erreur la plus rapide à corriger — à condition de la logger correctement plutôt que de l’avaler silencieusement.

try:
    # Paramètres invalides
    client.responses.create(
        model="gpt-5.6-terra",
        input="Test",
        temperature=5.0  # Valeur invalide (max 2.0)
    )
except BadRequestError as e:
    print(f"Code : {e.status_code}")  # 400
    print(f"Message : {e.message}")
    # Action : corriger les paramètres de la requête

429 — RateLimitError

Le 429 indique que vous envoyez trop de requêtes, ou que votre quota est dépassé. Contrairement aux deux précédentes, cette erreur est temporaire par nature : le compteur se réinitialise. C’est le cas d’école du retry, mais d’un retry qui attend — et qui attend de plus en plus longtemps à chaque tentative.

try:
    # Trop de requêtes ou quota dépassé
    client.responses.create(
        model="gpt-5.6-terra",
        input="Test"
    )
except RateLimitError as e:
    print(f"Code : {e.status_code}")  # 429
    print(f"Message : {e.message}")
    # Action : attendre et réessayer avec backoff exponentiel

500/503 — InternalServerError

Les 500 et 503 viennent de chez OpenAI, pas de chez vous. Votre requête était valide, l’infrastructure n’a simplement pas pu la traiter. Réessayez après un délai : dans l’immense majorité des cas, la deuxième ou la troisième tentative passe.

try:
    client.responses.create(
        model="gpt-5.6-terra",
        input="Test"
    )
except InternalServerError as e:
    print(f"Code : {e.status_code}")  # 500 ou 503
    print(f"Message : {e.message}")
    # Action : réessayer après un délai, l'erreur est côté OpenAI

Gestionnaire d’erreurs complet

Rassemblons maintenant ces comportements dans une seule fonction. La logique est celle du tri : les erreurs fatales sont converties en RuntimeError et propagées immédiatement, les erreurs transitoires déclenchent une attente doublée à chaque tour, et si le budget de tentatives s’épuise, l’appel échoue proprement plutôt que de rendre une valeur vide que l’appelant interpréterait mal.

from openai import OpenAI, APIError, AuthenticationError, RateLimitError
from openai import BadRequestError, APIConnectionError, InternalServerError
import time

client = OpenAI()

def appel_api_robuste(prompt: str, modele: str = "gpt-5.6-terra",
                      max_retries: int = 3) -> str:
    """Appel API avec gestion complète des erreurs."""

    for tentative in range(max_retries):
        try:
            response = client.responses.create(
                model=modele,
                input=prompt
            )
            return response.output_text

        except AuthenticationError:
            # Ne pas réessayer — la clé est invalide
            raise RuntimeError("Clé API invalide. Vérifiez OPENAI_API_KEY.")

        except BadRequestError as e:
            # Ne pas réessayer — la requête est mal formée
            raise RuntimeError(f"Requête invalide : {e.message}")

        except RateLimitError:
            # Réessayer avec backoff exponentiel
            delai = 2 ** tentative
            print(f"Rate limit atteint. Attente {delai}s...")
            time.sleep(delai)

        except InternalServerError:
            # Réessayer — erreur côté serveur
            delai = 2 ** tentative
            print(f"Erreur serveur. Tentative {tentative + 1}/{max_retries}...")
            time.sleep(delai)

        except APIConnectionError:
            # Réessayer — problème réseau
            delai = 2 ** tentative
            print(f"Erreur connexion. Tentative {tentative + 1}/{max_retries}...")
            time.sleep(delai)

    raise RuntimeError(f"Échec après {max_retries} tentatives")

# Utilisation
try:
    resultat = appel_api_robuste("Bonjour !")
    print(resultat)
except RuntimeError as e:
    print(f"Erreur fatale : {e}")

Erreurs spécifiques à la Responses API

Deux situations méritent un traitement à part parce qu’elles se présentent sous un code générique tout en appelant une correction très précise.

Contexte trop long

Un prompt qui dépasse la fenêtre de contexte remonte en 400, comme une faute de frappe sur un paramètre. Pour distinguer les deux, inspectez le message : la présence de context_length_exceeded vous oriente vers une troncature du texte ou un changement de modèle plutôt que vers une relecture de vos paramètres.

try:
    response = client.responses.create(
        model="gpt-5.6-terra",
        input="x " * 300000  # Dépasse la fenêtre de contexte
    )
except BadRequestError as e:
    if "context_length_exceeded" in str(e.message):
        print("Le prompt est trop long pour ce modèle.")
        print("Solutions : tronquer le texte, ou basculer sur un modèle")
        print("dont la fenêtre de contexte est plus large.")

Modèle indisponible

Les identifiants de modèles ne sont pas éternels. Un déploiement qui référence encore un modèle retiré échoue en 404, et l’application entière tombe alors qu’elle fonctionnait la veille. Prévoyez ce cas explicitement : c’est le rappel automatique qu’il faut mettre à jour la configuration.

try:
    response = client.responses.create(
        model="gpt-4o",  # Modèle retiré !
        input="Test"
    )
except NotFoundError as e:
    print(f"Modèle non trouvé : {e.message}")
    print("Utilisez gpt-5.6-terra ou gpt-5.6-sol à la place.")

Logging structuré des erreurs

Une erreur non tracée est une erreur que vous découvrirez par le ticket d’un client. Loguer en JSON plutôt qu’en texte libre change tout dès que vous cherchez à agréger : vous pouvez compter les 429 par heure, filtrer sur un response_id ou tracer une dérive de consommation, ce qui est impossible avec des lignes de log rédigées à la main.

import logging
import json
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("openai_client")

def appel_avec_log(prompt: str) -> str:
    """Appel API avec logging structuré."""
    request_id = None

    try:
        response = client.responses.create(
            model="gpt-5.6-terra",
            input=prompt
        )

        logger.info(json.dumps({
            "event": "api_success",
            "model": response.model,
            "tokens": response.usage.total_tokens,
            "response_id": response.id,
            "timestamp": datetime.now().isoformat()
        }))

        return response.output_text

    except APIError as e:
        logger.error(json.dumps({
            "event": "api_error",
            "status_code": e.status_code,
            "message": e.message,
            "timestamp": datetime.now().isoformat()
        }))
        raise

resultat = appel_avec_log("Bonjour !")

Tableau récapitulatif

Gardez cette grille sous la main : elle résume la décision à prendre pour chaque code.

CodeExceptionRéessayer ?Action
400BadRequestErrorNonCorriger la requête
401AuthenticationErrorNonVérifier la clé API
403PermissionDeniedErrorNonVérifier les permissions
404NotFoundErrorNonVérifier le modèle/endpoint
422UnprocessableEntityErrorNonCorriger le format
429RateLimitErrorOuiBackoff exponentiel
500InternalServerErrorOuiAttendre et réessayer
503InternalServerErrorOuiService temporairement indisponible

Points clés à retenir

  • Catégorisez les erreurs : réessayables (429, 500, 503) vs fatales (400, 401, 403)
  • Utilisez le backoff exponentiel pour les erreurs réessayables
  • Ne réessayez jamais les erreurs d’authentification ou de requête invalide
  • Loguez chaque erreur avec contexte (code, message, timestamp)
  • Le SDK gère automatiquement 2 retries — configurez max_retries si besoin