Reliability and Routing22 de septiembre de 2026Cxj

Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones

Corrige el error de API 400 "Text Content Blocks Must Be Non-Empty" en solicitudes a Anthropic con comprobaciones del payload, código de saneamiento, ejemplos de SDK y pasos de QA.

Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones

Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones significa que una solicitud de la API Messages de Anthropic contiene al menos un bloque de texto cuyo valor text está vacío. La solicitud puede parecer válida a nivel de mensaje, pero el proveedor la rechaza antes de la generación porque un bloque de contenido de texto debe contener al menos un carácter.

La solución rápida es simple: elimine los bloques de texto vacíos, recorte la entrada del usuario que solo contenga espacios en blanco antes de construir la solicitud y nunca envíe bloques de marcador de posición como {"type":"text","text":""}. La parte más difícil es encontrar dónde se introducen esos bloques. En las aplicaciones de producción, a menudo provienen de borradores de la interfaz de chat, fragmentos de recuperación vacíos, limpiadores de Markdown, variables de plantilla, búferes de transcripción en streaming o adaptadores multimodales que construyen un array de contenido antes de saber si hay texto presente.

Use esta guía para depurar el error, corregir el generador de solicitudes y agregar una protección preventiva para que el mismo 400 no vuelva a producirse.

Respuesta rápida: Error de API 400 "Text Content Blocks Must Be Non-Empty"

Anthropic acepta el content del mensaje como una cadena simple o como un array de bloques de contenido tipados. En la forma de array, un bloque de texto se ve así:

{
  "type": "text",
  "text": "Resume este ticket de soporte."
}

Esto falla porque el campo text está vacío:

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

Esto también puede fallar en la práctica si su aplicación normaliza un valor que solo contiene espacios en blanco a una cadena vacía:

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

La regla más segura es:

  1. Recorte los valores de texto antes de construir la solicitud de Anthropic.
  2. Elimine los bloques de texto cuyo texto recortado esté vacío.
  3. Si un mensaje no tiene bloques de contenido restantes, no envíe ese mensaje.
  4. Registre la forma de la carga útil saneada sin registrar texto privado del prompt.
  5. Añada una prueba unitaria para cadenas vacías, espacios en blanco, nulos y resultados de recuperación vacíos.

Esta es la solución práctica para Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones.

Por qué ocurre este error

La API Messages de Anthropic utiliza turnos de conversación estructurados. Cada mensaje de entrada tiene un role y un content. El valor de content puede ser una sola cadena o puede ser un array de bloques como bloques de texto e imagen. La referencia oficial de la API Messages describe el contenido de cadena como un atajo para un bloque de texto y enumera text en un bloque de texto con minLength: 1.

La referencia de errores de Anthropic clasifica HTTP 400 como invalid_request_error: un problema con el formato o el contenido de la solicitud. Por lo tanto, no se trata de un límite de tasa, un fallo de autenticación, una interrupción del proveedor ni un problema de calidad del modelo. Es un problema de validación de la solicitud.

Para los equipos de producto de IA, la lección operativa es importante: reintentar la misma solicitud no ayudará. Debe corregir la carga útil antes de volver a intentarlo.

Cinco causas comunes

1. Una entrada de chat vacía llega a la API

La ruta más común es un compositor de chat que permite a un usuario enviar un borrador vacío o un borrador que se vuelve vacío después de recortar.

Solicitud incorrecta:

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

Corrígelo antes de llamar a la API:

const input = userInput.trim();

if (!input) {
  throw new Error("Se requiere texto del mensaje antes de llamar a Anthropic.");
}

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

Usa un mensaje de validación a nivel de producto en la UI. No dejes que el backend descubra un prompt vacío a partir de un 400 del proveedor.

2. Retrieval agrega fragmentos vacíos

Los flujos RAG a menudo convierten los documentos recuperados en secciones del prompt. Si un resultado de recuperación tiene un fragmento vacío, un cuerpo HTML eliminado o un campo OCR fallido, el adaptador puede crear un bloque de texto vacío.

Adaptador incorrecto:

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

Adaptador más seguro:

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

Si el modelo necesita contexto de origen, conserva también un conteo de los fragmentos descartados. Si todos los fragmentos recuperados están vacíos, detén el proceso y devuelve un error de recuperación en lugar de enviar un prompt vacío.

3. Las variables de plantilla se renderizan sin nada

