Reliability and Routing22 сентября 2026 г.Cxj

Ошибка API 400 «Text Content Blocks Must Be Non-Empty»: причины и 5 способов исправления

Исправьте ошибку API 400 «Text Content Blocks Must Be Non-Empty» в запросах Anthropic с помощью проверки payload, кода санитайзера, примеров SDK и шагов QA.

Ошибка API 400 «Text Content Blocks Must Be Non-Empty»: причины и 5 способов исправления

Ошибка API 400 "Text Content Blocks Must Be Non-Empty": причины и 5 способов исправления означает, что в запросе к Anthropic Messages API есть как минимум один текстовый блок, у которого значение text пустое. На уровне сообщения запрос может выглядеть корректным, но провайдер отклоняет его ещё до генерации, потому что текстовый content-блок должен содержать как минимум один символ.

Быстрое исправление простое: удалите пустые текстовые блоки, обрезайте пользовательский ввод, состоящий только из пробелов, перед формированием запроса и никогда не отправляйте placeholder-блоки вроде {"type":"text","text":""}. Более сложная часть — найти, где именно эти блоки появляются. В production-приложениях они часто приходят из черновиков в чат-интерфейсе, пустых чанков retrieval, очистителей markdown, шаблонных переменных, буферов потоковой транскрипции или мультимодальных адаптеров, которые формируют массив content ещё до того, как узнают, есть ли там текст.

Используйте это руководство, чтобы отладить ошибку, исправить builder запроса и добавить preflight-проверку, чтобы одна и та же ошибка 400 больше не попадала в production.

Краткий ответ: ошибка API 400 "Text Content Blocks Must Be Non-Empty"

Anthropic принимает content сообщения либо как обычную строку, либо как массив типизированных content-блоков. В массивной форме текстовый блок выглядит так:

{
  "type": "text",
  "text": "Summarize this support ticket."
}

Это не проходит, потому что поле text пустое:

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

На практике это также может завершиться ошибкой, если ваше приложение приводит значение, состоящее только из пробелов, к пустой строке:

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

Самое безопасное правило:

  1. Обрезайте текстовые значения перед формированием запроса Anthropic.
  2. Удаляйте текстовые блоки, если их обрезанный текст пуст.
  3. Если у сообщения не осталось content-блоков, не отправляйте это сообщение.
  4. Логируйте очищенную структуру payload, не записывая приватный текст prompt.
  5. Добавьте unit-тест для пустых строк, пробелов, null и пустых результатов retrieval.

Это практическое исправление для ошибки API 400 "Text Content Blocks Must Be Non-Empty": причины и 5 способов исправления.

Почему возникает эта ошибка

Messages API от Anthropic использует структурированные ходы диалога. У каждого входящего сообщения есть role и content. Значение content может быть одной строкой или массивом блоков, таких как текстовые и image-блоки. В официальной документации по Messages API строковый content описывается как сокращённая запись для одного текстового блока, а у поля text в текстовом блоке указан minLength: 1.

В справочнике ошибок Anthropic HTTP 400 классифицируется как invalid_request_error: проблема с форматом или содержимым запроса. Значит, это не ограничение по rate limit, не ошибка авторизации, не сбой провайдера и не проблема качества модели. Это ошибка проверки запроса.

Для команд, создающих AI-продукты, это важный операционный вывод: повторная отправка того же запроса не поможет. Нужно исправить payload до повторной попытки.

Пять распространённых причин

1. Пустой ввод в чате попадает в API

Самый частый путь — chat composer, который позволяет пользователю отправить пустой черновик или черновик, который становится пустым после обрезки пробелов.

Неверный запрос:

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

Исправьте это до вызова API:

const input = userInput.trim();

if (!input) {
  throw new Error("Текст сообщения обязателен перед вызовом Anthropic.");
}

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

Используйте сообщение уровня продукта для проверки в интерфейсе. Не позволяйте бэкенду обнаруживать пустой prompt через ошибку 400 от провайдера.

2. При извлечении добавляются пустые фрагменты

Пайплайны RAG часто преобразуют извлечённые документы в секции prompt. Если в результате извлечения есть пустой фрагмент, удалённое тело HTML или поле OCR с ошибкой, адаптер может создать пустой текстовый блок.

Плохой адаптер:

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

Более безопасный адаптер:

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

