「compiled grammar is too large」エラーの原因とスキーマ分割で対処する方法
structured outputsで複雑なJSON Schemaを使うと出る400エラーの原因を、nullable型・$refの非重複展開・深いネストの3点から解説します。
structured outputsのoutput_config.formatで中程度に複雑なJSON Schemaを送ると、400エラーが返ることがあります。エラー文は「The compiled grammar is too large, which would cause performance issues」というものです。原因はJSON outputsのスキーマ単体にあるのに、メッセージはstrict: trueのツール向けの「tool schemas」「strict tools」という語彙で説明されるため、混乱を招きます。報告者はnullable型の分岐・$refの非重複展開・深いネストの3つを原因に挙げています。2026年8月の返信で上限の文書化が告げられ、上限を超えたリクエストには説明的なエラーメッセージが返ります。
「compiled grammar is too large」とはどんなエラーか
このエラーは、anthropic-sdk-pythonリポジトリのIssue #1185で報告されました。報告者はTypeScript SDKでoutput_config.format(JSON outputs)を使っています。5階層のネストとnullable型を含む、約50プロパティのスキーマを送信したところ、次のエラーを受け取りました。使っていたモデルはclaude-sonnet-4-5とclaude-opus-4-5で、2つのモデルで同じエラーが出ています。
400 {"type":"error","error":{"type":"invalid_request_error","message":"The compiled grammar is too large, which would cause performance issues. Simplify your tool schemas or reduce the number of strict tools."},"request_id":"req_..."}Issueの本文は、メッセージの語彙のずれも指摘しています。JSON outputsにはツールもstrictツールも登場しないのに、「Simplify your tool schemas or reduce the number of strict tools」と返るためです。structured outputsの基本的な使い方ではJSON outputsとStrict tool useを別機能として扱います。ただしドキュメントでは、JSON outputsとstrict toolsに同じ複雑さの上限が適用されます。エラー文に出てくるグラマーは、制約付きデコーディングの内部表現です。コメント欄にはGo SDKでも同じエラーに遭遇したという報告があり、特定の言語SDKに限った問題ではありません。
自分のスキーマが上限に対してどれだけ大きいか数える
エラーを読んでも、どのフィールドが重いのかは分かりません。文書化された上限のうち、スキーマを見て数えられるのは「requiredに入っていない任意プロパティの数」と「anyOfまたは型配列を使うパラメータの数」の2つです。次のPythonで数えられます。
def count(s, acc):
if isinstance(s, dict):
if s.get("type") == "object":
req = set(s.get("required", []))
for k, v in s.get("properties", {}).items():
if k not in req:
acc["optional"] += 1
count(v, acc)
if isinstance(s.get("type"), list): # ["number", "null"] など
acc["union"] += 1
if "items" in s:
count(s["items"], acc)
return accIssueに載っているスキーマを例に使います。TypeWithSchemaは4回、constraintsオブジェクトは1つのTypeWithSchemaの中に2回(直下とfieldsの要素の中)出てくるので、Issueの「...same...」の部分は同じ構造として展開して数えます。
TypeWithSchema 1個 {'optional': 16, 'union': 18} JSON 1338 bytes
mapper 1件(TypeWithSchema 4個) {'optional': 64, 'union': 72} JSON 5928 bytesmapper1件だけで任意プロパティが64個、union型のパラメータが72個です。上限の24個と16個のそれぞれ2倍以上になります。一方でJSONの大きさは約6KBで、後述する「10KB程度」という経験則の内側に収まっています。サイズの目安だけでは、この種のスキーマの危険度は測れません。数え方は、再出現したサブスキーマも出てくるたびに1回と数えています。上限の説明は同じ定義を何回と数えるかには触れていないので、実際の判定とは異なる可能性があります。
なぜ複雑なスキーマでグラマーが肥大化するか
Issueの投稿者は、スキーマが重くなる要因を次の3つに分けています。
グラマーを肥大化させる3つの要因
nullable型が分岐を生む
{"type": ["number", "null"]}のような型配列は、anyOfと同じunion型として数えられ、ドキュメントは指数関数的なコンパイルコストを生むと説明しています。Issueの報告者は、nullableなフィールドが増えるほど分岐が掛け算で増えると見ています。$refは減らさない
$ref・$defsは対応済みの機能です。ただしコンパイラは参照先をインラインで展開するようで、$refで共有してもコンパイル後のグラマーは小さくなりません。深いネスト
オブジェクトの中の配列の要素がさらにオブジェクトを持つ構造が4〜5階層続くと、複雑さが積み重なると報告者は見ています。ドキュメントも、任意フィールドを持つ深いネストは複雑さを増すと説明しています。
Issueが挙げた数字では、TypeWithSchema1つあたり約12個のnullableフィールドが、1件のmapperに4回(reducerには2回)出てきて、約48の分岐点になります。報告者の数え方では約12個ですが、上のスクリプトでは、enumのitemsにある型配列まで含めてunion型が18個になります。
2番目の$refの話は、同じIssueに別の投稿者が体験談を寄せています。評価用のハーネスでValidationResultというオブジェクトを4〜6回参照していて、$defsにまとめて重複を省いたのに、グラマーの問題は解消しなかったそうです。ドキュメントのJSON Schemaの項は$refを対応済みの機能として挙げているだけで、グラマーサイズとの関係には触れていません。
上限に当たったときの対処の順番
ドキュメントのTipsは、上から順に試すよう案内しています。コメント欄の実務者の回避策も、同じ方向でおおむね一致します。上のスクリプトの結果と合わせて、次の順で見直します。
スキーマを軽くする順番
- 1
strictにするツールを絞る
strict: trueは、スキーマ違反が実害になるツールだけに付けます。残りのツールはstrictなしで、Claudeの通常の追従に任せます。 - 2
任意プロパティを必須にする
ドキュメントによると、任意パラメータが1つ増えるごとに、グラマーの状態空間の一部がおおむね2倍になります。常に妥当なデフォルト値があるパラメータは
requiredに移し、そのデフォルト値をClaudeに返させます。 - 3
ネストを平坦にし、enumを絞る
オブジェクトの中に任意フィールドが重なる構造を平坦にします。コメント欄では、選択肢が多いenumはグラマーの分岐を増やすので、文字列とdescription(説明文)に切り替えるという報告があります。
- 4
リクエストを2つに分ける
1回目で配列の件数やトップレベルの必須項目といった骨格だけを取り、2回目以降で各要素の詳細を埋めます。1回あたりのスキーマが小さくなるので、
$refでは減らせない分も減ります。
2つ目の手順には落とし穴があります。任意プロパティを["string", "null"]のようなnullable型の必須に置き換えると、24個の枠から16個の枠へ負担が移るだけです。コメント欄には、省略可能な項目を必須のnullable型にする助言もあります。ただし型配列で書くなら、任意の枠を減らした分だけunion型の枠を消費します。
ある投稿者はこれとは別に、スキーマのJSON文字列サイズが10KB程度に収まると安定してコンパイルが通ることが多いと共有しています。保証された数値ではなく、構造(ネスト・nullable数・enum数)によって同じサイズでも結果が変わります。
Anthropicの対応 — エラーメッセージと制限値の文書化
このIssueは2026年8月6日、Anthropic側の返信でクローズされました。返信では、複雑さの上限を文書化したことと、上限を超えたリクエストには不透明なエラーではなく説明的な400エラーを返すようにしたことが述べられています。ドキュメントによると、そのときのエラーメッセージは「Schema is too complex for compilation」です。
文書化された上限
strictツール
20個
リクエストあたり。strictなしのツールは数えない
任意パラメータ
24個
全スキーマ合計。required外のプロパティが1個につき1
union型パラメータ
16個
anyOfまたは型配列を使うもの。strictスキーマ全体の合計
コンパイル
180秒
タイムアウトの最終防衛線
たとえば、任意パラメータ6個のstrictツールが4つあれば、どのツールも単体では軽く見えても24個の枠に達します。数値の細かい扱いはJSON Schema制限のSDK変換とStrict tool useの保証内容でまとめています。この記事で扱うエラーは、上限が文書化される前に返っていた、中身の見えないメッセージです。
Issueの機能要望には、スキーマの複雑さを送信前に見積もる検証用の窓口(dry-run)もありました。クローズ時の返信にその話は出てこないので、上のような手元の数え上げが代わりになります。返信が述べているのは、エラーメッセージの説明化と上限の数値の文書化の2点です。$refのグラマー展開に手を入れたとは述べていません。スキーマを分割する回避策は、返信後も残る対処です。
上限表を守っても落ちる報告がある
文書は上限表の下で、別枠の内部上限にも触れています。表の個別の上限をすべて満たしていても、スキーマ全体の複雑さが内部上限を超えれば、同じ「Schema is too complex for compilation」の400が返ります。表の数字を守ることは、通過の保証にはなりません。
Issueのコメントにも、その例があります。OpenRouter経由でclaude-sonnet-4.6に送った投稿者は、約20プロパティのスキーマで、任意20個(上限24個)・anyOf2個(上限16個)にとどまっているのに、応答が止まるか400が返ると書いています。list[Enum]とlist[str]のフィールドがそれぞれ3つ以上あると確実に壊れ、1フィールド削っても通らなかったそうです。投稿者の推測は、任意プロパティの数だけでなく、列挙型の配列と文字列の配列の組み合わせが効く、というものです。この報告は経由先を挟んでいるので、そのまま上限の仕様とは言えません。それでも、数え上げだけで合否を見積もる限界は示しています。
制約の扱いにも使い分けがあります。ドキュメントのSDK変換の説明では、minimum・maximum・minLength・maxLengthのような未対応の制約は、送信するスキーマから外してdescriptionへ追記されます。コンパイル済みのグラマーは最後の使用から24時間キャッシュされ、nameとdescriptionだけを変えてもキャッシュは無効になりません。初回のリクエストではコンパイルのぶんだけ遅れる、という説明もあります。
まとめ
表の数字を満たしても内部上限で落ちる報告があるので、上限内に収めるだけでなく、骨格と詳細の2回に分ける構成を最初から設計に入れておく選択肢があります。Python SDKでPydanticモデルから送る場合も、任意フィールドとnullable型の数は、スキーマ側で数えられます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。