Reliability and Routing2026年9月22日Flatkey Team

API エラー 400「Text Content Blocks Must Be Non-Empty」の原因と5つの修正方法

Anthropic へのリクエストで発生する API エラー 400「Text Content Blocks Must Be Non-Empty」を、ペイロードチェック、サニタイザーコード、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 の値が空のテキストブロックが少なくとも1つ含まれていることを意味します。メッセージのレベルでは妥当に見えても、テキストコンテンツブロックには少なくとも1文字必要なため、生成前にプロバイダー側で拒否されます。

素早い修正は簡単です。空のテキストブロックを削除し、リクエストを組み立てる前に空白のみのユーザー入力をトリムし、{"type":"text","text":""} のようなプレースホルダーブロックを送信しないことです。より難しいのは、そうしたブロックがどこで作られているかを見つけることです。本番アプリでは、チャットUIの下書き、空の検索取得チャンク、Markdownクリーナー、テンプレート変数、ストリーミングのトランスクリプトバッファ、あるいはテキストの有無が分かる前に content 配列を組み立てるマルチモーダルアダプターなどから発生することがよくあります。

このガイドを使ってエラーをデバッグし、リクエストビルダーを修正し、同じ400エラーが再発しないよう事前チェックを追加してください。

クイックアンサー: API エラー 400 "Text Content Blocks Must Be Non-Empty"

Anthropic はメッセージの content を、単一の文字列または型付きコンテンツブロックの配列として受け付けます。配列形式では、テキストブロックは次のようになります:

{
  "type": "text",
  "text": "このサポートチケットを要約してください。"
}

これは text フィールドが空のため失敗します:

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

また、アプリが空白のみの値を空文字列に正規化する場合も、実際には次のように失敗することがあります:

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

最も安全なルールは次のとおりです:

  1. Anthropic のリクエストを作成する前にテキスト値をトリムする。
  2. トリム後のテキストが空のテキストブロックを除外する。
  3. メッセージに残りの content ブロックがない場合は、そのメッセージを送信しない。
  4. 秘密のプロンプト本文はログに記録せず、サニタイズ後のペイロード構造のみをログに出す。
  5. 空文字列、空白、null、空の検索取得結果についてのユニットテストを追加する。

これが API エラー 400 "Text Content Blocks Must Be Non-Empty": 原因と5つの修正方法 に対する実用的な修正です。

このエラーが発生する理由

Anthropic の Messages API は構造化された会話ターンを使用します。各入力メッセージには rolecontent があります。content の値は単一の文字列でもよく、テキストや画像ブロックなどのブロック配列でも構いません。公式の Messages API リファレンスでは、文字列コンテンツは1つのテキストブロックの省略形として説明されており、テキストブロックの text には minLength: 1 が設定されています。

Anthropic のエラーリファレンスでは、HTTP 400 は invalid_request_error、つまりリクエスト形式または内容の問題として分類されています。したがって、これはレート制限、認証失敗、プロバイダー障害、あるいはモデル品質の問題ではありません。リクエスト検証の問題です。

AI 製品チームにとって、運用上の教訓は重要です。同じリクエストを再試行しても解決しません。再試行する前にペイロードを修正する必要があります。

よくある5つの原因

1. 空のチャット入力が API に届く

最も一般的な経路は、ユーザーが空の下書き、またはトリム後に空になる下書きを送信できてしまうチャットコンポーザーです。

不正なリクエスト:

{
  "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
  }
];

UI では製品レベルの検証メッセージを使用してください。バックエンドがプロバイダーの 400 エラーで空のプロンプトを発見するようにしてはいけません。

2. 検索で空のチャンクが追加される

RAG パイプラインでは、取得したドキュメントをプロンプトのセクションにマッピングすることがよくあります。検索結果に空のスニペット、削除された 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 }));

モデルにソースコンテキストが必要な場合は、除外されたチャンク数も保持してください。取得したチャンクがすべて空の場合は、空のプロンプトを送信するのではなく停止して検索エラーを返してください。

3. テンプレート変数が何も出力しない

プロンプトテンプレートも、API エラー 400「Text Content Blocks Must Be Non-Empty」の原因と5つの修正方法のよくある原因です。テンプレートはコード上では値が入っているように見えても、実行時には空のセクションとしてレンダリングされることがあります:

const prompt = `
顧客メッセージ:
${customerMessage}
`;

customerMessageundefinednull、またはクリーンアップ後に空白の場合、最終的なプロンプトは役に立たないか空になります。

必須フィールドを明示的に使用してください:

function requiredText(name: string, value: unknown): string {
  const text = String(value ?? "").trim();
  if (!text) {
    throw new Error(`必須のプロンプトフィールドがありません: ${name}`);
  }
  return text;
}

const prompt = `顧客メッセージ:\n${requiredText("customerMessage", customerMessage)}`;

これにより、曖昧なプロバイダーのエラーが、欠落したフィールド名を含むローカルのアプリケーションエラーに変わります。

4. マルチモーダルビルダーがプレースホルダーのテキストブロックを追加する

画像とテキストを組み合わせたフローを構築するチームでは、コンテンツ配列をプレースホルダーのテキストブロックで初期化し、後でそれを埋めることがあります。テキストが任意で、テキストが一切届かない場合、プレースホルダーは空のまま残ります。

不適切なパターン:

[
  { "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. メッセージ履歴の圧縮により空のターンが残る

長時間稼働するアシスタントは、以前のターンを圧縮または要約することがよくあります。圧縮ステップでメッセージ本文が削除されても履歴上にターンが残ると、リクエストに空のassistantメッセージまたはuserメッセージが含まれる可能性があります。

失敗例:

{
  "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を呼び出す前に少なくとも1件のメッセージが残っていることを確認してください。

そのまま使える事前検証バリデーター

最終的なネットワーク境界の近くで、リクエストの事前検証バリデーターを使用してください。これにより、上流の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版

バックエンドが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

ログのメタデータ構造は維持してください。プライバシーポリシーとデバッグのワークフローで明示的に許可されていない限り、未加工のユーザープロンプト、顧客文書、または非公開の検索テキストをログに出力しないでください。

デバッグのチェックリスト

API エラー 400「Text Content Blocks Must Be Non-Empty」の原因と5つの修正方法 が本番環境で発生したら、次の順序でデバッグしてください:

確認項目 確認する内容 修正
UI 入力 空、または空白のみのユーザー下書き トリム後にテキストが存在するまで送信をブロックする
プロンプトテンプレート 必須変数が空でレンダリングされる 必須フィールドを名前で検証する
RAG チャンク 空の cleanedText、OCR 結果、または markdown 本文 チャンクをフィルタリングし、コンテキストがすべて空なら失敗させる
マルチモーダルリクエスト 画像/ファイルブロックの前にあるプレースホルダーのテキストブロック テキストが存在する場合のみテキストブロックを追加する
履歴の圧縮 要約後に空白のユーザーまたはアシスタントのターンが残る 最終メッセージリストをサニタイズする
ネットワーク境界 最終ペイロードにまだ text: "" が含まれている 送信前バリデータとユニットテストを追加する

最終的なネットワークペイロードが真実の स्रोतです。ログに空のテキストブロックが表示されない場合でも、SDK がシリアライズ時に nullundefined、または空の配列をテキストブロックへ変換していないか確認してください。

追加すべきユニットテスト

少なくとも、次のケースのテストを追加してください:

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 経由でルーティングしている場合も、Anthropic のペイロードに対する同じ規律を保ってください。Flatkey の Anthropic SDK ガイドでは、Anthropic SDK の経路に base_url="https://router.flatkey.ai" を使用し、OpenAI 互換 API では chat-completions 形式のリクエストに https://router.flatkey.ai/v1 を使用します。クライアントとエンドポイントの形に合ったルートを使ってください。

このエラーに対して Flatkey が最も役立つのは、修正を支える運用レイヤーとしてです:

  • リクエストがゲートウェイに到達したかを確認する場所を一つに保つ。
  • 成功した再試行の後で、リクエストのステータスと使用状況の証跡を比較する。
  • ユーザーの完全なプロンプトとは別に、小さなスモークテストを維持する。
  • Anthropic 形式のリクエストと OpenAI 互換のリクエストを同じアダプターで混在させない。

Claude のワークロードでルートを選ぶなら、Claude API Proxy vs Multi-Model Router を読んでください。エンジニアが最初の安全な呼び出しを行う方法を標準化したいなら、Flatkey API quickstart を手元に置いてください。より広い本番確認には、これに AI routing API metricsAI model catalog guide を組み合わせてください。

やってはいけないこと

API エラー 400 "Text Content Blocks Must Be Non-Empty": 原因と 5 つの修正方法 を、やみくもな再試行で解決しようとしないでください。プロバイダーは、リクエストが不正な形式であると伝えています。

次のアンチパターンは避けてください:

アンチパターン 失敗する理由
同じペイロードを再試行する 決定論的な検証エラーは繰り返し失敗します
空のテキストを "." に置き換える 上流のデータ損失を隠し、モデルの挙動を変える可能性があります
空の assistant ターンを送る 履歴を汚染し、応答の継続を壊す可能性があります
デバッグのためにプロンプト全体をログに出す 顧客データやシークレットを露出する可能性があります
UI だけを修正する バックエンドジョブ、RAG、webhook、エージェントループでも空のブロックが作られる可能性があります

恒久的な修正は、生成側の境界と最終 API 境界の両方でコンテンツを検証することです。

FAQ

これは Anthropic の障害ですか?

いいえ。空のテキストブロックに対する 400 invalid_request_error は、リクエストの検証エラーです。アプリケーションが送信するペイロードを確認してください。

テキストブロックの配列の代わりに、単一の文字列を送れますか?

はい。Anthropic の Messages API では、メッセージの content を文字列にできます。ドキュメントでは、これは 1 つのテキストブロックの省略形として説明されています。シンプルなテキストだけが必要な場合は文字列を使ってください。複数のブロックやマルチモーダル入力が必要な場合は配列を使ってください。

空白文字は非空として扱うべきですか?

独自のバリデーターでは、空白文字だけのテキストは空として扱ってください。たとえプロバイダーが受け入れたとしても、それは有用なプロンプト内容ではなく、通常は UI、テンプレート、または取得処理のバグを示しています。

画像だけのメッセージでも機能しますか?

マルチモーダルリクエストでは、空のテキストのプレースホルダーは不要です。画像ブロックを含める場合は、画像ブロックを直接作成し、実際の指示テキストがある場合にのみテキストブロックを追加してください。

何をログに記録すべきですか?

メッセージ数、コンテンツブロックの種類、ブロックのインデックス、モデル、エンドポイント、ルート、ステータスコード、そして利用可能であればリクエスト ID を記録してください。チームのプライバシー規則で許可されていない限り、プロンプト全文のログ記録は避けてください。

公式リファレンス