Если модели нужен исходный контекст, также храните количество отброшенных фрагментов. Если каждый извлечённый фрагмент пуст, остановитесь и верните ошибку извлечения вместо отправки пустого prompt.

3. Переменные шаблона рендерятся в пустоту

Шаблоны prompt — ещё одна частая причина API Error 400 "Text Content Blocks Must Be Non-Empty": Causes and 5 Fixes. Шаблон может выглядеть заполненным в коде, но во время выполнения рендерить пустую секцию:

const prompt = `
Сообщение клиента:
${customerMessage}
`;

Если customerMessage равно undefined, null или пустое после очистки, итоговый prompt может оказаться бесполезным или пустым.

Используйте явные обязательные поля:

function requiredText(name: string, value: unknown): string {
  const text = String(value ?? "").trim();
  if (!text) {
    throw new Error(`Отсутствует обязательное поле prompt: ${name}`);
  }
  return text;
}

const prompt = `Сообщение клиента:\n${requiredText("customerMessage", customerMessage)}`;

Это превращает расплывчатую ошибку провайдера в локальную ошибку приложения с указанием имени отсутствующего поля.

4. Конструкторы мультимодальных запросов добавляют пустой текстовый блок-заглушку

Команды, которые создают потоки image-plus-text, иногда инициализируют массивы content текстовым блоком-заглушкой и заполняют его позже. Если текст необязателен и не приходит, заглушка остаётся пустой.

Плохой шаблон:

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

Более безопасный шаблон:

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

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

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

Формируйте блоки только тогда, когда соответствующий контент существует. Не используйте пустые текстовые блоки в качестве разделителей.

5. Компактификация истории сообщений оставляет пустые ходы

Ассистенты, работающие длительное время, часто сжимают или суммируют предыдущие ходы. Если на этапе компактификации из сообщения удаляется тело, но сам ход остаётся в истории, ваш запрос может содержать пустое сообщение ассистента или пользователя.

Пример сбоя:

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

Используйте очиститель истории перед каждым вызовом:

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

Затем убедитесь, что перед вызовом API остаётся хотя бы одно сообщение.

Проверяющий предварительный валидатор, который можно скопировать

Используйте валидатор предварительной проверки запроса рядом с финальной сетевой границей. Это позволяет выявлять пустые блоки, даже если вышестоящий UI, шаблон, RAG или модуль памяти их пропустил.

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("Dropped empty Anthropic text block", {
          messageIndex,
          blockIndex
        });
        return [];
      }

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

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

  if (!cleaned.length) {
    throw new Error("Anthropic request has no non-empty message content.");
  }

  return cleaned;
}

Это намеренно консервативный подход. Он удаляет пустые текстовые блоки, сохраняет нетекстовые блоки, удаляет пустые сообщения и отказывается вызывать модель, если не остаётся пригодного содержимого сообщения.

Версия на Python

