Vertex AI SDKからgoogle-genaiへ移行した — Pydanticネイティブな構造化出力が便利

Vertex AI SDKからgoogle-genaiへ移行した — Pydanticネイティブな構造化出力が便利

`vertexai.generative_models` が非推奨になったので `google-genai` SDK に移行しました。Pydantic モデルをそのまま `response_schema` に渡せる構造化出力と、`response.parsed` による型安全なアクセスが特に便利です。
2026.07.10

Google Chat Bot の RAG パイプラインで使っていた vertexai.generative_models が非推奨になったので、google-genai SDK に移行しました。移行ついでに構造化出力(Structured Output)を導入したら、Pydantic モデルをそのまま response_schema に渡せて便利だったので紹介します。

前提・環境

  • Python 3.14
  • google-cloud-aiplatform 1.88.0(vertexai.rag 用に継続利用)
  • google-genai 1.x(新規追加)
  • Pydantic 2.x
  • Cloud Functions 2nd gen(Cloud Run ベース)

何が非推奨になったのか

2025年6月24日、Google は google-cloud-aiplatform パッケージ内の以下モジュールを非推奨としました(削除期限: 2026年6月24日)。

  • vertexai.generative_models
  • vertexai.language_models
  • vertexai.vision_models
  • vertexai.caching
  • vertexai.tuning

移行先は google-genai パッケージです。

非推奨でないもの: vertexai.rag など、上記リスト以外のモジュールは引き続き利用可能です。RAG Engine を使っている場合、リトリーバル部分は変更不要でした。

migrate-vertex-ai-sdk-to-google-genai-with-structured-output-overview

移行のポイント

1. GenerativeModel シングルトン → genai.Client シングルトン

旧 SDK ではモデルオブジェクトがステートフルで、system_instruction をコンストラクタで渡していました。新 SDK ではクライアントオブジェクトが認証情報のみを保持し、モデル設定はリクエストごとに渡します。

Before(旧 SDK):

from vertexai.generative_models import GenerativeModel

model = GenerativeModel(
    "gemini-2.5-flash",
    system_instruction=SYSTEM_PROMPT,
)
response = model.generate_content(prompt)
print(response.text)

After(新 SDK):

from google import genai
from google.genai import types

client = genai.Client(
    vertexai=True,
    project="your-project-id",
    location="asia-northeast1",
)
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=prompt,
    config=types.GenerateContentConfig(
        system_instruction=SYSTEM_PROMPT,
    ),
)
print(response.text)

主な違い:

  • vertexai.init()genai.Client(vertexai=True, project=..., location=...)
  • GenerativeModel(model_id, system_instruction=...)client.models.generate_content(model=..., config=GenerateContentConfig(system_instruction=...))
  • モデルオブジェクトのシングルトン → クライアントオブジェクトのシングルトン

2. vertexai.rag との共存

vertexai.rag は非推奨対象外ですが、aiplatform.init() による初期化が必要です。genai.Client は別途初期化するため、両方の初期化を管理する必要がありました。

from google import genai
from google.cloud import aiplatform
from vertexai import rag

_rag_initialized = False
_genai_client = None
_lock = threading.Lock()

def _ensure_rag_initialized():
    """vertexai.rag 用に aiplatform を初期化(非推奨ではない)"""
    global _rag_initialized
    if not _rag_initialized:
        with _lock:
            if not _rag_initialized:
                aiplatform.init(project=PROJECT_ID, location=LOCATION)
                _rag_initialized = True

def _get_genai_client() -> genai.Client:
    """genai.Client のシングルトン"""
    global _genai_client
    if _genai_client is None:
        with _lock:
            if _genai_client is None:
                _genai_client = genai.Client(
                    vertexai=True,
                    project=PROJECT_ID,
                    location=LOCATION,
                )
    return _genai_client

vertexai.rag を使わないコード(スクリプト等)では aiplatform.init() は不要です。

3. Pydantic ネイティブな構造化出力

移行で最も嬉しかったのがこの点です。旧 SDK の GenerationConfig.response_schema は辞書形式のスキーマ(型名が "STRING", "BOOLEAN" 等の大文字)しか受け付けず、Pydantic の .model_json_schema() が出力する小文字の標準 JSON Schema とは互換性がありませんでした。

旧 SDK — 辞書スキーマを手書きする必要あり:

from vertexai.generative_models import GenerativeModel, GenerationConfig

# Pydantic モデルとは別に辞書スキーマを定義
RAG_ANSWER_SCHEMA = {
    "type": "OBJECT",
    "properties": {
        "answer": {"type": "STRING"},
        "is_answerable": {"type": "BOOLEAN"},
    },
    "required": ["answer", "is_answerable"],
}

response = model.generate_content(
    prompt,
    generation_config=GenerationConfig(
        response_mime_type="application/json",
        response_schema=RAG_ANSWER_SCHEMA,
    ),
)
# 手動で JSON パース
result = RagAnswer.model_validate_json(response.text)

新 SDK — Pydantic モデルをそのまま渡せる:

