Les erreurs d'une API Embedding sont plus discrètes que celles d'une interface de chat. Un échec de requête de chat se voit immédiatement par l'utilisateur ; l'embedding tourne souvent dans un pipeline d'indexation hors ligne, et un 400 ou un 429 peut simplement laisser quelques lignes manquantes dans un lot de documents, jusqu'à ce que la qualité de recherche baisse plusieurs jours plus tard.

Ne commencez pas le diagnostic par « essayons un autre modèle ». L'ordre est le suivant : statut et type d'erreur, taille du corps et lot, puis vérification des vecteurs eux-mêmes. Nous détaillons ci-dessous les 401, 413, 429, les dimensions incompatibles et les vecteurs vides, avec du code de traitement réutilisable.

Remarque : OpenAI, DeepSeek et les autres noms cités sont des marques de leurs détenteurs respectifs. Les limites d'interface prennent ici l'exemple de la documentation publique de l'interface embeddings d'OpenAI : une entrée de 8192 tokens au maximum, 2048 éléments par requête au maximum, et un total de 300 000 tokens par requête ; les limites des éditeurs et des passerelles peuvent différer, faites foi sur la documentation réelle.

1. Regrouper d'abord par type d'erreur

Les erreurs Embedding se répartissent en sept catégories. Classez avant de décider s'il faut modifier le code, les lots ou les données.

Symptôme Statuts fréquents Cause typique À vérifier en premier
Échec d'authentification 401 ou 403 Clé erronée, en-tête absent, clé désactivée, droits ou région incorrects En-tête Authorization et origine de la clé
Entrée trop longue 400 ou 413 Entrée au-delà de la limite du modèle, corps au-delà de la limite de la passerelle Tokens par entrée et octets du corps
Lot trop volumineux 400 ou 413 Nombre d'éléments ou total de tokens dépassé Nombre d'éléments et total de tokens
Limitation de débit 429 Pic de concurrence, clé partagée, tempête de reprises Débit, concurrence et Retry-After
Panne amont 5xx Surcharge ou incident temporaire du serveur Identifiant de requête et horodatage
Dimensions incompatibles 400 ou exception client Changement de modèle, de dimension ou index à dimension fixe Nom du modèle, dimension et configuration de l'index
Vecteur vide ou NaN 200 mais données anormales Texte vide, erreur d'analyse, division par zéro à la normalisation Longueur, valeurs finies et norme du vecteur

Les journaux doivent au minimum contenir l'heure, le statut, le type d'erreur, le nom du modèle, la taille du lot, la durée et l'identifiant de requête. N'enregistrez jamais la clé complète et n'imprimez pas le texte intégral.

2. 401 et 403 : vérifier d'abord l'en-tête

Un 401 signifie que l'identité n'a pas été validée ; un 403 que l'identité est validée mais que l'accès est refusé. Dans les deux cas, ne soupçonnez pas d'abord les capacités du modèle.

Causes fréquentes : l'en-tête n'utilise pas Authorization: Bearer <key> ; la clé est entourée d'espaces ou de guillemets ; la variable d'environnement n'est pas chargée ; la clé a été supprimée ou désactivée ; le Base URL et la clé n'appartiennent pas à la même plateforme ; un reverse proxy supprime l'en-tête d'authentification. Pour un 403, vérifiez aussi les droits d'accès au modèle, les droits du projet, la région et la liste blanche d'IP.

Envoyez d'abord avec curl une requête minimale à entrée courte et confirmez un 200, puis réintroduisez progressivement lots, concurrence et proxy. Si le 401 persiste, comparez avec une nouvelle clé pour distinguer le problème de clé d'un problème de configuration de passerelle.

3. 413 et entrée trop longue : identifier d'abord la source de la limite

Un 413 indique généralement un corps de requête trop volumineux, éventuellement à cause de la passerelle ou du reverse proxy, et pas nécessairement d'un refus du modèle. La limite d'entrée du modèle peut, elle, renvoyer un 400. Les deux cas doivent être distingués.

