Reliability and Routing22 septembre 2026Flatkey Team

Erreur API 400 « Les blocs de contenu texte doivent être non vides » : causes et 5 solutions

Corrigez l’erreur API 400 « Text Content Blocks Must Be Non-Empty » dans les requêtes Anthropic grâce à des vérifications de charge utile, du code de nettoyage, des exemples SDK et des étapes de contrôle qualité.

Erreur API 400 « Les blocs de contenu texte doivent être non vides » : causes et 5 solutions

Erreur API 400 "Text Content Blocks Must Be Non-Empty" : causes et 5 solutions signifie qu’une requête à l’API Messages d’Anthropic contient au moins un bloc de texte dont la valeur text est vide. La requête peut sembler valide au niveau du message, mais le fournisseur la rejette avant la génération, car un bloc de contenu texte doit contenir au moins un caractère.

La correction rapide est simple : supprimez les blocs de texte vides, supprimez les espaces uniquement dans les saisies utilisateur avant la construction de la requête, et n’envoyez jamais de blocs factices tels que {"type":"text","text":""}. La partie la plus difficile consiste à trouver où ces blocs sont introduits. Dans les applications de production, ils proviennent souvent des brouillons de l’interface de chat, de fragments de récupération vides, de nettoyeurs Markdown, de variables de modèles, de tampons de transcription en streaming ou d’adaptateurs multimodaux qui construisent un tableau de contenu avant de savoir si du texte est présent.

Utilisez ce guide pour déboguer l’erreur, corriger le générateur de requêtes et ajouter une protection préalable afin que le même 400 ne se reproduise pas.

Réponse rapide : Erreur API 400 "Text Content Blocks Must Be Non-Empty"

Anthropic accepte content d’un message soit sous forme de chaîne simple, soit sous forme de tableau de blocs de contenu typés. Dans la forme tableau, un bloc de texte ressemble à ceci :

{
  "type": "text",
  "text": "Résumez ce ticket d’assistance."
}

Cela échoue parce que le champ text est vide :

{
  "type": "text",
  "text": ""
}

Cela peut aussi échouer en pratique si votre application normalise une valeur composée uniquement d’espaces en chaîne vide :

{
  "type": "text",
  "text": "   "
}

La règle la plus sûre est la suivante :

  1. Supprimez les valeurs textuelles avant de construire la requête Anthropic.
  2. Supprimez les blocs de texte dont le texte nettoyé est vide.
  3. Si un message n’a plus de blocs de contenu, n’envoyez pas ce message.
  4. Consignez la forme du payload assaini sans journaliser le texte privé du prompt.
  5. Ajoutez un test unitaire pour les chaînes vides, les espaces, les valeurs nulles et les résultats de récupération vides.

C’est la solution pratique pour Erreur API 400 "Text Content Blocks Must Be Non-Empty" : causes et 5 solutions.

Pourquoi cette erreur se produit

L’API Messages d’Anthropic utilise des tours de conversation structurés. Chaque message d’entrée a un role et un content. La valeur content peut être une chaîne unique ou un tableau de blocs tels que des blocs de texte et d’image. La documentation officielle de référence de l’API Messages décrit le contenu sous forme de chaîne comme un raccourci pour un seul bloc de texte et indique text sur un bloc texte avec minLength: 1.

La référence des erreurs d’Anthropic classe HTTP 400 comme invalid_request_error : un problème de format ou de contenu de la requête. Il ne s’agit donc pas d’une limite de débit, d’un échec d’authentification, d’une panne du fournisseur ou d’un problème de qualité du modèle. Il s’agit d’un problème de validation de requête.

Pour les équipes produit IA, la leçon opérationnelle est importante : relancer la même requête n’aidera pas. Vous devez corriger le payload avant de réessayer.

Cinq causes courantes

1. Une entrée de chat vide atteint l’API

Le chemin le plus courant est un compositeur de chat qui permet à un utilisateur de soumettre un brouillon vide ou un brouillon qui devient vide après suppression des espaces.

Mauvaise requête :

{
  "model": "claude-sonnet-5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "" }
      ]
    }
  ]
}

Corrigez-la avant l’appel à l’API :

const input = userInput.trim();

if (!input) {
  throw new Error("Le texte du message est requis avant d’appeler Anthropic.");
}

