Claude API の Structured Outputs が API 正規形を output_config.format に寄せ、TypeScript / Python SDK で client.messages.parse() が推奨パスになりました。旧 output_format 引数は deprecated(まだ動く)。本記事では、新 API の使い方、Zod との組み合わせ、Strict Tool Use、refusal の扱いまでをまとめます。
到達点
client.messages.parse()+ Zod スキーマで、LLM 出力を型付き・検証済みの JS オブジェクトとして受けるmessages.create()を直接呼ぶときもoutput_config.formatに寄せる- 関数(tool)呼び出しの入力を
strict: trueで schema 厳密化 stop_reason: "refusal"で schema が守られないケースを安全にハンドリング
対象モデル(2026-04 時点): Claude Opus 4.7 / Sonnet 4.6 / Haiku 4.5(legacy としては Opus 4.5 / Opus 4.1 も対応)。
1. 推奨パス — client.messages.parse() + Zod
TypeScript SDK には zodOutputFormat ヘルパが用意されており、Zod スキーマから自動で JSON Schema を生成 → output_config.format に乗せ → レスポンスを検証までを一括で面倒を見ます。
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const ContactSchema = z.object({
name: z.string(),
email: z.string(),
plan: z.string(),
interests: z.array(z.string()),
demo_requested: z.boolean(),
});
const client = new Anthropic();
const res = await client.messages.parse({
model: "claude-opus-4-7",
max_tokens: 4_000,
messages: [
{
role: "user",
content:
"抽出して: Jane Doe (jane@co.com) wants Enterprise, interested in API & SDKs, wants a demo.",
},
],
output_config: {
format: zodOutputFormat(ContactSchema),
},
});
// parsed_output は null 許容なので assert / guard してから使う
console.log(res.parsed_output!.name); // "Jane Doe"
console.log(res.parsed_output!.interests); // string[]
console.log(res.parsed_output!.demo_requested); // true
Python でも同じ構造で、Pydantic モデルを渡すヘルパが用意されています。手書きの JSON Schema を書かずに済むのがこのパスの価値です。
2. 直接 messages.create() を呼ぶ場合
parse() を使わず素の API を叩くなら、output_config.format に JSON Schema を渡します。
const res = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4_000,
messages: [{ role: "user", content: "..." }],
output_config: {
format: {
type: "json_schema",
name: "Contact",
schema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
plan: { type: "string" },
interests: { type: "array", items: { type: "string" } },
demo_requested: { type: "boolean" },
},
required: ["name", "email", "plan", "interests", "demo_requested"],
additionalProperties: false,
},
},
},
});
旧 output_format パラメータは deprecated です。まだ動きますが、新規コードは output_config.format で統一します。
3. Strict Tool Use — strict: true
ツール呼び出し側でも strict: true で引数スキーマを厳密化できます。
await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 4_000,
messages: [{ role: "user", content: "Book a flight to Tokyo for 2 on 2026-04-25" }],
tools: [
{
name: "book_flight",
description: "Book a flight to a destination",
strict: true, // ← schema 厳密化
input_schema: {
type: "object",
properties: {
destination: { type: "string" },
date: { type: "string", format: "date" },
passengers: { type: "integer", enum: [1, 2, 3, 4, 5, 6, 7, 8] },
},
required: ["destination", "date", "passengers"],
additionalProperties: false,
},
},
],
});
strict ツールでは、スキーマに合致しない引数はそもそも生成されません。自前バリデーションが不要になり、tool 実装側のガードを薄くできます。
4. JSON Schema の制約
Claude の Structured Outputs は JSON Schema の サブセットを解釈します。
サポート: object / array / string / integer / number / boolean / null / enum / const / anyOf / allOf / $ref / $def / 主要な string formats(date, date-time, email, uri, uuid 等)。
未サポート:
- 再帰スキーマ(自己参照
$ref) - 数値制約(
minimum/maximum/multipleOf) - 文字列長制約(
minLength/maxLength) additionalPropertiesがfalse以外
全 object に additionalProperties: false が必須という点は忘れると地味にハマります。
Python / TypeScript SDK は未サポート制約を自動で剥がして API に送り、戻った値をクライアント側で validation する親切設計です。そのため Zod で .min(1).max(200) を書いても、送信時はスキーマから剥がされ、受信時に Zod 側で検証されます。
5. refusal の扱い
安全上の理由で Claude がスキーマ準拠の出力を拒否すると、レスポンスは stop_reason: "refusal" で返ります。このとき 出力はスキーマに合致しない可能性があるため、アプリ側で必ず分岐します。
if (res.stop_reason === "refusal") {
// スキーマが守られない可能性あり。UI では "回答できません" 等に振る
throw new Error("model refused to produce structured output");
}
parse() を使っていても refusal が返ったケースでは parsed_output が null になる可能性があります。nullable を前提に ! で殴らず、ガードを書いてください。
6. 運用のメンタルモデル
- 分類・抽出・整形が中心 → 迷わず
messages.parse()+ Zod / Pydantic - Tool の引数を信頼したい → 個別 tool に
strict: trueを付ける - 出力長・範囲を縛りたい → スキーマには書けないので
systemプロンプトで制約、受信側は自前で検証 - スキーマが複雑で re-compile が多い → 同じスキーマは 24h キャッシュされる。同日中に同じスキーマで数万リクエスト投げても初回ペナルティは一度きり
- citations / message prefill とは非互換 → 組み合わせると 400。citations が必要なら structured output を別リクエストに分ける
7. 落とし穴
output_formatを使い続ける — deprecated。新規はoutput_config.formatに寄せるadditionalProperties: falseを書き忘れる — ほぼ必ず 400。Zod 経由なら helper が付けてくれる- 数値/文字列制約を書いても通らない — JSON Schema レベルでは無視される。長さ制限が欲しいなら SDK の auto-strip + client-side validate に乗るか、受信後に Zod で検証
- refusal を null check でしか扱わない — ユーザーへのエラー表示を設計する
messages.parse()のparsed_outputを!で squash — refusal や parse 失敗でnullになるので ts-assert より optional-chain 推奨- citations を同時に有効化する — 400 が返る
まとめ
Structured Outputs は「LLM の JSON 出力を検証なしで信じてプロダクションに流す」という最大級のアンチパターンを、スキーマを 1 回書くだけで回避できる機能です。TS なら Zod、Python なら Pydantic でサーバー/クライアント両側同じ型を保ちながら使うのが主流です。
次回は Programmatic Tool Calling か Memory Tool を取り上げる予定です。release notes タグで一覧を追跡できます。