Las plantillas de prompt son otra causa frecuente de Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones. Una plantilla puede parecer poblada en el código, pero renderizar una sección vacía en tiempo de ejecución:

const prompt = `
Mensaje del cliente:
${customerMessage}
`;

Si customerMessage es undefined, null o está en blanco después de la limpieza, el prompt final puede ser inútil o vacío.

Usa campos obligatorios explícitos:

function requiredText(name: string, value: unknown): string {
  const text = String(value ?? "").trim();
  if (!text) {
    throw new Error(`Falta el campo de prompt obligatorio: ${name}`);
  }
  return text;
}

const prompt = `Mensaje del cliente:\n${requiredText("customerMessage", customerMessage)}`;

Esto convierte un error ambiguo del proveedor en un error local de la aplicación con el nombre del campo que falta.

4. Los constructores multimodales agregan un bloque de texto de marcador de posición

Los equipos que crean flujos de imagen y texto a veces inicializan los arreglos de contenido con un bloque de texto de marcador de posición y lo rellenan después. Si el texto es opcional y no llega ningún texto, el marcador de posición permanece vacío.

Patrón incorrecto:

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

Patrón más seguro:

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

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

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

Construye bloques solo cuando exista el contenido correspondiente. No uses bloques de texto vacíos como separadores.

5. La compactación del historial de mensajes deja turnos en blanco

Los asistentes de larga duración a menudo compactan o resumen turnos anteriores. Si el paso de compactación elimina el cuerpo de un mensaje pero deja el turno en el historial, tu solicitud puede contener un mensaje vacío del asistente o del usuario.

Ejemplo de fallo:

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

Usa un saneador del historial antes de cada llamada:

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 }] : [];
  });
}

Luego comprueba que quede al menos un mensaje antes de llamar a la API.

Un validador de preflight que se puede copiar

Usa un validador de preflight de solicitudes cerca del límite final de red. Esto detecta bloques vacíos incluso si una interfaz de usuario, plantilla, RAG o módulo de memoria upstream los pasa por alto.

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("Se descartó un bloque de texto vacío de Anthropic", {
          messageIndex,
          blockIndex
        });
        return [];
      }

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

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

  if (!cleaned.length) {
    throw new Error("La solicitud de Anthropic no tiene contenido de mensaje no vacío.");
  }

  return cleaned;
}

Esto es intencionalmente conservador. Elimina bloques de texto vacíos, preserva bloques que no son de texto, elimina mensajes vacíos y se niega a llamar al modelo si no queda contenido de mensaje utilizable.

Versión en Python