Pour l'interface embeddings d'OpenAI : une entrée de 8192 tokens au maximum, un tableau de 2048 éléments au maximum, et un total de 300 000 tokens par requête. Le dépassement du total renvoie généralement un 400, même si chaque entrée reste sous la limite. La passerelle peut aussi imposer une taille maximale de corps, renvoyée en 413 avant même d'atteindre le modèle.

Pour traiter une entrée trop longue, procédez dans cet ordre :

  1. Calculez les tokens avec le tokenizer du modèle concerné, sans estimer au nombre de caractères.
  2. Découpez selon les titres, les paragraphes et les frontières sémantiques, avec un léger recouvrement.
  3. Tronquez les fragments trop longs en dernier recours, en consignant la longueur d'origine et la position de coupe.
  4. Pour les lots, limitez à la fois le nombre d'éléments et le total de tokens, avec 10 % à 20 % de marge.
  5. En cas de 413, découpez d'abord le corps ; en cas de 400, vérifiez d'abord le contenu d'une entrée et les paramètres.

Attention aussi aux chaînes vides. L'interface embeddings d'OpenAI n'accepte pas d'entrée vide : les paragraphes devenus blancs après nettoyage doivent être filtrés avant l'envoi.

4. 429 : limitation, concurrence et tempête de reprises

Un 429 signale un dépassement de débit, de concurrence ou de quota, pas nécessairement un solde insuffisant. Déclencheurs fréquents : plusieurs services partagent une clé, des tâches par lots démarrent toutes à l'heure pile, les reprises n'ont pas de backoff, des requêtes longues consomment une part importante du quota.

Regardez d'abord si la réponse contient Retry-After et respectez-le ; sinon, utilisez un backoff exponentiel avec gigue aléatoire et fixez un nombre maximal de tentatives. La reprise ne convient qu'aux erreurs transitoires : un 401, un 403, un 400 et la plupart des 413 ne réussiront jamais, quel que soit le nombre de tentatives.

Côté ingénierie, vous pouvez aussi : séparer les clés par projet ; ajouter une file et un plafond de concurrence aux tâches par lots ; décaler les grosses tâches ; envoyer les échecs dans une file de messages morts. Limitez les reprises à trois ou cinq tentatives, puis déclenchez une alerte plutôt que de boucler indéfiniment.

5. Dimensions incompatibles : l'erreur la plus souvent négligée

Une incompatibilité de dimensions peut se manifester par une erreur de base de données, par exemple « expected dimension 1536, got 1024 », par une erreur de forme numpy, par une erreur de diffusion lors du calcul de similarité cosinus, ou simplement par des résultats de recherche soudainement sans rapport.

Causes fréquentes : le modèle a changé mais les anciens vecteurs restent dans l'index ; le paramètre dimensions a été modifié ; un modèle de même nom a des dimensions différentes selon l'éditeur ou la version ; l'index a été créé avec une dimension fixe ; quelqu'un a tronqué ou complété des vecteurs à la main. Une dimension identique ne prouve pas la compatibilité : les espaces vectoriels de modèles différents ne le sont pas.

La correction n'est pas de compléter par des zéros, mais de reconstruire. Enregistrez pour chaque vecteur le nom du modèle, la dimension, le mode de normalisation, la mesure de distance et la version de découpage ; un index ne contient que les vecteurs d'un même modèle et d'une même version ; en cas de changement, créez une nouvelle collection, recalculez tout, puis basculez après validation. Vérifiez les dimensions à l'écriture comme à la lecture.

6. Vecteurs vides, vecteurs nuls et NaN

Un statut 200 ne signifie pas que les données sont exploitables. Les vecteurs vides, entièrement nuls ou NaN rendent le calcul de similarité invalide, souvent sans lever d'exception.

