MAS-216: 本番 AI API を Vertex AI に移行 (Claude 実装 + Gemini 移行)
概要
| 項目 | 値 |
|---|---|
| 案件ID | MAS-216 |
| カテゴリ | プラットフォーム・セキュリティ |
| 優先度 | P2 ★★ |
| ステータス | 仕様書完了 |
| 関連 ADR | ADR-0008 本番 AI API を Vertex AI に集約 の実装 / ADR-0007 Gemini API を領収書解析に使用 の callClaudeApi_() 残タスク |
| 前提案件 | MAS-215 GCP プロジェクト整備 (完了 ✅ 2026-04-21) |
| 後続案件 | MAS-147 v2 (DocAI + Gemini ハイブリッド) の Gemini 呼び出しは本案件完了後に Vertex 経由へ切替 |
| 対象ファイル (改修・主) | 000_infra/004_utils.js (callClaudeApiOnVertex_() 新規) / 500_import/502_receipt_reader.js (callGeminiForReceiptOnVertex_() 新規 + 既存からの委譲) |
| 対象ファイル (改修・付随) | appsscript.json (OAuth cloud-platform スコープ追加、既存全スコープ完全列挙必須) |
| 仕様書作成日 | 2026-04-21 |
目的
ADR-0008 で決定した「本番 AI API (Claude / Gemini) を Vertex AI (GCP) に集約する」方針を実装する。subprocessor を Google 1 社に集約し、DPA・監査ログ・課金・認証を GCP で一元管理できる (商用 SaaS 化 ADR-0009 Phase 3 の前提)。
実装アプローチは 2 段階方式 (新旧関数並存 → 切替):
callClaudeApiOnVertex_()を000_infra/004_utils.jsに新規実装 (ADR-0007 残タスク、新規関数)callGeminiForReceiptOnVertex_()を500_import/502_receipt_reader.jsに新規実装、既存callGeminiForReceipt_()から委譲する形で段階切替- 認証を API キーベースから
ScriptApp.getOAuthToken()(OAuth 2.0) ベースに統一 - リージョン戦略:
asia-northeast1優先、タイムアウト / 5xx 時はus-east5にフォールバック - 既存スクリプトプロパティ
CLAUDE_API_KEY/GEMINI_API_KEYは切替完了後に削除
現在のコード
500_import/502_receipt_reader.js の Gemini 呼び出し (現行)
行 184〜 (関数定義):
function callGeminiForReceipt_(apiKey, base64Pdf, fileName) {
var url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=' + apiKey;
// (以下 UrlFetchApp で JSON POST、レスポンスから extractedList を抽出)
var FUNC = 'callGeminiForReceipt_';
// ...
}
呼び出し元 (行 99):
var extractedList = callGeminiForReceipt_(apiKey, base64, fileName);
- エンドポイント:
generativelanguage.googleapis.com(Google AI Studio 直叩き・個人向け API) - 認証: API キー (
?key={apiKey}クエリパラメータ) - モデル:
gemini-2.5-flash
000_infra/004_utils.js の Claude 呼び出し
未実装。ADR-0007 の末尾で callClaudeApi_() を追加予定と記載されているが、関数は存在しない。本案件で callClaudeApiOnVertex_() として Vertex 経由で新規実装する。
appsscript.json の現状 (OAuth スコープ自動検出モード)
{
"timeZone": "Asia/Tokyo",
"dependencies": {
"enabledAdvancedServices": [
{ "userSymbol": "Sheets", "version": "v4", "serviceId": "sheets" },
{ "userSymbol": "AdminReports", "version": "reports_v1", "serviceId": "admin" }
]
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": { "access": "MYSELF", "executeAs": "USER_DEPLOYING" }
}
oauthScopesフィールドが存在しない = 現在は GAS の自動検出モードで動作中- 後述の通り、
oauthScopesを手動追記すると自動検出が無効化されるため、追加時は 全スコープを完全列挙する必要がある (failure_patterns #26)
MAS-215 で実装済の下地
Env.gcpProjectId()— 環境別の GCP プロジェクト ID (GCP_PROJECT_ID_DEV/_PRODを自動切替)Env.vertexRegion()— Vertex AI リージョン (未設定時はasia-northeast1)- スクリプトプロパティ
GCP_PROJECT_ID_DEV/GCP_PROJECT_ID_PROD登録済、prod 環境でtestEnvGcp()により動作確認済
修正方針
Step 1: appsscript.json の OAuth スコープ更新 (failure_patterns #26 対策必須)
Vertex AI 呼び出しには https://www.googleapis.com/auth/cloud-platform スコープが必須。oauthScopes を手動宣言することで取得する。
⚠️ 絶対禁止事項: 新スコープのみ部分宣言すると GAS の自動検出が OFF になり、既存の SpreadsheetApp / DriveApp / MailApp / Utils.auditLog 等が一斉に「権限不足エラー」で動作不能になる (failure_patterns #26)。
正しい手順:
- GAS エディタ → プロジェクトの設定 → OAuth スコープ に自動検出された全スコープを確認・コピー
appsscript.jsonのoauthScopes配列に既存全スコープを列挙- 末尾に
"https://www.googleapis.com/auth/cloud-platform"を追加 clasp push:dev→ GAS エディタで OAuth 再認可- dev 環境で主要機能 (サイドバー・バックアップ・RPA・Gemini OCR) の一通りの動作確認を人間が実施
Step 2: GCP プロジェクト ID 取得関数の確認 (既に MAS-215 で実装済)
MAS-215 実装により Env.gcpProjectId() / Env.vertexRegion() は既に動作中のため、本案件では新規追加不要。
Step 3: 000_infra/004_utils.js に callClaudeApiOnVertex_() を新規実装
/**
* Vertex AI 経由で Claude API を呼び出す共通ヘルパー。
* ADR-0007 / ADR-0008 の方針に基づく実装。
*
* @param {string} prompt - ユーザープロンプト
* @param {Object} [options] - オプション
* @param {string} [options.model] - モデル ID (デフォルト 'claude-sonnet-4-6')
* @param {number} [options.maxTokens] - 最大出力トークン数 (デフォルト 4096)
* @param {Array<Object>} [options.messages] - プロンプト以外の追加メッセージ
* @returns {Object|null} { text, raw } または失敗時 null
*/
function callClaudeApiOnVertex_(prompt, options) {
var FUNC = 'callClaudeApiOnVertex_';
options = options || {};
var model = options.model || 'claude-sonnet-4-6';
var projectId = Env.gcpProjectId();
if (!projectId) {
Utils.logError(FUNC, new Error('GCP_PROJECT_ID 未設定'), 'Env.gcpProjectId() が空');
return null;
}
if (!prompt || typeof prompt !== 'string') {
Utils.logError(FUNC, new Error('prompt 空または非文字列'), String(prompt).slice(0, 100));
return null;
}
var regions = [Env.vertexRegion(), 'us-east5'];
var payload = {
anthropic_version: 'vertex-2023-10-16',
max_tokens: options.maxTokens || 4096,
messages: options.messages || [{ role: 'user', content: prompt }]
};
for (var i = 0; i < regions.length; i++) {
var region = regions[i];
var url = 'https://' + region + '-aiplatform.googleapis.com/v1/projects/'
+ projectId + '/locations/' + region
+ '/publishers/anthropic/models/' + model + ':rawPredict';
try {
var response = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
var code = response.getResponseCode();
var raw = response.getContentText();
if (code === 200) {
var parsed = JSON.parse(raw);
var text = parsed.content && parsed.content[0] && parsed.content[0].text || '';
return { text: text, raw: parsed };
}
// 5xx → フォールバック、4xx → 即時返却
if (code >= 500 && i === 0) {
Utils.persistLog('WARN', FUNC, 'region fallback: ' + region + ' → us-east5', 'HTTP ' + code);
continue;
}
Utils.logError(FUNC, new Error('HTTP ' + code), raw.slice(0, 500));
return null;
} catch (e) {
if (i === 0) {
Utils.persistLog('WARN', FUNC, 'region fallback (exception): ' + region + ' → us-east5', String(e));
continue;
}
Utils.logError(FUNC, e, 'region=' + region);
return null;
}
}
return null;
}
ポイント:
ScriptApp.getOAuthToken()で OAuth 2.0 認証 (Service Account JSON は不使用)- 401/403/404/429 等の 4xx は即時 null 返却 (リトライ不要)
- 5xx / ネットワーク例外はリージョンフォールバック 1 回のみ
- JSON パース失敗は try/catch で吸収
Utils.logError/Utils.persistLogを使用 (既存の004_utils.jsパターン)
Step 4: 500_import/502_receipt_reader.js の Vertex 移行
既存 callGeminiForReceipt_() はそのまま維持し、内部で Env.gcpProjectId() の有無を見て新旧を切り替える委譲方式:
function callGeminiForReceipt_(apiKey, base64Pdf, fileName) {
// 並行稼働期間: GCP プロジェクト ID が設定済みなら Vertex 経由へ委譲
if (Env.gcpProjectId()) {
return callGeminiForReceiptOnVertex_(base64Pdf, fileName);
}
// 旧系統 (Google AI Studio 直叩き) — apiKey パラメータを使用
var url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=' + apiKey;
var FUNC = 'callGeminiForReceipt_';
// (既存ロジックはそのまま)
}
/**
* Vertex AI 経由で Gemini モデルを呼び出して領収書 PDF を抽出。
*/
function callGeminiForReceiptOnVertex_(base64Pdf, fileName) {
var FUNC = 'callGeminiForReceiptOnVertex_';
var model = 'gemini-2.5-flash'; // Vertex 側で同名利用可能、差異あれば要調整
var projectId = Env.gcpProjectId();
var regions = [Env.vertexRegion(), 'us-east5'];
var payload = {
contents: [{
role: 'user',
parts: [
{ text: '...(既存プロンプトテンプレート)...' },
{ inline_data: { mime_type: 'application/pdf', data: base64Pdf } }
]
}],
generation_config: { response_mime_type: 'application/json' }
};
for (var i = 0; i < regions.length; i++) {
var region = regions[i];
var url = 'https://' + region + '-aiplatform.googleapis.com/v1/projects/'
+ projectId + '/locations/' + region
+ '/publishers/google/models/' + model + ':generateContent';
try {
var response = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
var code = response.getResponseCode();
var raw = response.getContentText();
if (code === 200) {
var parsed = JSON.parse(raw);
// (JSON から extractedList 抽出 — 既存ロジックを踏襲)
return /* extractedList */ null;
}
if (code >= 500 && i === 0) {
Utils.persistLog('WARN', FUNC, 'region fallback: ' + region + ' → us-east5', 'HTTP ' + code);
continue;
}
Utils.logError(FUNC, new Error('HTTP ' + code), raw.slice(0, 500) + ' (file=' + fileName + ')');
return null;
} catch (e) {
if (i === 0) {
Utils.persistLog('WARN', FUNC, 'region fallback (exception): ' + region + ' → us-east5', String(e));
continue;
}
Utils.logError(FUNC, e, 'region=' + region + ', file=' + fileName);
return null;
}
}
return null;
}
ポイント:
- 呼び出し元 (
502_receipt_reader.js:99) は変更不要 (既存関数から自動委譲) Env.gcpProjectId()が空文字を返せば旧系統にフォールバック (graceful degradation)- Gemini レスポンスの JSON 抽出ロジックは既存の
callGeminiForReceipt_()と同じものを再利用
Step 5: 並行稼働 → 切替 → 旧キー削除
- 並行稼働期間 (推奨 2 週間): dev 環境で領収書 OCR 回帰テスト実施 (サンプル 5〜10 件、一致率 95% 以上を合格基準)
- prod 切替:
npm run push:prod後、prod で動作確認 - 旧認証情報の削除:
CLAUDE_API_KEY/GEMINI_API_KEYスクリプトプロパティを dev / prod 両方から削除Env.geminiApiKey()/Env.claudeApiKey()は当面残す (graceful 空文字返却、未使用化後に別案件で削除)- 既存 "default gemini project" のシャットダウン予約 (30 日猶予)
影響範囲
| 対象 | ファイル | 変更量 | 既存動作への影響 |
|---|---|---|---|
callClaudeApiOnVertex_() 新規 | 000_infra/004_utils.js | 約 60 行追加 | なし (新規関数) |
callGeminiForReceiptOnVertex_() 新規 | 500_import/502_receipt_reader.js | 約 60 行追加 | なし (新規関数) |
callGeminiForReceipt_() 委譲追加 | 500_import/502_receipt_reader.js 行 184〜 | 約 5 行追加 | Env.gcpProjectId() 未設定時は従来動作、設定済み時のみ Vertex 経由 |
appsscript.json OAuth スコープ | appsscript.json | oauthScopes 配列新設 (既存全スコープ完全列挙 + cloud-platform) | 失敗パターン #26 対応必須、再認可が必要 |
| スクリプトプロパティ整理 | dev/prod 両環境 (GAS エディタ) | CLAUDE_API_KEY / GEMINI_API_KEY 削除 | 切替完了後のみ削除 |
Phase 1 の Grep 結果:
callClaudeApi_の呼び出し元: なし (未実装関数)callGeminiForReceipt_の呼び出し元:500_import/502_receipt_reader.js:99の 1 箇所のみCLAUDE_API_KEY/GEMINI_API_KEYの参照:Env.geminiApiKey()経由で500_import/502_receipt_reader.js:99のみ
注意事項
- OAuth スコープ部分宣言禁止 (failure_patterns #26):
oauthScopes追記時は GAS エディタ自動検出分を全て完全列挙してからcloud-platformを追加。部分追加は全機能崩壊 - GAS UrlFetchApp タイムアウト上限 60 秒: 1 リージョン試行が 60 秒超過すると即タイムアウト。フォールバック実施でも合計 2 分を超える可能性あり、バッチ処理では考慮
- 並行稼働期間の新旧切替:
Env.gcpProjectId()の有無で分岐。切替完了まで旧スクリプトプロパティを保持する - MAS-215 完了が前提: 本案件は MAS-215 (GCP プロジェクト整備) 完了後に着手。
Env.gcpProjectId()/Env.vertexRegion()が動作している必要あり - OAuth 再認可:
oauthScopes変更後、GAS エディタでの再認可ダイアログが出るまでユーザーが実行する - Anthropic 直契約のクローズ: 本案件完了後、Anthropic Console の個人 API キー (
CLAUDE_API_KEY) は個人アカウント側で無効化 (任意、必須ではない)
エッジケース
| 条件 | ハンドリング | ログ記録内容 | 戻り値 |
|---|---|---|---|
ScriptApp.getOAuthToken() 失敗 / トークン空 | Utils.logError + Utils.persistLog('ERROR', ...) | 関数名・エラーメッセージ・スタック | null |
| IAM 権限不足 (HTTP 403) | 同上 | HTTP ステータス・レスポンスボディ先頭 500 文字 | null |
タイムアウト / 5xx (asia-northeast1 1 回目) | us-east5 にフォールバックしてリトライ | 1 回目リージョン名・エラー内容 (Utils.persistLog('WARN', ...)) | リトライ結果 |
タイムアウト / 5xx (us-east5 2 回目) | Utils.logError + Utils.persistLog | 両リージョン試行履歴 | null |
| クォータ超過 (HTTP 429) | リトライせず即時返却 | ステータスコード・超過メッセージ | null |
| レスポンス JSON パース失敗 | Utils.logError | 生レスポンス先頭 500 文字 | null |
| Gemini OCR 必須フィールド欠落 | Utils.logError | 欠落フィールド名・生レスポンス | null |
| PDF 破損 / サイズ上限超過 (50MB) | 呼び出し前ガードで Utils.logError | ファイルサイズ・エラー詳細 | null |
| プロンプトが空文字列 | 早期リターン | 関数名・入力値 (null/空) | null |
Env.gcpProjectId() 空文字 (MAS-215 未完了) | 旧系統 (callGeminiForReceipt_ 直叩き) へフォールバック | (ログなし、正常時動作) | 旧系統の戻り値 |
| Vertex AI API 未有効化 (HTTP 403 or 404) | Utils.logError | MAS-215 Step 3 の再確認案内 | null |
| Claude / Gemini モデルがリージョン未提供 | フォールバック後も失敗 → null | 両リージョンの HTTP 404 | null |
実データ検証
実装前・実装中に以下を確認:
- MAS-215 完了の再確認: prod 環境で
testEnvGcp()実行、bizlp-gas-accounting-prod/asia-northeast1/prodの 3 値が返る - dev / prod 両環境のスクリプトプロパティ現状:
CLAUDE_API_KEY/GEMINI_API_KEYが実際に設定されているか (切替前の退避対象) - Vertex AI API の有効化確認: dev / prod の両 GCP プロジェクトで Vertex AI API (
aiplatform.googleapis.com) が「有効な API」一覧に表示される (MAS-215 Step 3 で実施済) - Claude モデルの可用性:
asia-northeast1でclaude-sonnet-4-6or 最新モデルが選択可能か Vertex AI Model Garden で確認。未提供ならus-east5前提で運用 (モデル ID は実装時に最新化) - Gemini モデル ID の Vertex 側対応:
gemini-2.5-flashがそのまま使えるか、gemini-2.5-flash-001等のバージョン付きが必要か確認 - OAuth スコープ自動検出の現状: GAS エディタ「プロジェクトの設定 → OAuth スコープ」の一覧をスクリーンショット等で保管 (oauthScopes 手動追記時の完全列挙用)
- 既存領収書サンプル: 取引日・取引先名・合計金額の抽出結果が正しく出ている領収書 PDF 5〜10 件を回帰テスト用に確保
- dev 環境のレイテンシ測定: Vertex 経由のレスポンスタイムが AI Studio と同等レベルか (大幅増は UX 劣化の原因、タイムアウト検討)
関連ドキュメント
| ドキュメント | 関連箇所 |
|---|---|
| ADR-0007 Gemini API を領収書解析に使用する判断 | callClaudeApi_() 残タスクの出発点 |
| ADR-0008 本番 AI API を Vertex AI に集約 | 本案件の方針のソース |
| MAS-215 GCP プロジェクト整備 | 前提案件 (完了) |
| MAS-147 v2 請求書 OCR (DocAI ハイブリッド) | 本案件完了後に Gemini 呼び出しを Vertex 経由へ切替 |
| failure_patterns #26 OAuth スコープ崩壊 | Step 1 の必須対応 |
人間が検討すべき事項
- MAS-215 完了の確認: 本案件は MAS-215 完了後に着手する前提条件。完了済 (2026-04-21) のため着手可能
- Gemini OCR の回帰テスト基準: 移行前後で既存の領収書 PDF 5〜10 点を用いて取引日・取引先名・合計金額の抽出結果を比較し、主要項目の一致率 95% 以上を合格基準とする。テスト資産の保管場所・管理方法・判定担当者は要検討
oauthScopes手動追記後の dev 環境動作確認: サイドバー・バックアップ・RPA の一通りの動作確認を人間が実施。失敗パターン #26 回避のため必須- 並行稼働の終了タイミング: dev 2 週間安定稼働 → prod 切替、prod 1 週間観察 → 旧スクリプトプロパティ削除、という順序で進めるか要承認
- 旧スクリプトプロパティ削除の承認フロー:
CLAUDE_API_KEY/GEMINI_API_KEYを本当に削除してよいか、他未知の参照がないかをコードレビューで最終確認 - Claude モデル ID の選定:
claude-sonnet-4-6が推奨だが、Vertex Model Garden で提供される最新モデル ID に合わせて実装時に調整 - 予算アラート: GCP Budgets で Vertex AI 向けに月額上限 (例: $50/月) アラートを設定するか検討
- 認証方式の最終確認:
ScriptApp.getOAuthToken()で十分か、Service Account JSON へ移行すべきか (現時点は代表 1 名体制、前者で十分)
実装プロンプト (Claude Code 用)
あなたは GAS 会計システム (bizlp-gas-accounting) のシニア開発者です。
案件 MAS-216「本番 AI API を Vertex AI に移行」を実装してください。
## 実行前タスク
1. docs/dev/dev_mas-216_vertex_ai_migration.md を Read して仕様全把握
2. docs/_internal/failure_patterns.md #26 (OAuth スコープ崩壊) を必ず確認
3. 既存 `500_import/502_receipt_reader.js` の callGeminiForReceipt_ 実装 (行 184〜) と呼び出し元 (行 99) を Read
4. 現在の appsscript.json (oauthScopes 未宣言、自動検出モード) を Read
5. `000_infra/001_env.js` で MAS-215 実装済の Env.gcpProjectId() / Env.vertexRegion() を確認
6. `000_infra/004_utils.js` の Utils.logError / Utils.persistLog のシグネチャを確認
7. dev / prod GCP プロジェクトで Vertex AI API 有効化済 (MAS-215 完了) を確認
8. Claude / Gemini モデルが asia-northeast1 で利用可能か Vertex AI Model Garden で確認
## 修正対象ファイル
- 改修: 000_infra/004_utils.js に callClaudeApiOnVertex_() 新規追加 (約 60 行)
- 改修: 500_import/502_receipt_reader.js に callGeminiForReceiptOnVertex_() 新規追加 + callGeminiForReceipt_() から委譲 (約 65 行)
- 改修: appsscript.json に oauthScopes 配列を新設 (既存全スコープ完全列挙 + cloud-platform)
## 実装順序 (Step 1-6)
### Step 1: appsscript.json の oauthScopes 更新
- GAS エディタ「プロジェクトの設定 → OAuth スコープ」で自動検出された全スコープを確認・記録
- appsscript.json の oauthScopes 配列に全スコープ完全列挙
- 末尾に "https://www.googleapis.com/auth/cloud-platform" を追加
- ⚠️ 部分宣言絶対禁止 (failure_patterns #26)
### Step 2: 000_infra/001_env.js の GCP 関数確認
- MAS-215 で Env.gcpProjectId() / Env.vertexRegion() 実装済の確認のみ
- 新規追加不要
### Step 3: 000_infra/004_utils.js への callClaudeApiOnVertex_() 新規実装
- 仕様書 Step 3 のサンプルコードに沿って実装
- ScriptApp.getOAuthToken() 認証、regions 配列でフォールバック、JSON パース失敗ハンドリング
- Utils.logError / Utils.persistLog を既存シグネチャで呼び出し
### Step 4: 500_import/502_receipt_reader.js の Vertex 移行
- callGeminiForReceipt_() の冒頭に Env.gcpProjectId() 分岐を追加、Vertex 呼び出しへ委譲
- callGeminiForReceiptOnVertex_() を新規関数として追加
- 既存のレスポンス JSON 抽出ロジックを Vertex 版でも踏襲
### Step 5: dev 環境での動作確認
- npm run push:dev でデプロイ
- GAS エディタで OAuth 再認可 (スコープ変更のため必須)
- dev の領収書 OCR を手動実行、Vertex 経由で正常完了することを確認
- 既存領収書 PDF 5〜10 件で回帰テスト (取引日・取引先名・合計金額の一致率 95% 以上)
- サイドバー・バックアップ・RPA が引き続き動作することを目視確認 (failure_patterns #26 対応)
- 2 週間並行稼働 → prod 切替
### Step 6: prod 切替・旧スクリプトプロパティ削除
- npm run push:prod 後、prod で 1 週間動作観察
- 代表者の承認後、CLAUDE_API_KEY / GEMINI_API_KEY スクリプトプロパティを dev / prod 両方から削除
- "default gemini project" の GCP 側シャットダウン予約 (30 日猶予)
## 制約
- appsscript.json の OAuth スコープ変更は failure_patterns #26 の手順厳守 (既存全スコープ完全列挙)
- Claude / Gemini の API 呼び出しは必ず ScriptApp.getOAuthToken() 経由
- Env.gcpProjectId() 空文字時は旧系統へ graceful fallback
- Gemini モデル ID は実装時に Vertex Model Garden で確認 (必要ならバージョン付きに調整)
- 回帰テストが完了するまで並行稼働期間を終わらせない
## エッジケース
- GCP_PROJECT_ID 未設定 → 旧系統 (generativelanguage.googleapis.com) にフォールバック
- OAuth トークン切れ → HTTP 401/403 で null 返却、logError
- Claude モデル未提供リージョン → us-east5 フォールバック
- HTTP 429 → リトライせず即時 null
- JSON パース失敗 → logError して null
- UrlFetchApp タイムアウト (60s) → us-east5 フォールバック 1 回のみ
## 動作確認
1. npm run push:dev でデプロイ
2. GAS エディタで OAuth 再認可 (新スコープのため必須)
3. dev で Vertex AI 呼び出しテスト関数実行 → 簡易プロンプトで応答取得確認
4. dev で領収書 PDF 取込 → Vertex 経由で Gemini が動作、結果が AI Studio 版と同精度
5. 主要機能 (サイドバー / バックアップ / RPA) の動作確認 (failure_patterns #26 対応)
6. 2 週間並行稼働 → prod 切替
7. prod 切替 1 週間後、CLAUDE_API_KEY / GEMINI_API_KEY 削除
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---|---|---|
| 実行前タスク (Read) | あり | 仕様把握 + OAuth 崩壊回避策 |
| Step 1 OAuth | あり | 全スコープ列挙の完全性確保 |
| Step 2 Env 確認 | なし | 確認のみ |
| Step 3 callClaudeApiOnVertex_ | なし | 仕様書通り |
| Step 4 Gemini 移行 | あり | Vertex 側のペイロード構造差分の見極め |
| Step 5 動作確認 | 人間確認必須 | Human-in-the-Loop |
| Step 6 切替 | 人間確認必須 | 同上 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1 (OAuth スコープ) | Claude Opus 4.7 | failure_patterns #26 対応で全スコープ列挙の判断が必要 |
| Step 2 (Env 確認) | Claude Haiku 4.5 | 確認のみ、実装要素なし |
| Step 3 (callClaudeApiOnVertex_) | Claude Sonnet 4.6 | 仕様書でコード完全定義済、中程度の判断 |
| Step 4 (Gemini 移行) | Claude Opus 4.7 | Vertex 側のペイロード差分・モデル ID 調整の判断 |
| Step 5-6 (動作確認・切替) | 人間確認必須 | Human-in-the-Loop ポリシー適用 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成 (ADR-0008 実装、ADR-0007 callClaudeApi_() 残タスクの仕様確定)。MAS-306 Gemini Deep Think 参照、script 1/2 経由で Gemini + Claude パイプラインが生成したプロンプトを元に執筆 |
仕様書作成プロンプト (再現性・監査性のため必ず記録)
展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. 拡張思考の使い分け: Phase 1(設計フェーズ)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書フェーズ)の各Step内では拡張思考を最小限に抑え、Phase 1で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. テキスト報告の禁止: 「〜を作成します」等のtextのみで tool_use なしに turn を終了しない。説明は1文以内。直ちに tool を呼ぶ。
3. 4-5分割のWrite/Edit実行: 2-1(骨格 ~20行) / 2-2(概要〜注意事項 ~300行) / 2-3a(エッジケース〜人間検討事項 ~200行) / 2-3b(実装プロンプト〜変更履歴 ~250行) / 2-4(`<details>`プロンプト全文記録・最重量・必ず独立Step) に分割して実行する。
4. 各Stepで何を書くかを具体指示: 設計判断をPhase 2実行時に持ち込まないよう、Phase 1で確定した内容をそのまま清書する。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 N-40「本番 AI API を Vertex AI に移行 (Claude 実装 + Gemini 移行)」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列 §E.1(基盤・DevOps)に必ず追記してください。
(以下、task_N-40.md の Phase 1 / Phase 2 / Phase 3 の全指示を参照。
scripts/1_generate_prompts_gemini.js (gemini-2.5-pro) + scripts/2_run_writers.sh (claude sonnet 添削)
パイプラインで生成された確定版プロンプト全文は tasks/prompts/task_N-40.md.done に保管)
</instruction>
原版 (Gemini 生成): tasks/prompts/task_N-40.gemini.md
添削版 (Claude Sonnet): tasks/prompts/task_N-40.md.done