Если ваш backend на Python, используйте ту же проверку на границе:

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(
                        "Удален пустой текстовый блок 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("В запросе Anthropic нет непустого содержимого сообщений.")

    return cleaned_messages

Сохраняйте структурные метаданные логов. Не логируйте исходные пользовательские запросы, клиентские документы или приватный текст из retrieval, если только ваша политика конфиденциальности и рабочий процесс отладки явно это не разрешают.

Контрольный список отладки

Когда в production появляется Ошибка API 400 "Text Content Blocks Must Be Non-Empty": причины и 5 способов исправления, отлаживайте в таком порядке:

Проверка Что проверить Исправление
Ввод в UI Пустой или состоящий только из пробелов черновик пользователя Блокировать отправку, пока не будет trimmed text
Шаблон промпта Обязательная переменная отрендерилась как пустая Проверять обязательные поля по имени
Фрагменты RAG Пустой cleanedText, результат OCR или body markdown Фильтровать фрагменты и завершать с ошибкой, если весь контекст пуст
Мультимодальный запрос Пустой текстовый блок-заполнитель перед блоком изображения/файла Добавлять текстовые блоки только когда текст действительно есть
Сжатие истории Пустые ходы пользователя или ассистента после суммаризации Проверять и очищать финальный список сообщений
Граница сети Итоговый payload всё ещё содержит text: "" Добавить предварительный валидатор и unit tests

Итоговый сетевой payload — это источник истины. Если в логах не видно пустого текстового блока, проверьте, не преобразует ли SDK null, undefined или пустой массив в текстовый блок во время сериализации.

Unit Tests To Add

Минимально добавьте тесты для следующих случаев:

const cases = [
  { name: "пустая строка без текста", content: "" },
  { name: "строка только из пробелов", content: "   " },
  { name: "пустой текстовый блок", content: [{ type: "text", text: "" }] },
  { name: "отсутствует поле text", content: [{ type: "text" }] },
  { name: "поле text равно null", content: [{ type: "text", text: null }] },
  { name: "валидный текстовый блок", content: [{ type: "text", text: "Hello" }] }
];

Ожидаемое поведение должно быть явным:

  • Пустые сообщения, содержащие только текст, удаляются или отклоняются локально.
  • Валидный текст сохраняется, при этом начальные и конечные пробелы удаляются.
  • Нетекстовые блоки контента сохраняются.
  • Запрос без пригодного к использованию контента вызывает ошибку до обращения к Anthropic.
  • Выброшенная ошибка указывает на границу вашего приложения, а не только на ответ провайдера.

Где здесь помогает Flatkey

Если ваша команда направляет трафик Claude через Flatkey, сохраняйте ту же дисциплину payload для Anthropic. Руководство Flatkey по Anthropic SDK показывает base_url="https://router.flatkey.ai" для маршрута Anthropic SDK, тогда как OpenAI-совместимый API использует https://router.flatkey.ai/v1 для запросов в стиле chat completions. Используйте маршрут, который соответствует вашему клиенту и форме endpoint.

Для этой ошибки Flatkey полезен как операционный слой вокруг исправления:

  • Держите одно место для проверки, дошел ли запрос до gateway.
  • Сравнивайте статус запроса и данные об использовании после успешной повторной попытки.
  • Держите небольшой smoke test отдельно от полного пользовательского prompt.
  • Не смешивайте запросы в формате Anthropic и OpenAI-совместимые запросы в одном адаптере.

Если вы выбираете маршрут для нагрузки Claude, прочитайте Claude API Proxy vs Multi-Model Router. Если вы стандартизируете первую безопасную проверку для инженеров, держите рядом краткое руководство по API Flatkey. Для более широких production-проверок сопоставьте это с метриками AI routing API и руководством по каталогу AI-моделей.

Чего не следует делать

Не решайте Ошибка API 400 "Text Content Blocks Must Be Non-Empty": причины и 5 способов исправления с помощью слепых повторных попыток. Провайдер сообщает, что запрос сформирован неверно.

Избегайте этих антипаттернов:

Антипаттерн Почему это не работает
Повторная отправка того же payload Детерминированная ошибка валидации будет продолжать возникать
Замена пустого текста на "." Это скрывает потерю данных выше по потоку и может изменить поведение модели
Отправка пустых ходов assistant Это засоряет историю и может сломать продолжение ответа
Логирование полных промптов для отладки Это может раскрыть данные клиентов или секреты
Исправление только интерфейса Фоновые задачи, RAG, вебхуки и циклы агентов все равно могут создавать пустые блоки

Устойчивое решение — проверять контент как на границе производителя, так и на конечной API-границе.

Часто задаваемые вопросы

Это сбой Anthropic?

Нет. Ошибка 400 invalid_request_error для пустых текстовых блоков — это проблема валидации запроса. Проверьте полезную нагрузку, которую отправляет ваше приложение.

Можно ли отправить обычную строку вместо массива текстовых блоков?

Да. Messages API Anthropic позволяет задавать content сообщения в виде строки, и в документации это описано как сокращённая форма для одного текстового блока. Используйте строку, когда нужен только простой текст. Используйте массив, когда нужны несколько блоков или мультимодальный ввод.

Считаются ли пробелы непустыми?

Считайте текст, состоящий только из пробелов, пустым в своём валидаторе. Даже если провайдер бы его принял, это не полезный промпт и обычно указывает на ошибку в интерфейсе, шаблоне или поиске данных.

Можно ли использовать сообщения только с изображениями?

Мультимодальный запрос не требует пустого текстового заполнителя. Если вы включаете блок изображения, добавляйте блок изображения напрямую и добавляйте текстовый блок только тогда, когда у вас есть настоящий текст инструкции.

Что следует логировать?

Логируйте количество сообщений, типы блоков содержимого, индексы блоков, модель, endpoint, маршрут, код состояния и request ID, если он доступен. Избегайте логирования полного текста промпта, если только правила конфиденциальности вашей команды этого не позволяют.

Официальные ссылки