from pydantic import BaseModel, Field
from google import genai
from google.genai import types

class RagAnswer(BaseModel):
    answer: str = Field(description="回答テキスト(Markdown形式)")
    is_answerable: bool = Field(
        description="ナレッジベースに該当情報があり回答できた場合はtrue、"
        "該当情報がなく回答できなかった場合はfalse",
    )

client = genai.Client(vertexai=True, project=PROJECT_ID, location=LOCATION)
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=prompt,
    config=types.GenerateContentConfig(
        system_instruction=SYSTEM_PROMPT,
        response_schema=RagAnswer,  # Pydantic モデルを直接渡す
        response_mime_type="application/json",  # response_schema 使用時は必須
    ),
)

result = response.parsed  # バリデーション済みの RagAnswer インスタンス
print(result.answer)
print(result.is_answerable)

response.parsed が返す型は response_schema に渡した Pydantic モデルのインスタンスです。JSON パースもバリデーションも SDK がやってくれるので、model_validate_json() を手動で呼ぶ必要がありません。

4. usage_metadata のフィールド名は同一

トークン数の取得に使う response.usage_metadata のフィールド名は新旧 SDK で変わりません。

usage = response.usage_metadata
print(usage.prompt_token_count)       # 入力トークン数
print(usage.candidates_token_count)   # 出力トークン数
print(usage.total_token_count)        # 合計

構造化出力の実用例: RAG のフォールバック検知

移行と同時に解決したかった問題があります。RAG パイプラインで「ナレッジベースに該当情報がなく回答できなかった」ケースの検知です。

キーワードマッチングの限界

当初、システムプロンプトで「情報が見つからない場合は『該当する情報が見つかりませんでした』と伝えてください」と指示し、回答テキストにこのフレーズが含まれるかで判定していました。

_FALLBACK_INDICATOR = "該当する情報が見つかりませんでした"

def is_fallback_response(text: str) -> bool:
    return _FALLBACK_INDICATOR in text

しかし Gemini はプロンプトの指示に関わらず表現を言い換えます。

  • 「情報や手順は見つかりませんでした」
  • 「ナレッジベースには登録されておりません」
  • 「ナレッジベースに該当する情報がないため…」

キーワードを追加するたびに新しいバリエーションが出現し、いたちごっこになりました。

構造化出力による解決

構造化出力を使えば、Gemini 自身に「回答できたか」を boolean で分類させることができます。

class RagAnswer(BaseModel):
    answer: str = Field(description="回答テキスト(Markdown形式)")
    is_answerable: bool = Field(
        description="ナレッジベースに該当情報があり回答できた場合はtrue、"
        "該当情報がなく回答できなかった場合はfalse",
    )

呼び出し側は result.is_answerable を見るだけです。

result = generate_answer(user_text, contexts)
is_fallback = not result.is_answerable

boolean 値なので表現の揺れは発生しません。キーワードリストのメンテナンスも不要になりました。

パース失敗への備え

構造化出力は基本的に安定して JSON を返しますが、念のためパース失敗時のフォールバックを入れています。

response = client.models.generate_content(
    model=MODEL_ID,
    contents=prompt,
    config=types.GenerateContentConfig(
        system_instruction=SYSTEM_PROMPT,
        response_schema=RagAnswer,
        response_mime_type="application/json",
    ),
)

try:
    if isinstance(response.parsed, RagAnswer):
        return response.parsed
    if response.text is not None:
        return RagAnswer.model_validate_json(response.text)
except (ValueError, TypeError):
    logger.warning("Failed to parse structured response, returning fallback")
return RagAnswer(answer=NO_RESULT_MESSAGE, is_answerable=False)

response.parsedRagAnswer インスタンスでない場合(SDK のバージョン差異等)は response.text から手動パースを試み、それも失敗したらフォールバックの RagAnswer を返します。

依存関係の変更

pyproject.toml
dependencies = [
    "google-cloud-aiplatform>=1.88.0",  # vertexai.rag 用に維持
    "google-genai>=1.0.0",              # 新 SDK(旧 vertexai.generative_models の代替)
    "pydantic>=2.13.4",                 # 構造化出力のスキーマ定義
]

google-cloud-aiplatformvertexai.rag を使い続ける限り必要です。google-genai が内部で依存するわけではないので、両方を明示的に指定します。

まとめ

vertexai.generative_models から google-genai SDK への移行はシンプルでした。主な変更点は以下の3つです。

  • 初期化パターン: vertexai.init() + GenerativeModel()genai.Client(vertexai=True)
  • 設定の渡し方: コンストラクタ → リクエストごとの GenerateContentConfig
  • 構造化出力: 辞書スキーマの手書き → Pydantic モデルをそのまま渡す + response.parsed で取得

特に Pydantic ネイティブの構造化出力は、スキーマ定義の二重管理がなくなるだけでなく、response.parsed による型安全なアクセスが得られるため、移行の手間以上のメリットがありました。

vertexai.rag との共存も問題なく、aiplatform.init()genai.Client を別々に管理するだけで済みます。

参考


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

この記事をシェアする

関連記事