【調査レポ6】Google Antigravity SDKでAIの回答を型安全に取得!response_schemaとPydanticによる構造化データ出力を解説

Antigravity SDK入門 AI

前回の記事(調査レポ5)では、Google Antigravity SDKのコアクラスである AgentLocalAgentConfig の設定パラメータを整理しました。

AIエージェントをWebアプリケーションやデータベース、社内システムと連携させる際、大きな課題となるのが「AIの回答フォーマットの揺れ」です。一般的なテキスト出力では、指示通りにJSONが返ってこなかったり、想定外の装飾文字が入ったりして、プログラム側のパース処理でエラーが発生するリスクがあります。

そこで今回は、LocalAgentConfig の設定項目である response_schema と Python のデータ検証ライブラリ Pydantic を組み合わせ、AIの回答を構造化データ(JSON / オブジェクト)として受け取る手法について解説します!

構造化データ出力(Structured Output)とは?

構造化データ出力(Structured Output)とは、AIに自由な文章(フリーテキスト)で回答させるのではなく、「あらかじめプログラム側で定義したデータ構造(JSONスキーマ)に合わせて回答を出力させる機能」です。

通常出力と構造化データ出力の主な違いです。

  • 通常のフリーテキスト出力
    AIが状況に合わせて自然な文章を生成します。人間が読むには最適ですが、特定フィールドの抜き出しや文字列のパースが必要になり、形式が崩れた際にプログラムが停止する懸念があります。
  • 構造化データ出力(response_schema 指定)
    指定した型構造(例: 感情判定満足スコア要約リスト)に従って整ったデータが出力されます。Python側でパースエラーを起こさず、直接オブジェクトのプロパティ(analysis.score など)として安全にデータを利用できます。

Pydanticとは?基本概要と特長

本記事で型定義に使用している Pydantic(パイダンティック) について簡潔に解説します。

Pydantic は、Python においてデータの型定義や自動バリデーション(データ妥当性の検証)を行うための標準的ライブラリです。名前は「Python」と「Pedantic(『細かいルールに厳密な、几帳面な』という意味)」を組み合わせた造語に由来しています。

  • 直感的なクラス構造(BaseModel)
    Pythonの標準的なクラス定義(BaseModel)を継承するだけで、属性の型(文字列、数値、リスト等)を厳密に定義できます。
  • 便利な組み込みクラスメソッド(model_ シリーズ)
    スキーマ生成を行う model_json_schema() や、JSON文字列から直接オブジェクトを復元する model_validate_json() など、強力な組み込みメソッドが最初から用意されています。(※Pydantic v2より model_ プレフィックスで統一されています)
  • JSON Schema の自動変換機能
    組み込みメソッド model_json_schema() を実行することで、Pydantic モデルから標準的な JSON スキーマ辞書を1行で自動生成・出力できます。Antigravity SDK や Gemini API の response_schema と非常に相性が良い特長を持っています。
  • FastAPIやAI開発での業界標準
    モダンなWebフレームワーク(FastAPI)や各種LLMアプリケーション開発で事実上の標準ツールとして広く採用されています。

Pydanticの基本的な使い方(モデル定義と型の自動検証)

BaseModel を継承してデータ構造を定義し、値を渡すだけで自動的に型検証が行われます。

from pydantic import BaseModel, Field

# 1. データ構造(モデル)の定義
class UserProfile(BaseModel):
    user_id: int
    name: str
    is_active: bool = True  # デフォルト値のみを設定するパターン
    # デフォルト値と Field(description=...) を同時に指定するパターン
    email: str = Field(default="user@example.com", description="ユーザーの電子メールアドレス")

# 2. データの生成と型チェック
# is_active や email を省略した場合は設定されたデフォルト値が適用される
user = UserProfile(user_id="101", name="山田太郎")

print(user.user_id)   # ➔ 101 (数値型として正しくアクセス可能)
print(user.is_active) # ➔ True (デフォルト値 True が適用)
print(user.email)     # ➔ user@example.com (Fieldで設定したデフォルト値が適用)

このように、数値形式の文字列("101")を渡しても自動的に数値型へキャストされ、型定義と合致しない不適切なデータが渡された場合は即座にエラー(ValidationError)を発生させて不正なデータを防御してくれます。

