Claude Media
Citationsとstructured outputsが併用できない理由と400エラー条件

Citationsとstructured outputsが併用できない理由と400エラー条件

CitationsとClaudeのstructured outputs(output_config.format)は併用できません。同時指定で起きる400エラーの正確な条件と理由、回避策を解説します。

Citationsとstructured outputsが併用できない

Claude APIのCitationsとstructured outputsは、公式ドキュメントが明記する数少ない完全に非互換な機能の組み合わせです。documentブロックまたはsearch_resultブロックでcitations.enabled: trueを指定した状態で、同じリクエストにoutput_config.format(旧output_formatを含む)を含めると、APIは400エラーを返してリクエストごと拒否します。

structured outputsは、JSON outputs(output_config.format)とStrict tool use(strict: true)という2つの独立した仕組みの総称です(Claude JSONモードの使い方で基本的な使い方を解説しています)。非互換として明記されているのはこのうちJSON outputsだけで、片方だけ知っていると「structured outputsは全部Citationsと使えない」と誤解しやすい構造になっています。

この組み合わせに実際にぶつかりやすいのは、根拠付きの回答と機械可読なデータの両方が要る用途です。たとえば社内ナレッジベースを参照して回答しつつ、そのままチケット管理システムに投げられる構造化データも欲しい、というカスタマーサポートのエージェントは典型例です。「引用付きで説明させたい」と「JSONで確実に受け取りたい」を1回のAPI呼び出しで同時に満たそうとすると、この制約に直面します。

400エラーになる正確な条件

エラーが起きるのは、次の2条件が同一リクエストに揃ったときだけです。

条件該当するもの
Citations有効なブロックがある該当するものdocumentブロックまたはsearch_resultブロックでcitations.enabled: true
JSON出力の形式指定がある該当するものoutput_config.format(または非推奨のoutput_format)

どちらか一方だけなら問題なく動きます。Citationsだけを使う通常のリクエストも、output_config.formatだけを使う通常のJSON固定リクエストも、これまで通り成立します。エラーになるのは両方を同じリクエストに同居させたときに限られます。

対象になるブロックはdocumentだけではありません。ツール実行結果として返すsearch_resultブロックでCitationsを有効にしている場合も同じ条件に含まれます。RAG(検索拡張生成)で検索結果をsearch_resultブロックとして渡し、そこにcitations.enabled: trueを付けている実装であれば、同じリクエストにoutput_config.formatを足した瞬間に同じ400エラーに当たります。ドキュメントを直接渡す構成か、検索結果を渡す構成かは関係なく、「Citations有効なブロックの有無」だけが条件です(ドキュメント種別ごとの引用形式の違いはClaudeのCitationsは引用形式がPDF・テキスト・カスタムで変わるで扱っています)。旧パラメータのoutput_formatを使い続けている実装でも同様にエラーになるので、移行が済んでいないコードベースほど気づきにくい落とし穴になります。

# citationsとoutput_config.formatを同居させると400になる例
curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 1024,
    "messages": [{
      "role": "user",
      "content": [
        {
          "type": "document",
          "source": {"type": "text", "media_type": "text/plain", "data": "..."},
          "citations": {"enabled": true}
        },
        {"type": "text", "text": "この内容をJSONで要約して"}
      ]
    }],
    "output_config": {
      "format": {"type": "json_schema", "schema": {"type": "object", "properties": {"summary": {"type": "string"}}}}
    }
  }'

APIのエラーは共通でtype: "error"のオブジェクトにerror.typeerror.messageを持つ形式で返ります。この組み合わせ違反はinvalid_request_errorに分類される400エラーで、レスポンスには追跡用のrequest_idも含まれます。

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "..."
  },
  "request_id": "req_..."
}

なぜ両立しないのか

理由は両機能の内部動作が構造的に競合するからです。Citationsが有効なとき、Claudeの応答は「地の文のテキストブロック」と「引用付きのテキストブロック」が交互に並ぶ形になります。引用は既存の文章の途中に割り込む形で挿入され、応答全体の形はリクエストごとに変わります。

一方でstructured outputsのJSON outputsは、応答全体を1つのJSON Schemaへ制約付きデコーディング(グラマーによってトークン単位の出力候補そのものを制限する仕組み)で一致させます。出力の構造がスキーマによって最初から厳密に固定されており、テキストブロックと引用ブロックが交互に挟まる余地を持ちません。引用の割り込みという可変構造と、JSON Schemaの厳密な固定構造は、同じ応答の中で両立できないというのが公式の説明です。

グラマーによる制約はClaudeの直接テキスト出力にだけかかり、ツール呼び出しやthinkingタグの中身には及ばない、という設計もこの非互換を理解する手がかりになります。セクションが切り替わるたびにグラマーの状態はリセットされる仕組みなので、Claudeは自由に考えてから最後だけ構造化して答える、という使い方ができます。裏を返せば、Citationsが割り込みを起こす場所(直接テキスト出力)は、まさにグラマーが制約をかけている場所と完全に重なっているため、この2つだけは共存できません。