Si tu backend es Python, usa la misma comprobación de límite:

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(
                        "Se eliminó el bloque de texto vacío de Anthropic",
                        {"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 solicitud de Anthropic no tiene contenido de mensaje no vacío.")

    return cleaned_messages

Mantén la estructura de los metadatos de los registros. No registres prompts de usuario sin procesar, documentos de clientes ni texto privado recuperado, a menos que tu política de privacidad y tu flujo de trabajo de depuración lo permitan explícitamente.

Lista de verificación de depuración

Cuando Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones aparece en producción, depura en este orden:

Verificación Qué inspeccionar Solución
Entrada de la UI Borrador del usuario vacío o solo con espacios en blanco Bloquea el envío hasta que exista texto recortado
Plantilla de prompt La variable requerida se renderiza vacía Valida los campos requeridos por nombre
Fragmentos RAG cleanedText vacío, resultado de OCR o cuerpo markdown Filtra los fragmentos y falla si todo el contexto está vacío
Solicitud multimodal Bloque de texto de marcador de posición antes de un bloque de imagen/archivo Envía bloques de texto solo cuando exista texto
Compactación del historial Turnos vacíos del usuario o del asistente después del resumen Sanitiza la lista final de mensajes
Frontera de red La carga final aún contiene text: "" Añade un validador previo al envío y pruebas unitarias

La carga final de red es la fuente de verdad. Si tus registros muestran que no hay ningún bloque de texto vacío, confirma que el SDK no está convirtiendo null, undefined o un array vacío en un bloque de texto durante la serialización.

Pruebas unitarias que añadir

Como mínimo, añade pruebas para estos casos:

const cases = [
  { name: "cadena vacía simple", content: "" },
  { name: "cadena simple de espacios en blanco", content: "   " },
  { name: "bloque de texto vacío", content: [{ type: "text", text: "" }] },
  { name: "campo text faltante", content: [{ type: "text" }] },
  { name: "campo text nulo", content: [{ type: "text", text: null }] },
  { name: "bloque de texto válido", content: [{ type: "text", text: "Hello" }] }
];

Tu comportamiento esperado debe ser explícito:

  • Los mensajes vacíos solo de texto se eliminan o se rechazan localmente.
  • El texto válido se conserva con los espacios en blanco iniciales y finales eliminados.
  • Los bloques de contenido que no son texto se conservan.
  • Una solicitud sin contenido utilizable arroja un error antes de llamar a Anthropic.
  • El error arrojado identifica el límite de tu aplicación, no solo la respuesta del proveedor.

Dónde encaja Flatkey

Si tu equipo enruta el tráfico de Claude a través de Flatkey, mantén la misma disciplina de carga útil de Anthropic. La guía de SDK de Anthropic de Flatkey muestra base_url="https://router.flatkey.ai" para la ruta del SDK de Anthropic, mientras que la API compatible con OpenAI usa https://router.flatkey.ai/v1 para solicitudes estilo chat-completions. Usa la ruta que coincida con tu cliente y la forma de tu endpoint.

Para este error, Flatkey es más útil como la capa operativa alrededor de la solución:

  • Mantén un solo lugar para verificar si la solicitud llegó al gateway.
  • Compara el estado de la solicitud y la evidencia de uso después de un reintento exitoso.
  • Mantén una pequeña prueba de humo separada del prompt completo del usuario.
  • Evita mezclar solicitudes en formato Anthropic y solicitudes compatibles con OpenAI en el mismo adaptador.

Si estás eligiendo una ruta para una carga de trabajo de Claude, lee Proxy de API de Claude vs enrutador multimodelo. Si estás estandarizando cómo los ingenieros hacen su primera llamada segura, mantén cerca la guía rápida de la API de Flatkey. Para comprobaciones de producción más amplias, combina esto con métricas de API de enrutamiento de IA y la guía del catálogo de modelos de IA.

Qué no hacer

No resuelvas Error de API 400 "Text Content Blocks Must Be Non-Empty": causas y 5 soluciones con reintentos a ciegas. El proveedor te está diciendo que la solicitud está mal formada.

Evita estos patrones antihigiénicos:

Patrón antihigiénico Por qué falla
Reintentar la misma carga útil Un error de validación determinista seguirá fallando
Reemplazar texto vacío con "." Oculta la pérdida de datos aguas arriba y puede cambiar el comportamiento del modelo
Enviar turnos vacíos del asistente Contamina el historial y puede romper la continuación de la respuesta
Registrar prompts completos para depurar Puede exponer datos de clientes o secretos
Arreglar solo la interfaz Los trabajos de backend, RAG, webhooks y bucles de agentes aún pueden crear bloques vacíos

La solución duradera es validar el contenido tanto en el límite del productor como en el límite final de la API.

Preguntas frecuentes

¿Se trata de una interrupción de Anthropic?

No. Un invalid_request_error 400 para bloques de texto vacíos es un problema de validación de la solicitud. Revisa el payload que envía tu aplicación.

¿Puedo enviar una cadena simple en lugar de una matriz de bloques de texto?

Sí. La API de Messages de Anthropic permite que content del mensaje sea una cadena, y la documentación describe eso como un atajo para un bloque de texto. Usa una cadena cuando solo necesites texto simple. Usa una matriz cuando necesites varios bloques o entrada multimodal.

¿El espacio en blanco debería contar como no vacío?

Trata el texto compuesto solo por espacios en blanco como vacío en tu propio validador. Incluso si un proveedor lo aceptara, no es contenido de prompt útil y normalmente indica un error de UI, plantilla o recuperación.

¿Pueden funcionar los mensajes con solo imágenes?

Una solicitud multimodal no necesita un marcador de posición de texto vacío. Si incluyes un bloque de imagen, construye el bloque de imagen directamente y añade un bloque de texto solo cuando tengas texto de instrucción real.

¿Qué debería registrar?

Registra el número de mensajes, los tipos de bloques de contenido, los índices de los bloques, el modelo, el endpoint, la ruta, el código de estado y el ID de la solicitud si está disponible. Evita registrar el texto completo del prompt a menos que las reglas de privacidad de tu equipo lo permitan.

Referencias oficiales