Vertex AIのOAuthで半日溶かした
GASからGemini APIを叩きたい。やりたいことはシンプルだった。
スプレッドシートに溜まったFAQの質問文を投げて、回答案を自動生成する。それだけ。なのにVertex AI経由で正攻法を選んだ瞬間、半日が消えた。
Service Accountを作る。JSONキーをダウンロードする。GASにOAuth2ライブラリを追加する。JWTを自前で組み立てる。アクセストークンを取得する。スコープを設定する。
ここまでやって、ようやく「あれ、トークン取れたのにAPIが403返してくる」という地獄にたどり着く。権限の問題だった。IAMロールを確認して、Vertex AI Userを付与して、反映を待って——。
正直、やりたいことに対して道のりが長すぎた。
やりたかったこと
事務局の問い合わせ対応を半自動化したかった。FAQが400件以上ある。定型的な質問(「住所変更したいんですが」「年会費はいくらですか」)は、AIに回答案を出させて人間が確認するだけでいい。
ポイントは「定型FAQ」に絞っていること。専門的な判断が必要な問い合わせは別ルートで処理する前提だった。だから、モデルに求める精度はそこまで高くなくていい。安くて速ければ勝ち。
OAuthの壁、3つ
Vertex AI経由でGeminiを叩こうとすると、こんな壁がある。
1. Service Accountの鍵管理
GASにはファイルシステムがない。JSONキーをどこに置くか。スクリプトプロパティに丸ごと突っ込む? 長すぎて切れる。分割して格納? 正気の沙汰じゃない。
2. JWTの自前生成
OAuth2ライブラリを使えば楽——と思いきや、GASのOAuth2ライブラリはService Account認証のJWT生成にクセがある。Utilities.computeRsaSha256SignatureでJWTに署名する必要があり、PEM形式の秘密鍵をパースする処理を自分で書くことになる。
3. トークンの有効期限管理
アクセストークンは1時間で切れる。リフレッシュの仕組みを入れないと、翌日のトリガー実行で確実にコケる。
どれも解決できなくはない。でも「FAQ回答案を自動生成する」というゴールに対して、認証基盤の構築に工数の8割を使っている。これは設計がおかしい。
APIキーに切り替えたら5分で動いた
Google AI StudioでAPIキーを発行する。GASのスクリプトプロパティに格納する。UrlFetchApp.fetchで叩く。
以上。動いた。
function callGemini(prompt) {
const API_KEY = PropertiesService.getScriptProperties().getProperty("GEMINI_API_KEY");
// モデル名は2026年3月時点。最新はGoogle AI Studioで確認
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-04-17:generateContent?key=${API_KEY}`;
const payload = {
contents: [{
parts: [{ text: prompt }]
}]
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const json = JSON.parse(response.getContentText());
return json.candidates[0].content.parts[0].text;
}
これをスプレッドシートのカスタム関数として使うなり、トリガーでバッチ処理するなり、好きにすればいい。
エラーハンドリングを足すならこんな感じ。
function callGeminiSafe(prompt) {
try {
const response = callGemini(prompt);
if (\!response) return "回答生成失敗: 空レスポンス";
return response;
} catch (e) {
return `エラー: ${e.message}`;
}
}
APIキーをスクリプトプロパティに入れておけば、コードに直書きしなくて済む。GASの「プロジェクトの設定」→「スクリプトプロパティ」からGEMINI_API_KEYを登録するだけ。
5件テストの結果
FAQから5件抜き出して投げてみた。
| # | 質問カテゴリ | 回答品質 | 判定 |
|---|---|---|---|
| 1 | 住所変更手続き | そのまま使える | ○ |
| 2 | 年会費の金額 | 正確 | ○ |
| 3 | 退会方法 | 手順が正しい | ○ |
| 4 | 資格更新の条件 | おおむね正確(細部要確認) | △ |
| 5 | 制度の解釈に関する質問 | 的外れ | × |
5件中、定型FAQ(1〜3)は全問正解。条件分岐がある4はやや怪しい。専門的判断が必要な5は完全に外した。
想定通りの結果だった。定型FAQならFlashで十分。
「Flashじゃ無理でしょうね」
テスト結果を上司に見せたとき、5番目の回答を見て一言。
「Flashじゃ無理でしょうね」
これで線引きが決まった。どこまでFlashに任せて、どこから上位モデルに回すか。PoCの価値は「全部うまくいきました」じゃない。「ここまではいける、ここからは無理」という境界線が引けることだ。
棲み分けの結論
結局こうなった。
定型FAQ(住所変更、会費、手続き系)
→ GAS × Gemini 2.5 Flash
→ 月額$0.2以下。自動生成→人間チェック
専門FAQ(制度解釈、例外処理、判断が必要)
→ Claude Projects × Opus 4.6
→ 精度重視。RAGでドキュメント参照
将来のチャットボット化
→ Dify等のプラットフォーム
→ 上記2つの知見を統合
全部を一つのモデルでやろうとすると、オーバースペックかアンダースペックか、どちらかになる。用途で切り分けるのが現実解。
コストの話
Gemini 2.5 Flashの料金(2026年3月時点)。
- 入力: $0.15 / 1Mトークン
- 出力: $0.60 / 1Mトークン
FAQの質問文は平均100トークン、回答は平均300トークン程度。月に500件処理しても入力5万トークン+出力15万トークン。合計で$0.1もいかない。
Vertex AI経由でもAPIキー経由でもモデルの料金は同じ。認証方式が違うだけで、叩いているモデルは同一。だったら楽な方を選ぶ。
Vertex AIが必要になるとき
APIキー認証でダメなケースもある。
- VPC Service ControlsでAPIアクセスを制限したい企業環境
- リクエストの監査ログをCloud Loggingに統合したい場合
- モデルのファインチューニングやContext Cachingを使いたい場合
- データの所在地指定(リージョン制御)が必要な場合
個人や小規模チームのPoC段階でこれらが必要になることは、まずない。必要になったら移行すればいい。認証方式を変えるだけで、アプリケーションコードはほぼそのまま使える。
PoCの価値は「成功」じゃない
半日かけてVertex AI OAuthと格闘したのは無駄だったか。そうは思わない。
OAuthの壁にぶつかったから「本当にこの認証方式が必要か?」という問いが生まれた。APIキーに切り替えたから5分で動くことがわかった。テストで5番目が外れたから、モデルの境界線が引けた。上司の一言で棲み分けが決まった。
回り道した分だけ、判断の精度が上がっている。
GASからGemini APIを叩くだけなら、この記事のコードをコピペすれば30秒で終わる。でも「なぜその方式を選んだか」を自分の言葉で説明できるかどうかで、次に別のAPI連携をやるときの判断速度が変わる。
PoCは「動いた」で終わらせない。「どこまで使えて、どこから使えないか」——その線を引くためにやる。