CTS-KB

Vertex AI Batch API 編 — GCS 経路で書き直す覚悟と二系統運用パターン

⏱ 約 10 分で読めます
#Gemini #Vertex AI #Batch API #GCS #JSONL #Cloud Storage #移行

🎯 はじめに:「同じ Batch API」と思って書き始めると確実にハマる

本記事はシリーズ 「Gemini AI プラットフォーム活用」の最終回(第 6 回) です。前回 Part 5: Vertex AI 移行ガイド(基本編) では「vertexai=True を加えるだけで移行完了」と書きました。これは Batch API を除いた話です。

本記事の核心: AI Studio の Batch API(Part 3)と Vertex AI の Batch API は、同じ「Gemini Batch API」を名乗りながら入出力経路が完全に別物です。前者は Files API(Google が提供する一時ファイル領域)、後者は GCS(Google Cloud Storage)バケットを経由します。SDK 上で client.batches.create() という同名のメソッドを呼ぶため、引数構造が全く違うことに気付かないままコピペすると、リクエスト失敗・JSONL スキーマ不一致・IAM 権限不足・ジョブ ID 形式の差異で立て続けに詰まります。

本記事は、その書き直しコストを最小化するための実装ガイドです。

🔍 AI Studio Batch vs Vertex AI Batch の決定的な違い

