0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude API の Structured Outputs で JSON パースエラーを撲滅する実装手順 — beta ヘッダー・スキーマ制約・初回レイテンシの3つのハマりどころ【2026】

0
Posted at

はじめに / 対象と前提

Claude API で「JSON で返して」とプロンプトに書いたのに、返答の先頭に「はい、以下が抽出結果です:」が付いて json.loads() が落ちる——この定番のワナは、現在は Structured Outputs で仕組みごと潰せる。

この記事の対象と前提:

  • 想定読者:Claude API を Python から叩いていて、LLM の応答を後段のプログラムでパースしている人
  • 環境:Python 3.13 / anthropic SDK 1.x / モデルは claude-opus-5(Claude 4.6 以降なら同じ書き方)
  • 自分は自律エージェントのツール応答パースで実際にハマったので、その回避策込みで書く

TL;DR

  • 応答スキーマは output_config: {"format": {...}} で指定する。ネット上に多い旧 output_format トップレベル引数+beta ヘッダーの記事はもう古い
  • Pydantic モデルを渡せる client.messages.parse() が最短。response.parsed_output が検証済みインスタンスで返る
  • スキーマには additionalProperties: falserequired 全列挙が必須。忘れると 400

手順 / 動かし方

1. Pydantic モデルで受ける(推奨)

from pydantic import BaseModel
import anthropic

class ContactInfo(BaseModel):
    name: str
    email: str
    plan: str
    demo_requested: bool

client = anthropic.Anthropic()

response = client.messages.parse(
    model="claude-opus-5",
    max_tokens=16000,
    messages=[{
        "role": "user",
        "content": "抽出して: 田中さん (tanaka@example.com) は Enterprise プラン希望、デモ申込あり",
    }],
    output_format=ContactInfo,  # parse() には Pydantic モデルをそのまま渡す
)

contact = response.parsed_output  # 検証済み ContactInfo インスタンス
print(contact.name, contact.demo_requested)

実行結果:

田中 True

json.loads() も try/except も書かない。パースと検証は SDK 側で終わっている。

2. 生の JSON Schema で受ける

Pydantic を入れたくない場合は messages.create()output_config を渡す。

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=16000,
    messages=[{"role": "user", "content": "..."}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "demo_requested": {"type": "boolean"},
                },
                "required": ["name", "demo_requested"],
                "additionalProperties": False,
            },
        }
    },
)

この場合、最初の text ブロックがスキーマに適合した JSON であることが保証されるので、json.loads() 一発で受けられる。

3. ツール引数側も固めるなら strict: true

応答だけでなく tool use の input も検証したいときは、ツール定義のトップレベルstrict を置く。

tools=[{
    "name": "book_flight",
    "description": "フライトを予約する",
    "strict": True,  # tool_choice 側ではなくここ
    "input_schema": {
        "type": "object",
        "properties": {
            "destination": {"type": "string"},
            "passengers": {"type": "integer"},
        },
        "required": ["destination", "passengers"],
        "additionalProperties": False,
    },
}]

これで tool_use.input がスキーマ通りであることが保証され、エージェントループ内の防御的パースがほぼ消える。

ハマりどころ

1. 旧記事の output_format + beta ヘッダーを写すと 400

初出時は anthropic-beta: structured-outputs-2025-11-13 ヘッダー+トップレベル output_format パラメータのベータ機能だったため、検索上位の記事は今もその書き方が多い。現在は GA で beta ヘッダー不要、パラメータは output_config.format に移動済み。旧形式を新 SDK に写経すると Extra inputs are not permitted 系の 400 で落ちる。エラーメッセージ内のパラメータ名を見たら、まず記事の鮮度(2025年末以前か)を疑うのが早い。

2. additionalProperties: falserequired を忘れて 400

Structured Outputs のスキーマは JSON Schema のサブセットで、object には additionalProperties: false と全プロパティの required 列挙が要求される。Pydantic 経由(parse())なら SDK が変換してくれるので、生スキーマを書くときだけ注意。オプショナルにしたいフィールドは "type": ["string", "null"] のように null 許容にして required には残す。

3. max_tokens 切れの JSON は保証対象外

スキーマ保証は「完走した応答」に対するもの。stop_reasonmax_tokens だと JSON が途中で切れて壊れたまま返る。自分はここを見落として、深夜バッチが週1回だけ落ちる不具合を仕込んだ。

if response.stop_reason == "max_tokens":
    raise RuntimeError("truncated: max_tokens を増やして再試行")

max_tokens はケチらず 16000 程度を既定にしておくのが安全。

背景・補足

  • 以前の定番だった「assistant メッセージに { を prefill して JSON を強制」は、Claude 4.6 以降のモデルでは prefill 自体が 400 で拒否されるため使えない。Structured Outputs がその公式後継
  • 新しいスキーマの初回リクエストは、サーバー側のスキーマ処理(文法へのコンパイル)が入るぶん少し遅くなることがある。結果はキャッシュされるので2回目以降は通常速度。レイテンシ計測は初回を捨てて測る
  • ドキュメントの citations 機能とは併用不可(400)なので、引用付き RAG の応答には使えない

まとめ

  • JSON 強制は output_config.format(または messages.parse() + Pydantic)が現行の正解。beta ヘッダー時代の記事は形式が違うので写さない
  • スキーマは additionalProperties: false + required 全列挙がお約束
  • 保証されるのは完走した応答だけ。stop_reason == "max_tokens" チェックは必ず入れる
  • prefill による JSON 強制は新モデルでは廃止済み。移行先も Structured Outputs
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?