const messages = [
  {
    role: "user",
    content: input
  }
];

Utilisez un message de validation au niveau du produit dans l’interface. Ne laissez pas le backend découvrir une invite vide à partir d’une erreur 400 du fournisseur.

2. La récupération ajoute des fragments vides

Les pipelines RAG mappent souvent les documents récupérés en sections d’invite. Si un résultat de récupération contient un extrait vide, un corps HTML supprimé ou un champ OCR en échec, l’adaptateur peut créer un bloc de texte vide.

Adaptateur incorrect :

const content = retrievedDocs.map((doc) => ({
  type: "text",
  text: doc.cleanedText
}));

Adaptateur plus sûr :

const content = retrievedDocs
  .map((doc) => (doc.cleanedText ?? "").trim())
  .filter(Boolean)
  .map((text) => ({ type: "text", text }));

Si le modèle a besoin du contexte source, conservez également un compteur des fragments supprimés. Si tous les fragments récupérés sont vides, arrêtez-vous et renvoyez une erreur de récupération au lieu d’envoyer une invite vide.

3. Les variables de modèle ne produisent rien

Les modèles d’invite sont une autre cause fréquente de Erreur API 400 "Les blocs de contenu texte doivent être non vides" : causes et 5 solutions. Un modèle peut sembler rempli dans le code, mais rendre une section vide à l’exécution :

const prompt = `
Message client :
${customerMessage}
`;

Si customerMessage vaut undefined, null ou est vide après nettoyage, l’invite finale peut être inutile ou vide.

Utilisez des champs obligatoires explicites :

function requiredText(name: string, value: unknown): string {
  const text = String(value ?? "").trim();
  if (!text) {
    throw new Error(`Champ d’invite requis manquant : ${name}`);
  }
  return text;
}

const prompt = `Message client :\n${requiredText("customerMessage", customerMessage)}`;

Cela transforme une erreur fournisseur vague en erreur d’application locale avec le nom du champ manquant.

4. Les générateurs multimodaux ajoutent un bloc de texte de remplacement

Les équipes qui construisent des flux image + texte initialisent parfois les tableaux de contenu avec un bloc de texte de remplacement et le remplissent plus tard. Si le texte est facultatif et qu’aucun texte n’arrive, le remplacement reste vide.

Mauvais schéma :

[
  { "type": "text", "text": "" },
  {
    "type": "image",
    "source": {
      "type": "base64",
      "media_type": "image/png",
      "data": "..."
    }
  }
]

Schéma plus sûr :

const content: Array<Record<string, unknown>> = [];

const instruction = optionalInstruction.trim();
if (instruction) {
  content.push({ type: "text", text: instruction });
}

content.push({
  type: "image",
  source: imageSource
});

Construisez les blocs uniquement lorsque le contenu correspondant existe. N’utilisez pas de blocs de texte vides comme séparateurs.

5. La compaction de l’historique des messages laisse des tours vides

Les assistants exécutés sur de longues périodes compactent ou résument souvent les tours précédents. Si l’étape de compaction supprime le corps d’un message mais laisse le tour dans l’historique, votre requête peut contenir un message assistant ou utilisateur vide.

Exemple d’échec :

{
  "role": "assistant",
  "content": [
    { "type": "text", "text": "" }
  ]
}

Utilisez un nettoyeur d’historique avant chaque appel :

type TextBlock = { type: "text"; text: string };
type Message = { role: "user" | "assistant"; content: string | TextBlock[] };

function sanitizeMessages(messages: Message[]): Message[] {
  return messages.flatMap((message) => {
    if (typeof message.content === "string") {
      const text = message.content.trim();
      return text ? [{ ...message, content: text }] : [];
    }

    const content = message.content
      .map((block) => ({ ...block, text: block.text.trim() }))
      .filter((block) => block.type !== "text" || block.text.length > 0);

    return content.length ? [{ ...message, content }] : [];
  });
}

Vérifiez ensuite qu’au moins un message reste avant d’appeler l’API.

Un validateur de prévalidation réutilisable

Utilisez un validateur de prévalidation de requête près de la frontière finale du réseau. Cela détecte les blocs vides même si une interface utilisateur, un modèle, un système RAG ou un module de mémoire en amont les manque.