項目AI Studio Batch(Part 3Vertex AI Batch
入力経路client.files.upload()Files API にアップロードGCS バケットに JSONL を gs:// URI で配置
出力経路client.files.download() で Files API から取得GCS バケットに JSONL が出力される
認証APIキーサービスアカウント / ADC(要 GCS + Vertex AI 権限)
JSONL の request スキーマGemini Developer API 形式Vertex AI 形式(generateContent 互換)
ジョブ ID 形式batches/xxxprojects/.../locations/.../batchPredictionJobs/xxx
対応モデルGemini 全モデル + Embedding 系全モデルGemini LLM 系(Embedding 系は限定的、gemini-embedding-001 は未対応
インフラ準備不要(APIキーのみ)GCS バケット作成、IAM ロール、リージョン整合
料金通常の 50% オフ通常の 50% オフ(同等)
ジョブ管理 UIAI Studio の DashboardVertex AI コンソール → バッチ推論

💡 「同じ SDK で vertexai=True を切り替えるだけ」が通じない数少ない領域。ここだけは設計時から Vertex AI 用に書き直す前提でいてください。

🚨 ハマりどころ Top 5

1. gemini-embedding-001 は Vertex AI Batch では未サポート

公式ドキュメント(Vertex AI Batch Embeddings)で明記されており、多言語対応の主力 Embedding モデルである gemini-embedding-001 は Vertex AI Batch API で使えません

このため、エンタープライズ運用でも以下の二系統運用が現実解になります。

Embedding 系 → AI Studio Batch(Files API)          ← 多言語 gemini-embedding-001 が使える
LLM 系       → Vertex AI Batch(GCS)                ← gemini-2.5-flash 等、エンタープライズ要件

「将来 Vertex AI に統一」という雑な計画は破棄しましょう。少なくとも 2026 年時点では、日本語ワークロードの Embedding を Vertex AI Batch に寄せるのは不可能です。

2. JSONL スキーマが違う(コピペが効かない)

同じ「JSONL に 1 行 1 リクエスト」でも、request 配下のスキーマが異なります。

AI Studio Batch(Part 3 で扱った形式):

{
  "key": "req_1",
  "request": {
    "contents": [
      { "parts": [{ "text": "商品Aを 1 行で説明して" }] }
    ]
  }
}

Vertex AI Batch:

{
  "request": {
    "contents": [
      { "role": "user", "parts": [{ "text": "商品Aを 1 行で説明して" }] }
    ],
    "generationConfig": { "temperature": 0.2, "maxOutputTokens": 256 },
    "systemInstruction": { "parts": [{ "text": "あなたは EC のコピーライターです。" }] }
  }
}

主な差分:

  • AI Studio は key で結果突合、Vertex AI は 行順 or labels フィールドで突合
  • Vertex AI は role を明示する必要がある(user / model / system
  • generationConfig / systemInstruction は Vertex AI 形式準拠(generateContent API と同じスキーマ)

JSONL を生成するコードは別関数として書く。共通化して if vertex: ... else: ... で分岐させると、後でモデル機能差分(thinking、grounding、function calling など)が増えるたびに分岐が腐ります。

3. GCS バケットのリージョンは Vertex AI のリージョンと一致必須

NG: GCS バケット = us-central1, Vertex AI = asia-northeast1
   → ジョブ作成時に "Bucket region must match" エラー

OK: GCS バケット = asia-northeast1, Vertex AI = asia-northeast1

マルチリージョンバケット(asia など)も NG のケースがあります。シングルリージョンで揃えるのが安全。

4. IAM ロールの組み合わせを間違えると無限リトライする

最低限必要な権限:

主体必要なロール用途
実行サービスアカウントroles/aiplatform.userVertex AI ジョブ投入
実行サービスアカウントroles/storage.objectAdmin(対象バケット限定)入力 JSONL 読込 + 出力 JSONL 書込
Vertex AI サービスエージェント(自動作成される)roles/storage.objectAdmin(対象バケット限定)バックエンド側で GCS にアクセス

3 番目のサービスエージェント権限を付け忘れる事故が頻発します。「自分のサービスアカウントには付けたのに動かない」場合、これが原因。Vertex AI のジョブ実行は内部的に service-{project_number}@gcp-sa-aiplatform.iam.gserviceaccount.com というエージェントが GCS を読み書きします。

5. ジョブ ID 形式が違うので、SDK の client.files.get() が使えない

AI Studio Batch のジョブ取得は batches/xxx(短い識別子)ですが、Vertex AI は projects/{p}/locations/{l}/batchPredictionJobs/{id}(長いリソース名)です。

# AI Studio
batch_job = client.batches.get(name="batches/abc123")

# Vertex AI(同じ SDK だが name の形式が違う)
batch_job = client.batches.get(
    name="projects/my-project/locations/asia-northeast1/batchPredictionJobs/1234567890"
)

DB に保存するジョブ識別子は Provider 別に値オブジェクトで抽象化するのが安全です。例えば BatchJobId = "provider:raw_id" のような型を作っておけば、AI Studio / Vertex AI どちらの形式も同じカラムに格納でき、SAGA 状態遷移ロジックが Provider 中立になります。

🛠 実装テンプレート(Vertex AI Batch)

前提準備

# 1. GCS バケット作成(Vertex AI と同リージョン)
gcloud storage buckets create gs://your-vertex-batch-bucket \
  --location=asia-northeast1 \
  --uniform-bucket-level-access

# 2. サービスアカウントへの権限付与
PROJECT_ID="your-gcp-project"
SA="your-sa@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="serviceAccount:${SA}" \
  --role="roles/aiplatform.user"

gcloud storage buckets add-iam-policy-binding gs://your-vertex-batch-bucket \
  --member="serviceAccount:${SA}" \
  --role="roles/storage.objectAdmin"

# 3. Vertex AI サービスエージェントへの権限付与(忘れがち)
PROJECT_NUM=$(gcloud projects describe "${PROJECT_ID}" --format='value(projectNumber)')
gcloud storage buckets add-iam-policy-binding gs://your-vertex-batch-bucket \
  --member="serviceAccount:service-${PROJECT_NUM}@gcp-sa-aiplatform.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

Python 実装

from google import genai
from google.cloud import storage
import json
import time

# Vertex AI モードでクライアント作成(Part 5 のとおり)
client = genai.Client(
    vertexai=True,
    project="your-gcp-project",
    location="asia-northeast1",
)

BUCKET = "your-vertex-batch-bucket"
INPUT_PATH = "input/req_2026-05-02.jsonl"
OUTPUT_PREFIX = "output/req_2026-05-02"

# 1. JSONL を作成して GCS にアップロード
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET)

requests_data = [
    {
        "request": {
            "contents": [
                {"role": "user", "parts": [{"text": f"商品 {i} を 1 行で説明して"}]}
            ],
            "generationConfig": {"temperature": 0.2, "maxOutputTokens": 256}
        }
    }
    for i in range(1000)
]

# JSONL を 1 行ずつ書き出し → GCS へ
local_path = "/tmp/batch_input.jsonl"
with open(local_path, "w") as f:
    for req in requests_data:
        f.write(json.dumps(req, ensure_ascii=False) + "\n")

bucket.blob(INPUT_PATH).upload_from_filename(local_path)
print(f"アップロード完了: gs://{BUCKET}/{INPUT_PATH}")

# 2. Batch ジョブ作成(src / dest が gs:// URI)
batch_job = client.batches.create(
    model="gemini-2.5-flash",
    src=f"gs://{BUCKET}/{INPUT_PATH}",
    config={
        "display_name": "product-descriptions-vertex-batch",
        "dest": f"gs://{BUCKET}/{OUTPUT_PREFIX}",
    },
)
print(f"ジョブ作成: {batch_job.name}")
# → 例: projects/123/locations/asia-northeast1/batchPredictionJobs/9876543210

# 3. ポーリング(24 時間 SLO)
TERMINAL = {"JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED", "JOB_STATE_CANCELLED"}
while batch_job.state.name not in TERMINAL:
    time.sleep(60)
    batch_job = client.batches.get(name=batch_job.name)
    print(f"状態: {batch_job.state.name}")

# 4. 結果取得(GCS から)
if batch_job.state.name == "JOB_STATE_SUCCEEDED":
    # 出力先プレフィックス配下の predictions.jsonl を読む
    blobs = list(bucket.list_blobs(prefix=OUTPUT_PREFIX))
    for blob in blobs:
        if blob.name.endswith(".jsonl"):
            content = blob.download_as_text()
            for line in content.splitlines():
                obj = json.loads(line)
                # obj に request / response / status がそれぞれ入る
                print(obj.get("response"))

Embedding は AI Studio 側で書く(再掲)

gemini-embedding-001 は Vertex AI Batch 未対応なので、Embedding だけは Part 3 / Part 4 の AI Studio 経路で書くのが正解です。

# Embedding は AI Studio で
ai_studio_client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])