strict tool useとの違い — 明記されているのはoutput_config.formatだけ

structured outputsのもう一方の柱であるStrict tool use(strict: true)は、Claudeの直接出力ではなくツール呼び出しの引数を検証する仕組みです。前段で見た通りグラマーが制約をかけるのはClaudeの直接テキスト出力だけなので、ツール呼び出しの引数を検証するstrict tool useはその適用範囲の外側にあります。公式ドキュメントがCitationsとの非互換として名指ししているのはoutput_config.formatのみで、strict: trueへの言及はどちらのドキュメントにもありません。

これは「Citationsとツールの厳密な引数検証を同じ会話で使えない」という意味ではなく、両者が競合する箇所(Claudeの直接テキスト出力)自体が違う、と読むのが正確です。Citationsとstrict tool useを併用したいケースでは、まず両方だけの小さいリクエストで実際に400が返るかを確認してから設計に組み込むのが安全です(ドキュメントに明記のない挙動は変わる可能性があるため)。

structured outputsの非互換はCitationsだけではない

output_config.formatが競合する相手はCitationsだけではありません。公式ドキュメントはMessage Prefilling(assistant側のメッセージを途中まで自分で書いて続きをClaudeに生成させる手法)もJSON outputsと非互換だと明記しています。理由は同じ系統で、Prefillingで自由な文字列を先頭に置くとその時点でJSON Schemaへの厳密な一致が崩れてしまうためで、JSON outputsは応答の構造を完全に固定する機能である以上、応答の形を外側から変えようとする要素とは基本的に相性が悪いという大枠で捉えておくと他の非互換にも応用できます。

逆にバッチ処理・トークンカウント・ストリーミング・Strict tool useとの併用はJSON outputsと問題なく組み合わせられます。structured outputsが避けているのは応答本文の形を予測不能にする要素であって、実行方式や別チャンネルの検証機構とは競合しません。

併用したいときの回避策

引用付きの根拠情報と、構造化されたJSON出力の両方が必要な場合は、リクエストを分けるのが公式の想定する回避策です。

  1. まずCitations有効なリクエストで、根拠テキストと出典位置を取得する
  2. 得られた回答テキストと引用情報を、2回目のリクエストの入力に渡し、Citationsを無効にした状態でoutput_config.formatを指定してJSON化する

この2段構成なら、1回目のレスポンスに含まれるcited_textやドキュメント位置の情報を、2回目のプロンプトにそのまま埋め込めます。1回のリクエストで両方を得ようとしない、というのがこの制約への向き合い方です。

エラーの原因を切り分ける

400エラーが返ったときにCitations由来かどうかを切り分けるには、まずoutput_config.formatを外してリクエストが通るかを試します。通れば原因はCitationsとの組み合わせ制約だと確定できます。逆にoutput_config.formatを残したままcitations.enabledfalseにして通るかも試すと、どちらの指定を外せば回避できるかが1往復で分かります。通らない場合は、JSON Schema側の非対応機能(minLengthmaximumなどの数値・文字列制約、外部$ref、再帰スキーマ)が原因のことが多く、こちらは併用エラーとは別の400です。

もう1つの切り分け方は、リクエストに含まれるdocumentブロックとsearch_resultブロックを1つずつcitations.enabled: falseに変えて、どのブロックが引き金になっているかを特定する方法です。複数のドキュメントやツール結果を同時に渡す実装では、どのブロックにCitationsが効いているかが見えにくくなりがちなので、意図的に有効化しているブロックの一覧をコード側で管理しておくと、エラー発生時に原因のブロックへすぐ辿り着けて切り分けが早くなります。エラーハンドリングの実装パターン全般はClaude APIのエラーハンドリング設計にまとめています。

まとめ

400エラーが起きるのは、documentまたはsearch_resultブロックでcitations.enabled: trueを指定し、かつoutput_config.format(旧output_format含む)を同じリクエストに含めたときだけです。片方だけなら問題なく動くので、まず自分のリクエストがこの2条件を同時に満たしていないかを確認するのが最初のチェックポイントになります。

両方の情報が必要なら、Citations有効なリクエストで根拠を取得したあと、その結果を入力にした別リクエストでJSON化する2段構成が実務的な回避策です。2往復目には引用そのものを入力として渡すので、返ってきたcited_textをそのまま載せるか、自分で抜粋を組み直してから渡すかを先に決めておくと実装がぶれません。1回のAPI呼び出しにこだわらず、最初から2段構成を前提にエンドポイント設計をしておけば、この制約自体は運用でじゅうぶん吸収できます。エラーが出たときにどちらの400なのかを切り分ける手順も含めて、実装前に一度目を通しておくと本番でのデバッグ時間を大きく減らせます。

この記事を共有:XはてブLinkedIn