Sources fréquentes : texte devenu blanc après nettoyage ; tableau d'entrée vide ; erreur d'analyse de la réponse laissant embedding sous forme de liste vide ; modèle local renvoyant un vecteur nul pour une entrée vide ; division par zéro à la normalisation produisant des NaN ; décodage base64 incorrect.

Avant l'indexation, effectuez au moins trois contrôles : la longueur correspond-elle à la dimension attendue ; chaque élément est-il un nombre fini ; la norme du vecteur dépasse-t-elle un seuil très faible. Placez les enregistrements en échec dans une table de quarantaine avec l'identifiant du texte source et la raison, puis relancez après correction ; ne les supprimez pas silencieusement.

7. Un code de gestion d'erreurs réutilisable

L'exemple ci-dessous cible une interface compatible OpenAI et couvre le backoff sur limitation, la division des lots et la validation des vecteurs. Adaptez le nom de modèle, la dimension et le Base URL à votre configuration.

import logging
import math
import random
import time

from openai import OpenAI, APIStatusError, APITimeoutError, RateLimitError

logger = logging.getLogger("embedding")
client = OpenAI(
    api_key="sk-votre-cle",
    base_url="https://api.zhongshangqihang.com/v1",
    max_retries=0,  # désactive les reprises automatiques du SDK pour éviter la superposition
)

MODEL = "text-embedding-3-small"
EXPECTED_DIM = 1536


def vector_is_valid(vec: list[float]) -> bool:
    if len(vec) != EXPECTED_DIM:
        return False
    if not all(math.isfinite(x) for x in vec):
        return False
    norm = math.sqrt(sum(x * x for x in vec))
    return norm > 1e-6


def embed_batch(texts: list[str], attempt: int = 0) -> list[list[float] | None]:
    # ne pas envoyer les textes vides : None conserve l'alignement des indices
    pairs = [(i, t) for i, t in enumerate(texts) if t and t.strip()]
    if not pairs:
        return [None] * len(texts)
    indices = [i for i, _ in pairs]
    payload = [t for _, t in pairs]

    try:
        resp = client.embeddings.create(model=MODEL, input=payload, timeout=60)
        items = sorted(resp.data, key=lambda item: item.index)
        vectors = [item.embedding for item in items]
        if len(vectors) != len(payload):
            raise ValueError(f"Nombre de vecteurs incohérent : {len(vectors)} != {len(payload)}")
        for index, vec in enumerate(vectors):
            if not vector_is_valid(vec):
                raise ValueError(f"Vecteur invalide : index={index}, dim={len(vec)}")
        result: list[list[float] | None] = [None] * len(texts)
        for source_index, vec in zip(indices, vectors):
            result[source_index] = vec
        return result
    except (RateLimitError, APITimeoutError) as exc:
        if attempt >= 5:
            raise
        wait = min(2 ** attempt, 30) + random.random()
        logger.warning("Limitation ou délai dépassé, nouvelle tentative dans %.1f s : %s", wait, exc)
        time.sleep(wait)
        return embed_batch(texts, attempt + 1)
    except APIStatusError as exc:
        if exc.status_code in (400, 413) and len(texts) > 1:
            mid = len(texts) // 2
            logger.warning("Lot trop volumineux, division en %d + %d", mid, len(texts) - mid)
            return embed_batch(texts[:mid], attempt) + embed_batch(texts[mid:], attempt)
        if exc.status_code >= 500 and attempt < 5:
            wait = min(2 ** attempt, 30) + random.random()
            logger.warning("Erreur amont, nouvelle tentative dans %.1f s : %s", wait, exc)
            time.sleep(wait)
            return embed_batch(texts, attempt + 1)
        raise