embed_batch = ai_studio_client.batches.create_embeddings(
    model="gemini-embedding-001",
    requests=[
        {"content": "商品Aの説明..."},
        {"content": "商品Bの説明..."},
    ],
    config={"display_name": "embeddings-batch"},
)

🏗 二系統運用の設計パターン

Provider 抽象化(推奨)

実装上は 「どの Batch を使っているか」を呼び出し側に意識させない抽象化が効きます。

from abc import ABC, abstractmethod

class GeminiBatchProvider(ABC):
    """Gemini Batch API の Provider 抽象。AI Studio / Vertex AI を切替可能にする。"""
    @abstractmethod
    async def submit(self, jsonl_path: str) -> str: ...
    @abstractmethod
    async def get_status(self, job_id: str) -> str: ...
    @abstractmethod
    async def fetch_results(self, job_id: str) -> list[dict]: ...

class AiStudioBatchProvider(GeminiBatchProvider):
    """Files API 経路(gemini-embedding-001 など多言語 Embedding 向け)"""
    # ...

class VertexBatchProvider(GeminiBatchProvider):
    """GCS 経路(LLM 系のエンタープライズ運用向け)"""
    # ...

抽象化のメリット:

  • 呼び出し側 SAGA / Workflow は「どの Provider を使うか」を Config で切替可能
  • 将来 Vertex AI 側で gemini-embedding-001 がサポートされた時、Provider 1 つ差し替えで移行可能
  • ジョブ ID 形式の違いも値オブジェクト(BatchJobId = "provider:raw_id")で吸収

SAGA / 状態管理での冪等性

Batch API は最大 24 時間の非同期処理なので、SAGA パターン(Submit → Poll → Save の 3 段階)で状態を永続化するのが定石です。

[Submit Step]
  ├─ JSONL 作成 → GCS / Files API へアップロード
  ├─ batches.create() でジョブ投入
  └─ DB に job_id, state=SUBMITTED で保存

[Poll Step]
  ├─ 1 分間隔で batches.get(name=job_id)
  ├─ TERMINAL 状態なら Save Step へ
  └─ ポーリング上限超過 → state=TIMED_OUT で人手復旧待ち