サンプルコードのポイント

  • 1. BaseModel の継承による型定義
    クラス定義の際に BaseModel を継承させることで、普通のPythonクラスが「型検証機能付きモデル」へと拡張されます。
  • 2. デフォルト値のみのシンプルな指定方法(is_active)
    is_active: bool = True のようにイコール(=)を使って値を直接代入することで、データ生成時に値が省略された場合のデフォルト値をシンプルに設定できます。
  • 3. Field() を使った同時指定パターン(email)
    email: str = Field(default="user@example.com", description="...") のように、Field(default=...) を使用することで「デフォルト値」と「AIへの指示文(description)」などのパラメータを同時に設定できます。
  • 4. 型の自動キャスト(自動変換)と安全制御
    インスタンス化の際、user_id="101" のように文字列で渡しても、型定義が int であれば Pydantic が解釈して数値の 101 に型変換します。数値に変換できない無効な文字列(例: "abc")が渡された場合は、即座に ValidationError 例外を発生させて不整合なデータの混入を防ぎます。

Pydantic と response_schema を組み合わせたサンプルPythonコード(response_schema_demo.py)

製品レビュー文章を分析し、評価判定(ポジティブ/ネガティブ/中立)、スコア(1〜5)、要約、ポイント一覧を構造化データとして取得するサンプルスクリプトです。インポートから型定義、非同期実行まで記述しています。

import os
import sys
import json
import asyncio
from typing import List
from pydantic import BaseModel, Field

# 1. 安全対策:python-dotenv ライブラリの検出と環境変数の読み込み
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    print("注意: python-dotenv がインストールされていません。環境変数から直にAPIキーを読み込みます。")

# 2. Google Antigravity SDK のインポート
try:
    from google.antigravity import Agent, LocalAgentConfig
except ImportError:
    print("エラー: google.antigravity パッケージが見つかりません。pip install google-antigravity で導入してください。", file=sys.stderr)
    sys.exit(1)


# 3. Pydantic による構造化データの型定義 (出力スキーマ)
class ReviewAnalysis(BaseModel):
    sentiment: str = Field(description="感情分析結果 ('ポジティブ', 'ネガティブ', '中立')")
    score: int = Field(description="5段階の満足度スコア (1: 最低, 5: 最高)")
    summary: str = Field(description="レビュー内容の簡潔な要約 (80文字程度)")
    key_features: List[str] = Field(description="ユーザーが評価している主要な特徴・ポイントのリスト")


# 4. 非同期メイン関数 (async def)
async def main():
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        print("エラー: GEMINI_API_KEY が設定されていません。.env ファイルを確認してください。", file=sys.stderr)
        return

    print("--- response_schema (構造化データ出力) デモを開始します ---\n")

    # Pydantic モデルから JSON Schema 辞書を生成
    schema_dict = ReviewAnalysis.model_json_schema()

    # 5. LocalAgentConfig の設定 (response_schema に JSON Schema 辞書を指定)
    config = LocalAgentConfig(
        system_instructions=(
            "あなたはプロの製品レビュー分析アナリストです。"
            "提供されたカスタマーレビューを分析し、指定されたJSON構造に従って結果を出力してください。"
        ),
        response_schema=schema_dict,  # JSON スキーマを登録
        model="gemini-3.1-flash-lite"
    )

    # 6. 分析対象のレビューテキスト
    sample_review = (
        "購入して2週間使いましたが、非常に満足しています!特にバッテリーの持ちが抜群で、"
        "1回の充電で3日は余裕で持ちます。デザインもスタイリッシュで気に入っています。"
        "ただ、付属の充電ケーブルが少し短いのだけが惜しいポイントでした。"
    )

    user_prompt = f"以下の製品レビューを分析して結果を出力してください:\n\n{sample_review}"
    print(f"入力レビュー文:\n{sample_review}\n")
    print("--- [AIへ分析リクエスト送信中...] ---\n")

    # 7. Agent の非同期制御 (async with & await)
    async with Agent(config) as agent:
        response = await agent.chat(user_prompt)
        
        # 8. レスポンスからJSONテキストを安全に抽出(出力形式の2パターンに対応)
        json_text = ""
        async for chunk in response.chunks:
            # パターン1: ツール呼び出し応答か確認 (hasattrで'name'属性の存在を確認しエラーを防ぐ)
            if hasattr(chunk, 'name') and chunk.name == 'finish' and 'output_string' in chunk.args:
                json_text = chunk.args['output_string']
                break
            # パターン2: 通常の文章応答か確認 (hasattrで'text'属性の存在を確認)
            elif hasattr(chunk, 'text') and chunk.text != "Finished":
                json_text += chunk.text

        # マークダウンのコードブロック指示子 (```json ... ```) があれば除外
        clean_json = json_text.strip()
        if clean_json.startswith("```json"):
            clean_json = clean_json[7:]
        if clean_json.startswith("```"):
            clean_json = clean_json[3:]
        if clean_json.endswith("```"):
            clean_json = clean_json[:-3]
        clean_json = clean_json.strip()

        print("--- [AIからの生のJSON出力] ---")
        print(clean_json)
        print("\n--- [Pydantic による復元・型安全なアクセス] ---")

        # 9. Pydantic モデルへ自動バリデーション&復元
        try:
            analysis = ReviewAnalysis.model_validate_json(clean_json)
            print(f"・判定結果   : {analysis.sentiment}")
            print(f"・満足スコア : {analysis.score} / 5")
            print(f"・要約       : {analysis.summary}")
            print(f"・評価ポイント: {', '.join(analysis.key_features)}")
        except Exception as e:
            print(f"JSONパースエラー: {e}")


