Claude APIで構造化出力(JSON mode)を使いこなす!Pydanticで型安全なAIレスポンスを取得する方法
「Claude APIのレスポンスをそのまま使ったら、毎回パース処理が大変で…」そんな経験をしたことはありませんか?
AIが返す文章はとても柔軟ですが、それがアプリ開発では逆に困ることがあります。「ちゃんとJSONで返ってくると思ったら、説明文が混じってた」「キーの名前が微妙に違う」など、レスポンスのブレに悩まされるケースが多いんですよね。
そこで今回は、Claude APIの構造化出力(JSON mode)とPydanticを組み合わせて、型安全なAIレスポンスを取得する方法を解説します。
この記事を読めば、AIの出力を「確実に決まった形で受け取る」設計ができるようになります。アプリ開発の品質がぐっと上がりますよ 🎯
対象読者と前提知識

- Claude APIをPythonで使ったことがある方
- Pydanticをなんとなく知っているか、これから学びたい方
- AIのレスポンスをアプリに組み込んで使いたい中級者
Claude APIの基本的な使い方については、このブログの入門記事を先にご覧いただくとよりスムーズです。
構造化出力(JSON mode)とは?
まず「構造化出力」という言葉を整理しておきましょう。
通常、Claude APIに質問すると、自然な文章(テキスト)で返ってきます。これはチャットには最適ですが、アプリに組み込む場合は困ります。なぜなら、プログラムは「どこに何が書いてあるか」を正確に知る必要があるからです。
たとえば「この商品レビューの感情分析をして」と頼んだとき、次のような返答では困りますよね。
このレビューはポジティブです。スコアは0.85くらいです。
キーワードとしては「品質」「コスパ」などが目立ちます。
アプリが欲しいのは、こっちです。
{
"sentiment": "positive",
"score": 0.85,
"keywords": ["品質", "コスパ"]
}
これが構造化出力の考え方です。AIに「必ずこの形式で返して」と指定して、プログラムが扱いやすいデータ構造にするわけです 📦
Pydanticが活躍する理由
PydanticはPythonのデータバリデーションライブラリで、「このデータはこういう型・構造であるべき」を宣言的に定義できます。
Claude APIと組み合わせると、次のような流れになります。
- Pydanticでレスポンスの「型定義(スキーマ)」を書く
- そのスキーマをJSON Schema形式でClaudeに渡す
- ClaudeがそのスキーマにぴったりなJSONを返す
- PydanticでパースしてPythonオブジェクトとして使う
型の不一致があれば即エラーになるので、「なんか動いてるけど中身がおかしい」という沼にはまりにくくなります。これが型安全ということです。
実践!Pydantic × Claude APIで感情分析を型安全に実装する
では実際にコードを見ていきましょう。まずは必要なライブラリをインストールします。
pip install anthropic pydantic
STEP 1|Pydanticでレスポンス型を定義する
from pydantic import BaseModel, Field
from typing import Literal
# AIに返してほしいデータの「型」を定義
class SentimentAnalysis(BaseModel):
sentiment: Literal["positive", "negative", "neutral"] = Field(
description="テキストの感情(positive / negative / neutral)"
)
score: float = Field(
ge=0.0, le=1.0,
description="感情スコア(0.0〜1.0)"
)
keywords: list[str] = Field(
description="感情に関連するキーワードのリスト"
)
summary: str = Field(
description="一言での感情サマリー"
)
ポイントをまとめるとこんな感じです。
Literal["positive", "negative", "neutral"]で選択肢を限定できるField(ge=0.0, le=1.0)で数値の範囲を制限できるdescriptionがそのままClaudeへの指示になる
STEP 2|スキーマをClaudeに渡してJSONを取得する
import anthropic
import json
client = anthropic.Anthropic()
def analyze_sentiment(text: str) -> SentimentAnalysis:
# PydanticのスキーマをJSON Schema形式に変換
schema = SentimentAnalysis.model_json_schema()
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system="""あなたはテキスト感情分析の専門家です。
必ず指定されたJSONスキーマに従って、JSONのみを返してください。
説明文や前置きは不要です。""",
messages=[
{
"role": "user",
"content": f"""以下のテキストを感情分析してください。
## テキスト
{text}
## 出力スキーマ(このJSONの形式で返してください)
{json.dumps(schema, ensure_ascii=False, indent=2)}"""
}
]
)
# レスポンスをPydanticでパース(型チェック込み)
response_text = message.content[0].text
result = SentimentAnalysis.model_validate_json(response_text)
return result
# 実行してみよう
review = "このイヤホンは音質が最高で、コスパも抜群です!デザインもシンプルで気に入っています。"
result = analyze_sentiment(review)
print(f"感情: {result.sentiment}")
print(f"スコア: {result.score}")
print(f"キーワード: {result.keywords}")
print(f"サマリー: {result.summary}")
実行するとこんな感じの出力が得られます 🎉
STEP 3|複数アイテムを一括分析する応用例
次は、複数のレビューをまとめて分析するケースを見てみましょう。ネストした構造にも対応できます。
from pydantic import BaseModel, Field
from typing import Literal
import anthropic
import json
client = anthropic.Anthropic()
class SingleReview(BaseModel):
id: int = Field(description="レビューのID")
sentiment: Literal["positive", "negative", "neutral"]
score: float = Field(ge=0.0, le=1.0)
top_keyword: str = Field(description="最も重要なキーワード1つ")
class BatchAnalysis(BaseModel):
reviews: list[SingleReview] = Field(description="全レビューの分析結果リスト")
overall_sentiment: Literal["positive", "negative", "neutral"] = Field(
description="全体的な感情傾向"
)
average_score: float = Field(ge=0.0, le=1.0, description="平均スコア")
def batch_analyze(reviews: list[dict]) -> BatchAnalysis:
schema = BatchAnalysis.model_json_schema()
reviews_text = "\n".join(
[f"ID {r['id']}: {r['text']}" for r in reviews]
)
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=2048,
system="JSONスキーマに従い、JSONのみ返してください。説明文は不要です。",
messages=[
{
"role": "user",
"content": f"""以下の複数レビューを一括分析してください。
## レビュー一覧
{reviews_text}
## 出力スキーマ
{json.dumps(schema, ensure_ascii=False, indent=2)}"""
}
]
)
return BatchAnalysis.model_validate_json(message.content[0].text)
# テスト実行
sample_reviews = [
{"id": 1, "text": "とても使いやすくて満足しています!"},
{"id": 2, "text": "梱包が雑でがっかりしました。"},
{"id": 3, "text": "普通の商品です。可もなく不可もなく。"},
]
result = batch_analyze(sample_reviews)
print(f"全体感情: {result.overall_sentiment}")
print(f"平均スコア: {result.average_score:.2f}")
for r in result.reviews:
print(f" [ID:{r.id}] {r.sentiment} ({r.score:.2f}) キーワード: {r.top_keyword}")
ここが重要です。
- ネストした
BaseModelもそのままスキーマに変換できる list[SingleReview]のような複雑な型定義も問題なしmodel_validate_json()が型チェックしながらパースしてくれる
よくあるハマりポイントと対処法
❌ Claudeが余計な説明文を返してくる
システムプロンプトに「JSONのみ返してください。説明文は不要です。」と明示するのが効果的です。それでもブレる場合は、レスポンステキストから{}部分だけを正規表現で抽出する前処理を挟む方法もあります。
import re
def extract_json(text: str) -> str:
# ```json ... ``` や { ... } を抽出
match = re.search(r'\{.*\}', text, re.DOTALL)
if match:
return match.group()
raise ValueError("JSONが見つかりませんでした")
❌ Pydanticのバリデーションエラーが出る
model_validate_json()が失敗する場合は、スキーマのdescriptionをより具体的に書くと改善されることが多いです。「0.0から1.0の浮動小数点」のように、AIが迷わない表現を心がけましょう。
まとめ
今回はClaude APIの構造化出力(JSON mode)とPydanticを組み合わせた、型安全なレスポンス取得の方法を解説しました。
- ✅ Pydanticで型定義 →
model_json_schema()でスキーマ生成 - ✅ スキーマをプロンプトに埋め込んでClaudeに指示
- ✅
model_validate_json()でパースして型安全なオブジェクトに
この設計パターンを使うと、AIのレスポンスが「プログラムが確実に扱える形」で届くようになります。本番アプリへの組み込みも、ぐっとやりやすくなりますよ 🚀
ぜひ自分のプロジェクトでも試してみてください。型定義を工夫するだけで、AIがどんどん「使いやすい部品」になっていくのを実感できるはずです!
こちらも読まれています
📚 関連商品・おすすめ書籍
※本記事にはアフィリエイトリンクが含まれます。





