
Vertex AI SDKからgoogle-genaiへ移行した — Pydanticネイティブな構造化出力が便利
Google Chat Bot の RAG パイプラインで使っていた vertexai.generative_models が非推奨になったので、google-genai SDK に移行しました。移行ついでに構造化出力(Structured Output)を導入したら、Pydantic モデルをそのまま response_schema に渡せて便利だったので紹介します。
前提・環境
- Python 3.14
google-cloud-aiplatform1.88.0(vertexai.rag用に継続利用)google-genai1.x(新規追加)- Pydantic 2.x
- Cloud Functions 2nd gen(Cloud Run ベース)
何が非推奨になったのか
2025年6月24日、Google は google-cloud-aiplatform パッケージ内の以下モジュールを非推奨としました(削除期限: 2026年6月24日)。
vertexai.generative_modelsvertexai.language_modelsvertexai.vision_modelsvertexai.cachingvertexai.tuning
移行先は google-genai パッケージです。
非推奨でないもの: vertexai.rag など、上記リスト以外のモジュールは引き続き利用可能です。RAG Engine を使っている場合、リトリーバル部分は変更不要でした。

移行のポイント
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.parsed が RagAnswer インスタンスでない場合(SDK のバージョン差異等)は response.text から手動パースを試み、それも失敗したらフォールバックの RagAnswer を返します。
依存関係の変更
dependencies = [
"google-cloud-aiplatform>=1.88.0", # vertexai.rag 用に維持
"google-genai>=1.0.0", # 新 SDK(旧 vertexai.generative_models の代替)
"pydantic>=2.13.4", # 構造化出力のスキーマ定義
]
google-cloud-aiplatform は vertexai.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 を別々に管理するだけで済みます。