# 10. Python非同期イベントループの起動トリガー
if __name__ == "__main__":
    asyncio.run(main())

プログラムの実行手順と動作確認

手元の環境で動作確認を行う手順です。Pydantic ライブラリが導入されていない場合は追加でインストールします。
仮想環境の準備等の詳細については調査レポ1をご参照ください。

手順1. ライブラリの準備

# 仮想環境の有効化
source .venv/bin/activate

# Pydantic パッケージのインストール
pip install pydantic

手順2. プログラムの実行

% python3 response_schema_demo.py
--- response_schema (構造化データ出力) デモを開始します ---

入力レビュー文:
購入して2週間使いましたが、非常に満足しています!特にバッテリーの持ちが抜群で、1回の充電で3日は余裕で持ちます。デザインもスタイリッシュで気に入っています。ただ、付属の充電ケーブルが少し短いのだけが惜しいポイントでした。

--- [AIへ分析リクエスト送信中...] ---

--- [AIからの生のJSON出力] ---
{"key_features":["バッテリー持ちが良い","スタイリッシュなデザイン","付属充電ケーブルが短い"],"score":5,"sentiment":"ポジティブ","summary":"長時間のバッテリー持ちとデザインの良さが高評価。ただし付属ケーブルの短さが唯一の改善点。"}

--- [Pydantic による復元・型安全なアクセス] ---
・判定結果   : ポジティブ
・満足スコア : 5 / 5
・要約       : 長時間のバッテリー持ちとデザインの良さが高評価。ただし付属ケーブルの短さが唯一の改善点。
・評価ポイント: バッテリー持ちが良い, スタイリッシュなデザイン, 付属充電ケーブルが短い

AIが指示通りのJSON構造を出力し、Pythonの Pydantic オブジェクト(analysis.sentimentanalysis.score)にアクセスできていることが確認できます!

プログラムコードの詳細解説

スクリプト内で使用している3つの重要ポイントの解説です。

① Pydantic による型定義と Field(description=…) の役割

BaseModel を継承したクラス内に、型ヒント(str, int, List[str])を定義します。各属性に Field(description="...") を添えることで、AIに対して「このフィールドにどのようなデータを埋め込むべきか」という指示ガイドとして機能します。

② model_json_schema() によるスキーマ自動生成

model_json_schema() は Pydantic に用意されている標準組み込みクラスメソッドです。ReviewAnalysis.model_json_schema() を呼び出すことで、定義した型情報から標準的な JSON Schema 辞書オブジェクト(Dict)が1行で自動生成されます。これを LocalAgentConfig(response_schema=...) に渡すことで、Gemini APIに適切なスキーマ制約がセットされます。

ここで生成される 「JSON Schema(ジェイソン・スキーマ)辞書」 とは、JSON データの「型・必須項目・説明文」のルールを定義した世界共通の標準規格(仕様書データ)を Python の辞書形式(Dict)で表現したものです。AIモデル(LLM)は Python のクラス構造(Pydanticモデル)を直接解釈できないため、Pydantic がこの標準的な「JSON Schema 辞書」へ自動変換して渡すことで、AIが『このルールに従ってデータを生成すればいい』と正確に理解できるようになります。

③ response.chunks からの安全な結果抽出と「finish ツール」の役割

