Gemini APIで構造化データ（JSON）を取得する方法 - Pydanticとの連携

## はじめに
この記事では、Google製生成AI「Gemini」の新機能「response_schema」を使って、JSONデータを直接取得し、Pydanticで型安全に扱う方法を紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ae121043-c871-4c72-86d5-3002d1a25a0a.png)

## 環境準備

本記事のコードはGoogle Colabで実行することを想定しています。まず必要なライブラリをインストールしましょう。

```python
!pip install -q google-generativeai pydantic
```

## 実装の流れ

1. APIキーの設定とクライアント初期化
2. Pydanticによるスキーマ定義
3. プロンプト作成と構造化出力の指定
4. レスポンス取得と構造化データの利用

## コード全体

まずは全体のコードを見てみましょう：

```python
from google import genai
from pydantic import BaseModel
from typing import List
from google.colab import userdata

# Geminiクライアント初期化
client = genai.Client(api_key=userdata.get('GEMINI_API_KEY'))

# スキーマ定義：スパイス調合
class Spice(BaseModel):
    name: str
    amount: str
    purpose: str

class CurrySpiceMix(BaseModel):
    recipe_name: str
    spices: List[Spice]


# プロンプト
prompt = (
    "インド風カレーのスパイス調合を提案してください。"
    "スパイスの名前、量、目的を含め、レシピ名とともにJSONで返してください。"
)

# コンテンツ生成リクエスト
response = client.models.generate_content(
    model="gemini-2.0-flash",
    contents=prompt,
    config={
        "response_mime_type": "application/json",
        "response_schema": CurrySpiceMix,
    },
)

# 生テキスト表示（JSON文字列）
print("=== JSON出力 ===")
print(response.text)

# オブジェクトとして利用可能（Pydanticモデル化）
parsed: CurrySpiceMix = response.parsed
print("\n=== 構造化出力 ===")
print(f"レシピ名: {parsed.recipe_name}")
for spice in parsed.spices:
    print(f"- {spice.name}: {spice.amount}（{spice.purpose}）")
```

## コードの詳細解説

### 1. APIキーとクライアント初期化

```python
from google import genai
from google.colab import userdata

# Geminiクライアント初期化
client = genai.Client(api_key=userdata.get('GEMINI_API_KEY'))
```

Colabの環境変数に保存されたAPIキーを使ってGeminiのクライアントを初期化しています。

### 2. Pydanticによるスキーマ定義

```python
from pydantic import BaseModel
from typing import List

# スキーマ定義：スパイス調合
class Spice(BaseModel):
    name: str
    amount: str
    purpose: str

class CurrySpiceMix(BaseModel):
    recipe_name: str
    spices: List[Spice]
```

Pydanticを使って、APIから取得したいデータの型定義を行っています。この定義によって：

- `Spice`クラス：各スパイスの「名前」「量」「目的」の3つのプロパティを持つ
- `CurrySpiceMix`クラス：「レシピ名」と「スパイスのリスト」の2つのプロパティを持つ

この型定義により、取得するデータに対して静的型チェックが可能になります。

### 3. プロンプトと構造化出力の指定

```python
# プロンプト
prompt = (
    "インド風カレーのスパイス調合を提案してください。"
    "スパイスの名前、量、目的を含め、レシピ名とともにJSONで返してください。"
)

# コンテンツ生成リクエスト
response = client.models.generate_content(
    model="gemini-2.0-flash",
    contents=prompt,
    config={
        "response_mime_type": "application/json",
        "response_schema": CurrySpiceMix,
    },
)
```

ここがこの実装の最も重要なポイントです：

- `response_mime_type`: レスポンスの形式をJSONに指定
- `response_schema`: 先ほど定義したPydanticクラスを指定

これにより、Gemini APIは指定されたスキーマに従ったJSONを生成してくれます。

### 4. レスポンスの利用