Le code fait deux compromis. D'abord, les 400 participent aussi à la division des lots, car certaines passerelles signalent un « total de tokens dépassé » en 400 ; si l'échec persiste sur une entrée unique, l'exception est levée. Ensuite, la validation des vecteurs a lieu avant l'écriture : les données invalides n'entrent pas dans l'index. En production, consignez aussi l'identifiant de requête, le nombre de tentatives et le lot en échec.

8. Ordre de diagnostic et liste de supervision

Face à une erreur Embedding, procédez dans cet ordre, généralement plus efficace que de changer de modèle :

  1. Consignez le statut, le type d'erreur, l'identifiant de requête, le modèle, la taille du lot et la durée.
  2. Pour un 401 ou 403, vérifiez l'en-tête, la clé, les variables d'environnement et la plateforme.
  3. Pour un 400, vérifiez les tokens par entrée, le total, les chaînes vides et le format des paramètres.
  4. Pour un 413, vérifiez les octets du corps et les limites de la passerelle, puis divisez la requête.
  5. Pour un 429, consultez Retry-After, appliquez un backoff, réduisez la concurrence et vérifiez le partage de clé.
  6. Pour un 5xx, effectuez un nombre limité de reprises avec backoff ; si le problème persiste, contactez le fournisseur avec l'identifiant de requête.
  7. Si le statut est 200 mais le résultat anormal, vérifiez dimension, norme, NaN et normalisation.
  8. Intégrez ces indicateurs à la supervision et exécutez régulièrement une régression sur un jeu d'évaluation fixe.

La supervision compte chaque heure les 401, 400, 413, 429 et 5xx, ainsi que les vecteurs vides et les anomalies de dimension. En cas de pic du taux d'erreur, vérifiez d'abord s'il se concentre sur un projet, une clé, un modèle ou un lot de documents, puis décidez de réduire la concurrence, diviser les lots, revenir en arrière ou corriger les données.

Questions fréquentes

413 et 400 signalent tous deux une entrée trop longue : quelle différence ?

Le 413 vient le plus souvent d'une limite de taille du corps imposée par la passerelle ou le reverse proxy, avant même d'atteindre le modèle ; le 400 vient plutôt des paramètres ou de la limite de tokens de l'interface. Examinez le corps de réponse et l'identifiant de requête, puis décidez s'il faut découper la requête ou le texte.

Une entrée ne dépasse pas 8192 tokens, pourquoi un 400 ?

Le total de tokens de la requête peut dépasser la limite, ou le tableau d'entrée peut contenir trop d'éléments, une chaîne vide, un paramètre mal formé ou un nom de modèle incorrect. Réduisez le lot, filtrez les textes vides et vérifiez chaque champ du corps.

Combien de tentatives pour un 429 ?

Trois à cinq est une pratique courante, avec backoff exponentiel et gigue aléatoire, en respectant Retry-After en priorité. Au-delà, mettez en file ou déclenchez une alerte, sans boucler indéfiniment. En cas de limitation sévère, réduire la concurrence est plus efficace qu'augmenter les reprises.

Une incompatibilité de dimensions peut-elle se corriger en complétant par des zéros ?

Non. Le complément par zéros modifie la direction du vecteur et la similarité, rendant la recherche non fiable. La bonne méthode consiste à unifier modèle et dimension, à construire un nouvel index avec un recalcul complet, puis à basculer après validation.

Pourquoi l'interface renvoie-t-elle 200 avec un vecteur vide ?

L'entrée peut être vide ou devenue blanche après nettoyage, ou bien l'analyse de la réponse, le décodage base64 ou l'inférence locale a échoué. Avant l'indexation, validez longueur, valeurs finies et norme, et isolez les anomalies pour investigation.

Pour aller plus loin

Créez d'abord une clé de test sur ZSQH AI Gateway, puis exécutez sur un petit lot de textes les contrôles d'authentification, de lots, de limitation et de validation des vecteurs avant de brancher le pipeline d'indexation réel. La disponibilité des modèles et les prix sont sur la page Tarifs des modèles.

Pour aller plus loin :