Antigravity SDK は自律型AIエージェントを制御するためのフレームワークです。エージェントはタスクの完了を判定・通知するために、SDK内部で用意されている制御機能である finish ツール を呼び出して処理の終了を宣言します。

response_schema(構造化データ)を指定した場合、AIエージェントは生成した最終成果物(JSON文字列)を、この finish ツールの引数に格納してタスク完了を送信することがあります。一方、通常のテキストチャンク(Text)として出力される場合もあります。

ここで登場する output_string とは、AIエージェントが finish ツールを呼び出す際に「最終的な回答データ(生成されたJSON文字列)」を格納してPython側に渡すための引数名(パラメータ)です。プログラム側からは chunk.args['output_string'] と記述することで、AIが作成した成果物文字列を直接取り出すことができます。

そもそも nametext とは、AIエージェントの行動(出力の種類)によって使い分けられるレスポンス要素のプロパティ(属性)です。

  • name(実行ツール名)
    AIがツール(関数)を実行する行動をとった際に保持される属性です(例: chunk.name = 'finish')。ツール呼び出し応答にのみ存在します。
  • text(会話文章テキスト)
    AIが人間への回答文章を出力している際に保持される属性です(例: chunk.text = '{sentiment: ...}')。通常の文章テキスト応答にのみ存在します。

【補足】なぜAIからの回答の返し方に2種類存在するのか?
Antigravity SDK のような自律AIエージェント環境では、AIが人間(呼び出し元)へ成果物を返却するルートとして以下の2パターンが存在するためです。

  • ルート1. 会話テキスト方式(text 属性)
    AIがチャット画面でおしゃべりするように、回答テキスト(JSON文字列)を直に出力して届けるパターンです。
  • ルート2. 完了ツール引数方式(name=’finish’ 属性)
    AIが思考を終え、「タスク完了関数(finish)」を呼び出して、その引数(output_string)に成果物データを梱包して届けるパターンです。

サンプルコードでは hasattr(chunk, 'name')hasattr(chunk, 'text') の両方を分岐チェックすることで、AIがどちらのルートで回答を返してきた場合であっても、プログラム側で回答結果のJSONを取り出せるように設計しています。

④ マークダウン装飾(“`json)のクレンジング処理

AIモデル(Gemini等)は、JSON文字列を出力する際に親切心からマークダウンのコードブロック装飾(```json ... ```)で囲んで返してくることがあります。そのまま Pydantic や json.loads() に渡すとパースエラー(構文エラー)の原因となるため、コード内では startswith("```json")endswith("```") を使って余分な装飾文字を切り落とし、純粋なJSON文字列だけを抽出する前処理を行っています。

⑤ model_validate_json() による Pydantic モデルへの復元と型安全アクセス

model_validate_json() も Pydantic の標準組み込みクラスメソッドです。生の JSON 文字列をそのまま引数として受け取り、内部で「JSONのパース(構造分解)」と「型の検証(バリデーション)」を一括して行って Pydantic オブジェクトへと復元します。手動での json.loads() などを経由せずに1ステップで復元でき、型構造が不正な場合は即座に ValidationError 例外を発生させて安全に検知できます。

構造化データ出力利用時の注意点

response_schema を扱う際、特に注意したいポイントです。

① 複雑すぎるネスト構造による精度低下

スキーマのネスト(階層)が深すぎたり、フィールド数が多すぎたりすると、AIがスキーマ条件を満たそうとして生成速度が落ちたり、一部フィールドの生成をスキップしたりするケースがあります。実務では必要なフィールドをシンプルに設計することをお勧めします。

② 必須フィールド(Required)とデフォルト値

Pydantic モデルでオプショナル(省略可能)な項目がある場合は、Optional[str] = NoneField(default=None, description="...") のようにデフォルト値を明記しておくと、AIが該当情報を取得できなかった場合にパースエラーを防ぐことができます。

まとめ

今回は、Google Antigravity SDK の response_schema パラメータと Pydantic を組み合わせて、AIの回答を型安全な構造化データとして確実に取得する手法を解説しました。

テキストのパース処理が不要になり、直接オブジェクトの属性として扱えるため、Webアプリの開発や自動化バッチの構築がスムーズになります。ぜひ本記事のサンプルコードを参考に、ご自身の自作エージェントに構造化データ出力を導入してみてください!

コメント

タイトルとURLをコピーしました