```python
# 生テキスト表示（JSON文字列）
print("=== JSON出力 ===")
print(response.text)

# オブジェクトとして利用可能（Pydanticモデル化）
parsed: CurrySpiceMix = response.parsed
print("\n=== 構造化出力 ===")
print(f"レシピ名: {parsed.recipe_name}")
for spice in parsed.spices:
    print(f"- {spice.name}: {spice.amount}（{spice.purpose}）")
```

レスポンスは2つの形式で利用できます：

1. `response.text`：生のJSON文字列
2. `response.parsed`：Pydanticモデルにパース済みのオブジェクト

`.parsed`プロパティを使うことで、すでにPydanticモデルとしてパースされたオブジェクトにアクセスできるため、追加のパース処理は不要です。

## 実行結果例


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9ea9275f-f678-4ed6-9d62-e0d6ec2e835b.png)

```
=== JSON出力 ===
{
  "recipe_name": "本格インド風チキンカレー",
  "spices": [
    {
      "name": "ターメリック",
      "amount": "大さじ2",
      "purpose": "色付け、抗炎症作用"
    },
    {
      "name": "コリアンダー",
      "amount": "大さじ3",
      "purpose": "香り、とろみ"
    },
    {
      "name": "クミン",
      "amount": "大さじ2",
      "purpose": "香り、消化促進"
    },
    {
      "name": "レッドチリパウダー",
      "amount": "小さじ1-2 (お好みで調整)",
      "purpose": "辛味"
    },
    {
      "name": "ガラムマサラ",
      "amount": "小さじ2",
      "purpose": "仕上げの香り"
    },
    {
      "name": "ジンジャーパウダー",
      "amount": "小さじ1",
      "purpose": "風味、消化促進"
    },
    {
      "name": "ガーリックパウダー",
      "amount": "小さじ1",
      "purpose": "風味"
    }
  ]
}

=== 構造化出力 ===
レシピ名: 本格インド風チキンカレー
- ターメリック: 大さじ2（色付け、抗炎症作用）
- コリアンダー: 大さじ3（香り、とろみ）
- クミン: 大さじ2（香り、消化促進）
- レッドチリパウダー: 小さじ1-2 (お好みで調整)（辛味）
- ガラムマサラ: 小さじ2（仕上げの香り）
- ジンジャーパウダー: 小さじ1（風味、消化促進）
- ガーリックパウダー: 小さじ1（風味）
```

## この実装のメリット

Pydanticを使うことで、データの型安全性が確保され、手動でJSONを解析する必要がなくなります。スキーマに合わないデータがあった場合には、明確なエラーメッセージが表示されるため、エラー処理も容易です。また、構造化されたオブジェクトに対してIDEの補完が効くため、開発効率が向上します。さらに、Pydanticの自動バリデーション機能により、入力データの整合性を確実に保つことができます。

## 活用例

この実装パターンは以下のようなケースで特に有効です：

- チャットボットから構造化データを取得したい場合
- 生成AIによるデータエントリーの自動化
- 既存のデータモデルとの連携
- 複雑なJSON構造のハンドリング

## 注意点

1. モデルによってはスキーマ通りの出力を生成できない場合があります
2. 複雑すぎるスキーマは失敗しやすいため、シンプルな構造を心がけましょう
3. APIキーの管理には十分注意してください

## まとめ

Google GeminiのAPI + Pydanticによる構造化データ取得は、生成AIを実践的なアプリケーション開発に組み込む際の強力なパターンです。特に、生成AIの出力を他のシステムと連携させる場合に非常に有効です。

是非、みなさんも自分のユースケースに合わせて試してみてください！

## 参考リンク

- [Google Gemini API公式ドキュメント](https://ai.google.dev/docs)
- [Pydantic公式ドキュメント](https://docs.pydantic.dev/)
- [生成AIとの効果的なプロンプトエンジニアリング](https://developers.google.com/machine-learning/resources/prompt-eng)