[Save Step]
  ├─ 結果 JSONL を取得(GCS or Files API)
  ├─ DB に冪等保存(同じ job_id で再実行しても二重保存しない)
  └─ state=SAVED で確定

💡 ポーリング上限を超えても課金は発生済み。タイムアウトしてデータが失われると、コストだけ払って結果なしの最悪パターンになります。SAGA で TIMED_OUT 状態を作り、job_id を指定して結果取得を再開できる手動復旧用 CLI コマンドを用意しておくのが運用の保険です。

🧰 ハマりどころチェックリスト

実装前に必ず確認:

  • GCS バケットのリージョンは Vertex AI のリージョンと一致しているか?
  • サービスアカウントroles/aiplatform.user + roles/storage.objectAdmin を付与したか?
  • Vertex AI サービスエージェントservice-{project_num}@gcp-sa-aiplatform...)に GCS 権限を付与したか?
  • JSONL の request スキーマは Vertex AI 形式role 必須、generationConfigsystemInstruction)か?
  • 使うモデルは Vertex AI Batch でサポートされているか?(特に Embedding 系は要確認)
  • ジョブ ID を DB 保存するなら、Provider 別に値オブジェクトで抽象化したか?
  • ポーリングタイムアウト時の復旧手段を用意したか?(24 時間 SLO 全使用時の対策)
  • GCS バケットのライフサイクルポリシーを設定したか?(古い JSONL の自動削除)

🔄 二系統運用のコスト試算

「AI Studio + Vertex AI の両方を使う」運用で、月間コストはどう見えるか。Embedding を AI Studio、LLM を Vertex AI で運用するパターン:

仮定: 月 100 万件の商品を処理
  Embedding: 1 件あたり 100 トークン入力
  LLM:       1 件あたり 500 トークン入力 / 200 トークン出力

[AI Studio Batch(Embedding)]
  Embedding: 100M トークン × $0.075 / 1M = $7.50

[Vertex AI Batch(LLM、gemini-2.5-flash)]
  入力: 500M トークン × $0.075 / 1M = $37.50
  出力: 200M トークン × $0.30  / 1M = $60.00
  小計: $97.50

合計: $105/月(約 16,000 円)

💡 100 万件規模で月 1.6 万円。Batch API(50% オフ)を使わない場合は倍の 3.2 万円になります。書き直しコストは 1〜2 日でも、年間で約 20 万円の差。Vertex AI 側は GCS 経路の書き直しが必要ですが、その投資はすぐ回収できます。

✅ 第 6 回まとめ・シリーズ完結

  • AI Studio Batch(Files API)と Vertex AI Batch(GCS)は 同名の API でも別物vertexai=True 切替では足りない
  • gemini-embedding-001 は Vertex AI Batch 未対応 → Embedding は AI Studio 固定の二系統運用が現実解
  • JSONL スキーマも別物(role 必須、generationConfig / systemInstruction は Vertex AI 形式)
  • GCS バケットのリージョン整合Vertex AI サービスエージェントの IAMが頻発するハマりどころ
  • ジョブ識別子は形式が違うので BatchJobId = "provider:raw_id" のように Provider 別値オブジェクトで抽象化するのが安全
  • Batch API は 24 時間 SLO の非同期処理 → SAGA パターン(Submit / Poll / Save)で状態管理
  • 二系統運用でも年間コストメリットは大きい(Batch 50% オフが両系統に効く)

シリーズ全 6 回を通じて、個人 PoC レベルから本番マルチテナント運用までの Gemini 活用の全体像を扱いました。各回の振り返り:

📚 シリーズ記事

#タイトル内容
1Google AI Studio 入門課金・APIキー・料金体系・無料枠・予算アラート
2モデル選定とマルチモーダル活用Flash / Pro / Flash-Lite、画像・動画・音声・PDF
3Batch API で 50% 安く使う非同期バッチ、JSONL、Flash-Lite × Batch(AI Studio 編)
4Python 実装ガイドテキスト・Embedding・Vision・モデル切替
5Vertex AI 移行ガイド(基本編)エンタープライズ運用、Provisioned Throughput
6Vertex AI Batch API 編(本記事)GCS 経路、AI Studio Batch との違い、二系統運用

🔗 関連リソース