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

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

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

同じリクエストに入れると400になる組み合わせ

Claude APIのCitationsとstructured outputsは、同じリクエストに入れると400エラーでリクエストごと拒否される組み合わせです。条件は2つです。

  • documentブロックまたはsearch_resultブロックでcitations.enabled: trueを指定している
  • 同じリクエストにoutput_config.format(旧output_formatを含む)がある

どちらか一方だけなら問題なく動きます。RAG(検索拡張生成)で検索結果をsearch_resultブロックとして渡している実装も、ドキュメントを直接渡す実装も条件は同じで、判定に使われるのは「Citations有効なブロックの有無」です(ドキュメント種別ごとの引用形式の違いはClaudeのCitationsは引用形式がPDF・テキスト・カスタムで変わるで扱っています)。

structured outputsは、JSON outputs(output_config.format)とStrict tool use(strict: true)の総称です(Claude JSONモードの使い方で基本を解説しています)。400の条件として名指しされているのはoutput_config.format(JSON outputs)で、strict tool useとの組み合わせは条件に入っていません。

# 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-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"}}}}
    }
  }'

返ってくるのは共通のエラー形式で、error.typeはinvalid_request_error、レスポンスには追跡用のrequest_idも入ります。

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

400の原因がCitationsかどうかを切り分ける

output_config.formatを付けたリクエストが400で落ちたとき、原因の候補は3つあります。切り分けは次の順に進めると1往復ずつで絞れます。

手順

400が出たときの切り分け順

  1. 1

    output_config.formatだけを外して送る

    通れば、Citationsとの組み合わせ制約が原因です。通らなければCitationsとは別の問題です。

  2. 2

    formatを戻し、全ドキュメントのcitations.enabledをfalseにして送る

    通れば、原因はやはり両機能の同居です。一部のブロックだけをfalseにしても切り分けになりません。Citationsは1リクエスト内で「全ドキュメントで有効」か「全ドキュメントで無効」かのどちらかにする決まりだからです。

  3. 3

    スキーマ側と旧パラメータを見る

    minLength・maximumなどの数値・文字列制約、外部$ref、再帰スキーマは未対応で、使うと400です。スキーマが複雑すぎる場合も別の400で、メッセージは「Schema is too complex for compilation」です。output_formatを使い続けている場合は、betaヘッダーstructured-outputs-2025-11-13が無いと400になります。

    ヘッダーが要るのは、非推奨のoutput_formatを使うときだけです。output_config.formatならヘッダーは不要です。Python SDK(v1.0以降)では、client.beta.messages.create()にoutput_format={...}を渡せません。count_tokens()も同じで、TypeErrorになります。

スキーマ由来の400は、スキーマ自体を直さない限り残ります。なお、Citationsが対応するのはテキストの引用だけで、画像の引用はまだできません。エラーハンドリングの実装パターン全般はClaude APIのエラーハンドリング設計にまとめています。

なぜ両立しないのか

Citationsが有効なとき、引用付きブロックは、cited_textと文書内の位置を持つcitations配列を抱えて、文章の途中に割り込みます。

くらべる

応答の形が正反対になる

可変

Citations

テキストブロックと引用付きテキストブロックが交互に並び、ブロックの数も区切りもリクエストごとに変わります。

固定

JSON outputs

応答全体が1つのJSON Schemaに一致するよう、グラマー(トークン単位の出力候補を絞る仕組み)で最初から縛られます。

Anthropicの説明は、引用の割り込みと厳密なJSON Schemaの制約がぶつかる、という一点です。

グラマーの適用範囲も、この非互換を読む手がかりになります。グラマーがかかるのはClaudeの直接テキスト出力だけで、ツール呼び出し・ツール結果・thinkingタグには及びません。セクションが切り替わるたびにグラマーの状態もリセットされるので、Claudeは自由に考えてから最後だけ構造化して答えられます。

JSON outputsで注意したいのは、400にならないのにスキーマへ合わない出力が返る場合です。次の2つが該当します。

  • 拒否(stop_reason: "refusal")は、ステータス200で返ります
  • max_tokensに達した場合(stop_reason: "max_tokens")は、出力が途中で切れてスキーマに合わないことがあります

文字列のenumとconstは、大文字小文字までは保証されません。2段目の出力を受ける側では、stop_reasonの確認と値の正規化が要ります。

併用できる機能とできない機能

structured outputs側の互換性欄には、併用できるものと併用できないものがはっきり分かれて載っています。

まとめ

JSON outputs(output_config.format)の互換性

  • 併用できる

    バッチ処理(50%割引)・トークンカウント・ストリーミング、そしてStrict tool use。JSON outputsとStrict tool useは同じリクエストで一緒に使えます。

  • 併用できない

    Citations(有効なブロックがあると400)と、メッセージのprefill(最後のassistantターンを自分で書き始める手法)。

Citationsの側から見ると、併用可能な機能はプロンプトキャッシュ・トークンカウント・バッチ処理です。非互換として両方のページに載っているのはJSON outputsです。Citationsとstrict tool useを同じ会話に入れたい設計では、先に両方だけの小さいリクエストで挙動を確かめてから組み込むという手があります。