type ContentBlock =
  | { type: "text"; text?: unknown }
  | { type: string; [key: string]: unknown };

type AnthropicMessage = {
  role: "user" | "assistant";
  content: string | ContentBlock[];
};

export function validateAnthropicMessages(messages: AnthropicMessage[]) {
  const cleaned = messages.flatMap((message, messageIndex) => {
    if (typeof message.content === "string") {
      const text = message.content.trim();
      return text ? [{ ...message, content: text }] : [];
    }

    const content = message.content.flatMap((block, blockIndex) => {
      if (block.type !== "text") return [block];

      const text = String(block.text ?? "").trim();
      if (!text) {
        console.warn("Bloc de texte Anthropic vide supprimé", {
          messageIndex,
          blockIndex
        });
        return [];
      }

      return [{ ...block, text }];
    });

    return content.length ? [{ ...message, content }] : [];
  });

  if (!cleaned.length) {
    throw new Error("La requête Anthropic ne contient aucun contenu de message non vide.");
  }

  return cleaned;
}

Cette approche est délibérément conservatrice. Elle supprime les blocs de texte vides, préserve les blocs non textuels, supprime les messages vides et refuse d’appeler le modèle s’il ne reste aucun contenu de message exploitable.

Version Python

Si votre backend est en Python, utilisez la même vérification à la frontière :

def sanitize_anthropic_messages(messages):
    cleaned_messages = []

    for message_index, message in enumerate(messages):
        content = message.get("content")

        if isinstance(content, str):
            text = content.strip()
            if text:
                cleaned_messages.append({**message, "content": text})
            continue

        if isinstance(content, list):
            cleaned_blocks = []

            for block_index, block in enumerate(content):
                if block.get("type") != "text":
                    cleaned_blocks.append(block)
                    continue

                text = str(block.get("text") or "").strip()
                if text:
                    cleaned_blocks.append({**block, "text": text})
                else:
                    print(
                        "Bloc de texte Anthropic vide supprimé",
                        {"message_index": message_index, "block_index": block_index},
                    )

            if cleaned_blocks:
                cleaned_messages.append({**message, "content": cleaned_blocks})

    if not cleaned_messages:
        raise ValueError("La requête Anthropic ne contient aucun contenu de message non vide.")

    return cleaned_messages

Conservez la structure des métadonnées de journalisation. Ne consignez pas les prompts bruts des utilisateurs, les documents clients ou le texte privé issu de la récupération, sauf si votre politique de confidentialité et votre flux de débogage l’autorisent explicitement.

Liste de contrôle du débogage

Lorsque Erreur API 400 "Les blocs de contenu texte doivent être non vides" : causes et 5 solutions apparaît en production, déboguez dans cet ordre :

Vérification À inspecter Correctif
Entrée UI Brouillon utilisateur vide ou contenant uniquement des espaces Bloquez l’envoi jusqu’à ce qu’un texte après suppression des espaces existe
Modèle de prompt Variable requise rendue vide Validez les champs obligatoires par leur nom
Segments RAG cleanedText vide, résultat OCR ou corps markdown vide Filtrez les segments et échouez si tout le contexte est vide
Requête multimodale Bloc de texte d’espace réservé avant un bloc image/fichier N’ajoutez des blocs de texte que lorsque du texte existe
Compaction de l’historique Passages utilisateur ou assistant vides après synthèse Nettoyez la liste finale des messages
Limite réseau La charge utile finale contient toujours text: "" Ajoutez un validateur de prévol et des tests unitaires

La charge utile réseau finale est la source de vérité. Si vos journaux n’affichent aucun bloc de texte vide, vérifiez que le SDK ne convertit pas null, undefined ou un tableau vide en bloc de texte lors de la sérialisation.

Tests unitaires à ajouter

Au minimum, ajoutez des tests pour ces cas :

const cases = [
  { name: "chaîne vide simple", content: "" },
  { name: "chaîne de whitespace simple", content: "   " },
  { name: "bloc de texte vide", content: [{ type: "text", text: "" }] },
  { name: "champ text manquant", content: [{ type: "text" }] },
  { name: "champ text nul", content: [{ type: "text", text: null }] },
  { name: "bloc de texte valide", content: [{ type: "text", text: "Hello" }] }
];

