GCP Vertex AI Batch 設定 Step by Step:用 Gemini 批次處理大量文字
先講結論:Batch Prediction 是什麼、為什麼要用
你有一批文字要丟給 Gemini 處理。可能是幾百筆客服記錄要分類,可能是上千篇文章要摘要。
一筆一筆送 API?可以。但你會撞到 rate limit,程式要處理重試邏輯,還要自己管狀態。
Batch Prediction 讓你把所有 request 打包成一個 JSONL 檔案,丟上去,等它跑完,一次撈回所有結果。不用管 rate limit,不用寫 retry,費用還比 online prediction 便宜。
代價是等待時間。Batch Job 不是即時的,排隊加處理可能幾分鐘到幾小時。但如果你不急,這是最省事的做法。
整體流程
啟用 API → 建 Bucket → 設權限 → 產金鑰 → 設環境變數
→ 準備 JSONL → 上傳 GCS → 建 Batch Job → 輪詢狀態 → 下載結果
前半段只做一次,後半段每次跑新 Job 都會用到。
1. 前置準備
你需要:
- Google 帳號
- GCP 專案(已有或新建都行)
- 本機開發環境(Node.js 18+ 或 Python 3.10+)
登入 GCP 控制台後,先記下兩個東西:
| 項目 | 範例值 | 在哪裡看 |
|---|---|---|
| 專案 ID | my-gcp-project-123 | 控制台頂部的專案選擇器 |
| 服務帳戶 Email | vertex-batch-sa@my-gcp-project-123.iam.gserviceaccount.com | IAM → 服務帳戶頁面 |
2. 啟用 Vertex AI API
方法一:Console(推薦新手)
前往 Vertex AI Studio:
https://console.cloud.google.com/vertex-ai?project=YOUR_PROJECT_ID
頁面會提示啟用所需 API,點「同意並繼續」。需要啟用的 API:
- Vertex AI API(
aiplatform.googleapis.com) - Cloud Storage(
storage.googleapis.com) - Dataform API(Vertex AI 相依)
等 1~2 分鐘,頂部警告橫幅消失就好。
方法二:gcloud CLI
gcloud services enable aiplatform.googleapis.com --project=YOUR_PROJECT_ID
gcloud services enable storage.googleapis.com --project=YOUR_PROJECT_ID
3. 建立 Cloud Storage Bucket
Batch Job 需要一個 GCS Bucket 來放輸入的 JSONL 和輸出結果。
Console 建立
前往 Cloud Storage → 點「建立 bucket」:
| 欄位 | 建議值 | 說明 |
|---|---|---|
| 名稱 | vertex-batch-YOUR_PROJECT_ID | 全球唯一,建議加上專案 ID |
| 位置類型 | Region | 單一區域,費用最低 |
| 區域 | asia-east1(台灣) | 價格與 us-central1 相同,有地利之便 |
| 儲存空間級別 | Standard | 適合頻繁存取 |
| 存取控制 | 統一(IAM) | 安全性較高 |
| 公開存取 | 禁止公開存取 | 必選 |
gcloud CLI
gcloud storage buckets create gs://vertex-batch-YOUR_PROJECT_ID \
--project=YOUR_PROJECT_ID \
--location=asia-east1
Bucket 區域要跟 GCP_LOCATION=global 一致嗎?
不用。 兩件事不要搞混:
GCP_LOCATION=global→ Vertex AI API endpoint 的設定,決定你呼叫哪個 API 入口- Bucket 的
location=asia-east1→ 資料實際儲存的地方
global endpoint 可以存取任何 region 的 Bucket,asia-east1 對台灣用戶延遲最低,價格與 us-central1 相同,兩者完全相容。
Bucket 不要設成 multi-region
multi-region storage 費用約為 regional 的 2 倍,對 Batch Job 沒有效能優勢。regional bucket 在同區域存取延遲更低,價格更便宜。
Lifecycle Policy:自動清理舊檔案
GCS 的檔案不會自動過期,放多久就收多久的錢。費用其實很低(Standard Storage $0.020/GB/月),但養成好習慣仍然值得。
設定方式:Bucket →「生命週期」頁籤 →「新增規則」→ 動作選「刪除物件」→ 條件選「存在時間 30 天」。
或用 CLI:
cat > lifecycle.json << 'EOF'
{
"lifecycle": {
"rule": [{
"action": { "type": "Delete" },
"condition": { "age": 30 }
}]
}
}
EOF
gcloud storage buckets update gs://YOUR_BUCKET_NAME --lifecycle-file=lifecycle.json
4. 設定 IAM 服務帳戶權限
服務帳戶需要三個角色,分別在專案層級和 Bucket 層級設定。
專案層級
前往 IAM 頁面,找到你的服務帳戶,點右側鉛筆圖示,新增:
| 角色 | 用途 |
|---|---|
Vertex AI 使用者(roles/aiplatform.user) | 提交 / 查詢 Batch Job |
BigQuery 使用者 | Vertex AI 內部相依(通常系統會自動加) |
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:YOUR_SA_EMAIL" \
--role="roles/aiplatform.user"
Bucket 層級
前往 Bucket 的「權限」頁籤 → 「授予存取權」→ 填入服務帳戶 Email → 角色選 Storage 物件使用者(roles/storage.objectUser)。
gcloud storage buckets add-iam-policy-binding gs://YOUR_BUCKET_NAME \
--member="serviceAccount:YOUR_SA_EMAIL" \
--role="roles/storage.objectUser"
權限總覽
| 角色 | 層級 | 用途 |
|---|---|---|
Vertex AI 使用者 | 專案 | 提交 / 查詢 Batch Job |
Storage 物件使用者 | Bucket | 上傳 JSONL / 下載結果 |
BigQuery 使用者 | 專案 | Vertex AI 內部相依 |
權限不足是最常見的卡關原因
如果遇到Permission denied,第一步就是回來對這張表。
5. 產生服務帳戶 JSON 金鑰
本地開發需要 JSON 金鑰來驗證身份。
- 前往「IAM 與管理」→「服務帳戶」→ 點你的 SA → 上方「金鑰」頁籤
- 若還未建立服務帳戶,先建一個
- 點「新增鍵」→「建立新的金鑰」→ 類型選 JSON → 建立
- 瀏覽器會下載一個
.json檔案 - 移到安全目錄:
mkdir -p ~/.gcp
mv ~/Downloads/my-gcp-project-*.json ~/.gcp/service-account.json
金鑰洩漏 = 別人用你的 GCP 帳戶花你的錢
把~/.gcp/和.env.local加進.gitignore,不要上傳到任何公開平台。
6. 設定環境變數
在專案根目錄建立 .env.local:
# 服務帳戶 JSON 金鑰的完整路徑
GOOGLE_APPLICATION_CREDENTIALS=/Users/yourname/.gcp/service-account.json
# GCP 專案 ID
GCP_PROJECT_ID=my-gcp-project-123
# Vertex AI 區域 — 重要:用 global
GCP_LOCATION=global
# Bucket 名稱(不含 gs:// 前綴)
GCS_BUCKET_NAME=vertex-batch-my-gcp-project-123
各變數說明
| 變數名稱 | 取得方式 | 備註 |
|---|---|---|
GOOGLE_APPLICATION_CREDENTIALS | 步驟 5 下載的 JSON 檔案路徑 | 用絕對路徑 |
GCP_PROJECT_ID | GCP 控制台頂部專案選擇器 | |
GCP_LOCATION | 固定填 global | 見下方說明 |
GCS_BUCKET_NAME | 步驟 3 建立的 Bucket 名稱 | 不含 gs:// 前綴 |
GCP_LOCATION 為什麼要用 global?
這是最容易踩到的坑。
Preview 模型(如 gemini-3.1-pro-preview)的 Batch Prediction 只有 global 才能用。設成 asia-east1 或 us-central1 會收到:
The PublisherModel gemini-3.1-pro-preview does not exist
模型名稱完全正確,但 location 不對就是 NOT_FOUND。穩定版模型在 asia-east1 等特定 region 也能跑,但為了統一,直接用 global 省得換模型還要改 location。
7. 準備 JSONL 輸入檔案
JSONL 是什麼?
JSONL(JSON Lines)= 每行一個獨立的 JSON 物件。每行代表一筆要送給 Gemini 的 request。
基本格式
{
"request": {
"systemInstruction": {
"parts": [{ "text": "你是文字處理助手。請用繁體中文回覆。" }]
},
"contents": [{
"role": "user",
"parts": [{ "text": "[REQUEST_ID:req_001]\n請摘要以下文章:..." }]
}],
"generationConfig": {
"thinkingConfig": { "thinkingBudget": 8192 }
}
}
}
實際 JSONL 中每筆 request 必須壓成一行
上面是格式化顯示,方便閱讀。
怎麼識別每筆 request?
Vertex AI Batch Prediction 沒有頂層 key 欄位來標記資料。解法是把識別碼嵌入 prompt 開頭:
[REQUEST_ID:req_001]
請摘要以下文章...
結果回來後,從 response 內容中找到這個標記,就能對應回原始資料。
用 Node.js 產生 JSONL
import * as fs from 'fs';
interface RequestItem {
id: string;
text: string;
}
function buildJsonl(items: RequestItem[]): string {
return items.map(item => {
return JSON.stringify({
request: {
systemInstruction: {
parts: [{ text: '你是文字處理助手。請用繁體中文回覆。' }]
},
contents: [{
role: 'user',
parts: [
{ text: `[REQUEST_ID:${item.id}]\n${item.text}` }
]
}],
generationConfig: {
thinkingConfig: { thinkingBudget: 8192 }
}
}
});
}).join('\n');
}
const jsonl = buildJsonl([
{ id: 'req_001', text: '請摘要以下文章:...' },
{ id: 'req_002', text: '請分類以下客服記錄:...' },
]);
fs.writeFileSync('./input.jsonl', jsonl);
8. 建立並執行 Batch Job
上傳 JSONL 到 GCS
Console:前往 Bucket → 點「上傳檔案」→ 選 input.jsonl。建議放在子資料夾中,例如 batch-jobs/job_001/input.jsonl。
gcloud CLI:
gcloud storage cp ./input.jsonl gs://YOUR_BUCKET_NAME/batch-jobs/job_001/input.jsonl
Node.js SDK:
import { Storage } from '@google-cloud/storage';
const storage = new Storage({
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
projectId: process.env.GCP_PROJECT_ID,
});
const bucket = storage.bucket(process.env.GCS_BUCKET_NAME!);
const file = bucket.file('batch-jobs/job_001/input.jsonl');
await file.save(jsonlContent, { contentType: 'application/jsonl' });
建立 Batch Prediction Job
curl(REST API):
PROJECT_ID="my-gcp-project-123"
LOCATION="global"
MODEL="gemini-2.5-pro"
BUCKET="vertex-batch-my-gcp-project-123"
ACCESS_TOKEN=$(gcloud auth print-access-token)
curl -X POST \
"https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/batchPredictionJobs" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"displayName\": \"text-processing-batch-001\",
\"model\": \"publishers/google/models/${MODEL}\",
\"inputConfig\": {
\"instancesFormat\": \"jsonl\",
\"gcsSource\": {
\"uris\": [\"gs://${BUCKET}/batch-jobs/job_001/input.jsonl\"]
}
},
\"outputConfig\": {
\"predictionsFormat\": \"jsonl\",
\"gcsDestination\": {
\"outputUriPrefix\": \"gs://${BUCKET}/batch-jobs/job_001/output/\"
}
}
}"
回傳會包含 name 欄位(Job 的完整路徑),後續查詢狀態要用。
Node.js SDK:
import { v1 } from '@google-cloud/aiplatform';
const client = new v1.JobServiceClient({
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
apiEndpoint: `${process.env.GCP_LOCATION}-aiplatform.googleapis.com`,
});
const [job] = await client.createBatchPredictionJob({
parent: `projects/${process.env.GCP_PROJECT_ID}/locations/${process.env.GCP_LOCATION}`,
batchPredictionJob: {
displayName: 'text-processing-batch-001',
model: 'publishers/google/models/gemini-2.5-pro',
inputConfig: {
instancesFormat: 'jsonl',
gcsSource: {
uris: [`gs://${process.env.GCS_BUCKET_NAME}/batch-jobs/job_001/input.jsonl`],
},
},
outputConfig: {
predictionsFormat: 'jsonl',
gcsDestination: {
outputUriPrefix: `gs://${process.env.GCS_BUCKET_NAME}/batch-jobs/job_001/output/`,
},
},
},
});
console.log('Job 已建立:', job.name);
支援的模型
| Model ID | 說明 |
|---|---|
gemini-2.5-pro | 穩定版,支援 asia-east1、us-central1、global |
gemini-3.1-pro-preview | 最新 pro,需 global |
gemini-3.1-flash-image-preview | 較 gemini-3-pro-image-preview 便宜,需 global |
模型清單會隨 Google 更新
以Vertex AI Model Garden 為準。
9. 查看 Job 狀態與下載結果
Console 查看
前往 Vertex AI 批次推論頁面:
https://console.cloud.google.com/vertex-ai/batch-predictions?project=YOUR_PROJECT_ID
curl 查詢
JOB_NAME="projects/my-gcp-project-123/locations/global/batchPredictionJobs/123456789"
curl -X GET \
"https://global-aiplatform.googleapis.com/v1/${JOB_NAME}" \
-H "Authorization: Bearer $(gcloud auth print-access-token)"
Job 狀態對照表
| 狀態 | 意思 | 你該做什麼 |
|---|---|---|
JOB_STATE_QUEUED | 排隊中 | 等 |
JOB_STATE_RUNNING | 執行中 | 等 |
JOB_STATE_SUCCEEDED | 完成 | 下載結果 |
JOB_STATE_FAILED | 失敗 | 看 error 欄位 |
JOB_STATE_CANCELLED | 已取消 | 重新送出 |
下載結果
# 列出輸出檔案
gcloud storage ls gs://YOUR_BUCKET_NAME/batch-jobs/job_001/output/
# 下載到本地
gcloud storage cp "gs://YOUR_BUCKET_NAME/batch-jobs/job_001/output/*.jsonl" ./results/
解析結果 JSONL
結果每行也是一個 JSON,包含 response 欄位:
{
"response": {
"candidates": [{
"content": {
"role": "model",
"parts": [{ "text": "這篇文章的主要內容是..." }]
},
"finishReason": "STOP"
}],
"usageMetadata": {
"promptTokenCount": 256,
"candidatesTokenCount": 150,
"totalTokenCount": 406
}
}
}
用 Node.js 解析:
import * as fs from 'fs';
const content = fs.readFileSync('./results/output.jsonl', 'utf-8');
const lines = content.split('\n').filter(Boolean);
for (const line of lines) {
const result = JSON.parse(line);
const text = result.response?.candidates?.[0]?.content?.parts?.[0]?.text;
if (text) {
const idMatch = text.match(/\[REQUEST_ID:(\w+)\]/);
console.log(`${idMatch?.[1] ?? 'unknown'}: ${text.substring(0, 100)}...`);
}
}
10. 配額與結算
查看配額
前往配額頁面,篩選條件輸入 GenerateContent,可以看到所有 Gemini 相關配額。需要提高的話,點右側「⋮」→「編輯配額」。
https://console.cloud.google.com/iam-admin/quotas?project=YOUR_PROJECT_ID
結算帳戶
確認帳單帳戶已連結專案、目前費用與預算。
https://console.cloud.google.com/billing?project=YOUR_PROJECT_ID
Vertex AI Batch Job 使用付費層級
需確保結算帳戶已啟用,否則會遇到配額限制錯誤。
11. 踩坑指南
JSONL 格式錯誤(最常見)
Vertex AI 的 JSONL 格式比一般 Gemini API 嚴格得多。很多在 online prediction 能跑的寫法,在這裡都會報錯。
| 錯誤訊息 | 原因 | 怎麼修 |
|---|---|---|
Please use a valid role: user, model | contents 沒寫 role | 加上 "role": "user" |
Cannot store struct with no fields | googleSearch: {} 是空物件 | 改成 { "exclude_domains": [] } |
google_search_retrieval is not supported | 用了舊的 tool 名稱 | 改用 googleSearch |
no such field: 'dynamicRetrievalConfig' | 不是合法欄位 | 移除,用 exclude_domains 代替 |
no such field: 'outputMimeType' | Batch 不支援 | 從 generationConfig 移除 |
先跑一筆
用只有 1 行的 JSONL 測試格式,確認通過再放大量資料。
模型找不到
The PublisherModel X does not exist
90% 的 NOT_FOUND 錯誤都是 GCP_LOCATION 沒設成 global。模型名稱對但 location 錯,就是找不到。
權限不足
| 錯誤訊息 | 怎麼修 |
|---|---|
Permission denied: storage.objects.create | 加 roles/storage.objectUser(Bucket 層級) |
Permission denied: batchPredictionJobs.create | 加 roles/aiplatform.user(專案層級) |
確認目前權限:
gcloud projects get-iam-policy YOUR_PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.members:YOUR_SA_EMAIL" \
--format="table(bindings.role)"
從 Gemini Batch API 遷移過來?
兩套系統長得很像但有差異:
| 項目 | Gemini Batch API | Vertex AI Batch Prediction |
|---|---|---|
| 識別碼 | 頂層 "key": "req_001" | 無頂層 key,嵌入 prompt |
role 欄位 | 可省略 | 必須 "role": "user" |
| 檔案上傳/下載 | Gemini File API | GCS |
| 認證方式 | API Key | Service Account JSON 金鑰 |
除錯心法
- 先跑單筆 — 1 行 JSONL 測格式,對了再放量
- 看 Job error — Console 點 Job 名稱,或 GET API 的
error欄位 - 確認
global— NOT_FOUND 先改 location - 檢查 GCS 輸出路徑 — Job 完成但看不到結果?去 Bucket 裡看 output 資料夾
12. 重要頁面 URL 整理
把 YOUR_PROJECT_ID 替換成你的專案 ID:
| 頁面 | URL |
|---|---|
| GCP 控制台首頁 | https://console.cloud.google.com/home/dashboard?project=YOUR_PROJECT_ID |
| Vertex AI Studio | https://console.cloud.google.com/vertex-ai/studio/multimodal?project=YOUR_PROJECT_ID |
| 批次推論 | https://console.cloud.google.com/vertex-ai/batch-predictions?project=YOUR_PROJECT_ID |
| Cloud Storage | https://console.cloud.google.com/storage/browser?project=YOUR_PROJECT_ID |
| IAM 權限管理 | https://console.cloud.google.com/iam-admin/iam?project=YOUR_PROJECT_ID |
| 服務帳戶 | https://console.cloud.google.com/iam-admin/serviceaccounts?project=YOUR_PROJECT_ID |
| 配額 | https://console.cloud.google.com/iam-admin/quotas?project=YOUR_PROJECT_ID |
| 結算帳戶 | https://console.cloud.google.com/billing?project=YOUR_PROJECT_ID |
FAQ
Job 一直卡在排隊不動? Batch Prediction 不保證排隊時間,尖峰可能等數小時。超過 24 小時考慮 cancel 重送。
部分 request 成功、部分失敗?
結果 JSONL 中,失敗的 request 會有 error 欄位。挑出失敗的重組 JSONL 再送一次。
JSONL 很大(>100MB)? GCS SDK 上傳預設就是 resumable upload,確保網路穩定即可。Console 有檔案大小限制,大檔案用 CLI 或 SDK。
可以用 v1beta1 API 嗎?
可以。某些新功能(如 thinking config)可能只在 v1beta1 可用,把 endpoint 的 v1 改成 v1beta1 就行。