prefillはClaude 4.6以降のモデルでは最後のassistantターンへの指定自体が400になります。経緯はClaude 4.6でprefillが廃止された理由と5つの移行先にあります。

併用したいときは2回に分ける

根拠付きの回答と機械可読なデータの両方が要る用途では、リクエストを分けます。社内ナレッジを参照して答えつつ、同じ内容をそのままチケット管理システムに渡せる形でも欲しい、といったエージェントが典型です。

手順

2段構成の流れ

  1. 1

    Citations有効で根拠を取る

    documentまたはsearch_resultにcitations.enabled: trueを付け、output_config.formatは付けずに送ります。返ってくるのは引用付きの自然文と、cited_text・文書位置です。

  2. 2

    Citationsなしで構造化する

    1回目の回答テキストと、使いたい引用情報を2回目の入力に渡します。2回目には元のドキュメントを再送せず、output_config.formatだけを指定します。Citations有効なブロックが無いので、400の条件は成立しません。

この構成には設計上の判断が2つ付きます。

1つ目は、2回目に渡す引用の形です。1回目が返したcited_textをそのまま埋め込む案と、必要な部分だけ抜粋し直す案があり、先に決めておくと実装がぶれません。cited_textはCitationsの応答では出力トークンに数えられません。citationsブロックのまま次のターンへ返す場合は入力トークンにも数えられませんが、2回目の入力に文字列として埋め込めば通常の入力トークンとして数えられます。Citationsを有効にすると、システムプロンプトの追加とドキュメントの分割で入力トークンがわずかに増える点も、1回目のコストに入ります。仕組みはClaude Citationsのトークンコストの仕組みで扱っています。

2つ目は、プロンプトキャッシュです。output_config.formatを変えると、その会話スレッドのプロンプトキャッシュは無効になります。さらにstructured outputsを使うと、出力形式を説明する追加のシステムプロンプトがClaude側で自動的に入り、入力トークンがわずかに増えます。2段構成では、formatを付ける2回目と、付けない1回目でキャッシュの扱いが別々になる点も設計に入れておきます。

1回目のキャッシュは、元ドキュメントの側で効かせます。応答に含まれるcitationsブロックは直接キャッシュできませんが、参照先のドキュメントはキャッシュできます。トップレベルのドキュメントブロックにcache_controlを付けます。

グラマーにも別のキャッシュがあります。同じスキーマを初めて使うときはグラマーのコンパイルで遅延が増え、コンパイル済みのグラマーは最後に使ってから24時間保持されます。nameやdescriptionだけの変更ではこのキャッシュは無効になりません。プロンプトキャッシュとは別物で、2段目を同じスキーマで繰り返す運用では2回目以降の遅延が下がります。

2回目のスキーマに持ち回る引用フィールド

2段目でJSON化するとき、引用の位置情報をスキーマのプロパティとして受け取りたい場合があります。位置情報の形はドキュメントの種類で決まり、1回目の応答にはその形のまま入っています。

ドキュメントtype位置を表すフィールド
プレーンテキストtypechar_location位置を表すフィールドstart_char_index・end_char_index(0始まり、終端は含まない)
PDFtypepage_location位置を表すフィールドstart_page_number・end_page_number(1始まり、終端は含まない)
カスタムコンテンツtypecontent_block_location位置を表すフィールドstart_block_index・end_block_index(0始まり、終端は含まない)

どの型でもdocument_indexとdocument_title、引用された原文のcited_textが付きます。document_indexはリクエスト内の全メッセージにまたがるドキュメントブロックの並びに対する0始まりの番号なので、2回目にドキュメントを再送しない構成では、番号が何を指していたかを自前の対応表で持っておく必要があります。

もう1つ、位置情報の型がドキュメントごとに違うため、スキーマを1つに固定するなら、型ごとにanyOfで分けるか、共通部分(document_index・cited_text)だけを持たせるかを選ぶことになります。anyOfは対応していますが、anyOfや型の配列を使うパラメータは1リクエストで16個まで、オプションのパラメータ(requiredに無いもの)は全スキーマ合計で24個までです。上限を超えると、前述の「Schema is too complex for compilation」で400になります。明示された上限をすべて満たしていても、コンパイル結果が大きすぎると180秒のタイムアウトに当たることがあります。

対処は、可能なパラメータをrequiredにしてオプションを減らすことです。オプションのパラメータ1つごとに、グラマーの状態空間が部分的におよそ2倍になるためです。それでも収まらなければ、スキーマを分けて複数リクエストにします。共通部分だけに絞る案も、この上限に当たりにくい選択肢です。

まとめ

2段構成で後から困りやすいのは、1回目のdocument_indexが指していたドキュメントの対応表を残さないことと、2段目のスキーマを位置情報の型ごとに広げすぎることです。対応表を自前で持ち、スキーマは共通部分から始めておけば、この制約は運用で吸収できます。

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