Le comportement attendu doit être explicite :

  • Les messages vides contenant uniquement du texte sont supprimés ou rejetés localement.
  • Le texte valide est conservé avec les espaces en début et en fin supprimés.
  • Les blocs de contenu non textuels sont conservés.
  • Une requête sans contenu exploitable déclenche une exception avant l’appel à Anthropic.
  • L’erreur levée identifie la frontière de votre application, pas seulement la réponse du fournisseur.

Où Flatkey s’intègre

Si votre équipe fait transiter le trafic Claude via Flatkey, conservez la même discipline de charge utile Anthropic. Le guide SDK Anthropic de Flatkey montre base_url="https://router.flatkey.ai" pour le chemin SDK Anthropic, tandis que l’API compatible OpenAI utilise https://router.flatkey.ai/v1 pour les requêtes de type chat-completions. Utilisez la route qui correspond à votre client et à la forme de votre point de terminaison.

Pour cette erreur, Flatkey est surtout utile comme couche d’exploitation autour du correctif :

  • Conservez un seul endroit pour vérifier si la requête a atteint la passerelle.
  • Comparez l’état de la requête et les preuves d’utilisation après une tentative réussie.
  • Gardez un petit test de vérification séparé du prompt complet de l’utilisateur.
  • Évitez de mélanger des requêtes au format Anthropic et des requêtes compatibles OpenAI dans le même adaptateur.

Si vous choisissez une route pour une charge de travail Claude, lisez Proxy API Claude vs routeur multi-modèles. Si vous standardisez la façon dont les ingénieurs effectuent leur premier appel sécurisé, gardez le guide de démarrage rapide de l’API Flatkey à portée de main. Pour des vérifications plus larges en production, associez cela à des métriques d’API de routage IA et au guide du catalogue de modèles IA.

Ce qu’il ne faut pas faire

Ne résolvez pas Erreur API 400 "Les blocs de contenu texte doivent être non vides" : causes et 5 solutions avec des nouvelles tentatives aveugles. Le fournisseur vous indique que la requête est mal formée.

Évitez ces anti-patterns :

Anti-pattern Pourquoi cela échoue
Renvoyer la même charge utile Une erreur de validation déterministe continuera d’échouer
Remplacer le texte vide par "." Cela masque une perte de données en amont et peut modifier le comportement du modèle
Envoyer des tours assistant vides Cela pollue l’historique et peut casser la continuité de la réponse
Journaliser les prompts complets pour déboguer Cela peut exposer des données clients ou des secrets
Ne corriger que l’interface utilisateur Les jobs backend, le RAG, les webhooks et les boucles d’agent peuvent toujours créer des blocs vides

La correction durable consiste à valider le contenu à la fois à la frontière du producteur et à la frontière finale de l’API.

FAQ

Est-ce une panne d’Anthropic ?

Non. Une erreur 400 invalid_request_error pour des blocs de texte vides est un problème de validation de la requête. Vérifiez la charge utile envoyée par votre application.

Puis-je envoyer une simple chaîne au lieu d’un tableau de blocs de texte ?

Oui. L’API Messages d’Anthropic permet que le content d’un message soit une chaîne, et la documentation décrit cela comme un raccourci pour un seul bloc de texte. Utilisez une chaîne lorsque vous n’avez besoin que de texte simple. Utilisez un tableau lorsque vous avez besoin de plusieurs blocs ou d’une entrée multimodale.

Les espaces doivent-ils compter comme non vides ?

Traitez le texte composé uniquement d’espaces comme vide dans votre propre validateur. Même si un fournisseur l’acceptait, ce n’est pas un contenu utile pour le prompt et cela indique généralement un bug de l’interface, du modèle ou de la récupération.

Les messages contenant uniquement des images peuvent-ils fonctionner ?

Une requête multimodale n’a pas besoin d’un espace réservé de texte vide. Si vous incluez un bloc image, construisez directement le bloc image et ajoutez un bloc texte uniquement lorsque vous avez un véritable texte d’instruction.

Que dois-je consigner dans les logs ?

Consignez le nombre de messages, les types de blocs de contenu, les index des blocs, le modèle, l’endpoint, la route, le code d’état et l’identifiant de requête si disponible. Évitez de consigner le texte complet du prompt, sauf si les règles de confidentialité de votre équipe l’autorisent.

Références officielles