概要

項目
案件IDMAS-216
カテゴリプラットフォーム・セキュリティ
優先度P2 ★★
ステータス仕様書完了
関連 ADRADR-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 段階方式 (新旧関数並存 → 切替):

  1. callClaudeApiOnVertex_()000_infra/004_utils.js に新規実装 (ADR-0007 残タスク、新規関数)
  2. callGeminiForReceiptOnVertex_()500_import/502_receipt_reader.js に新規実装、既存 callGeminiForReceipt_() から委譲する形で段階切替
  3. 認証を API キーベースから ScriptApp.getOAuthToken() (OAuth 2.0) ベースに統一
  4. リージョン戦略: asia-northeast1 優先、タイムアウト / 5xx 時は us-east5 にフォールバック
  5. 既存スクリプトプロパティ 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)。

正しい手順:

  1. GAS エディタ → プロジェクトの設定 → OAuth スコープ に自動検出された全スコープを確認・コピー
  2. appsscript.jsonoauthScopes 配列に既存全スコープを列挙
  3. 末尾に "https://www.googleapis.com/auth/cloud-platform" を追加
  4. clasp push:dev → GAS エディタで OAuth 再認可
  5. dev 環境で主要機能 (サイドバー・バックアップ・RPA・Gemini OCR) の一通りの動作確認を人間が実施

Step 2: GCP プロジェクト ID 取得関数の確認 (既に MAS-215 で実装済)

MAS-215 実装により Env.gcpProjectId() / Env.vertexRegion() は既に動作中のため、本案件では新規追加不要

Step 3: 000_infra/004_utils.jscallClaudeApiOnVertex_() を新規実装

/**
 * 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: 並行稼働 → 切替 → 旧キー削除

  1. 並行稼働期間 (推奨 2 週間): dev 環境で領収書 OCR 回帰テスト実施 (サンプル 5〜10 件、一致率 95% 以上を合格基準)
  2. prod 切替: npm run push:prod 後、prod で動作確認
  3. 旧認証情報の削除:
    • 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.jsonoauthScopes 配列新設 (既存全スコープ完全列挙 + 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 のみ

注意事項

  1. OAuth スコープ部分宣言禁止 (failure_patterns #26): oauthScopes 追記時は GAS エディタ自動検出分を全て完全列挙してから cloud-platform を追加。部分追加は全機能崩壊
  2. GAS UrlFetchApp タイムアウト上限 60 秒: 1 リージョン試行が 60 秒超過すると即タイムアウト。フォールバック実施でも合計 2 分を超える可能性あり、バッチ処理では考慮
  3. 並行稼働期間の新旧切替: Env.gcpProjectId() の有無で分岐。切替完了まで旧スクリプトプロパティを保持する
  4. MAS-215 完了が前提: 本案件は MAS-215 (GCP プロジェクト整備) 完了後に着手。Env.gcpProjectId() / Env.vertexRegion() が動作している必要あり
  5. OAuth 再認可: oauthScopes 変更後、GAS エディタでの再認可ダイアログが出るまでユーザーが実行する
  6. 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.logErrorMAS-215 Step 3 の再確認案内null
Claude / Gemini モデルがリージョン未提供フォールバック後も失敗 → null両リージョンの HTTP 404null

実データ検証

実装前・実装中に以下を確認:

  1. MAS-215 完了の再確認: prod 環境で testEnvGcp() 実行、bizlp-gas-accounting-prod / asia-northeast1 / prod の 3 値が返る
  2. dev / prod 両環境のスクリプトプロパティ現状: CLAUDE_API_KEY / GEMINI_API_KEY が実際に設定されているか (切替前の退避対象)
  3. Vertex AI API の有効化確認: dev / prod の両 GCP プロジェクトで Vertex AI API (aiplatform.googleapis.com) が「有効な API」一覧に表示される (MAS-215 Step 3 で実施済)
  4. Claude モデルの可用性: asia-northeast1claude-sonnet-4-6 or 最新モデルが選択可能か Vertex AI Model Garden で確認。未提供なら us-east5 前提で運用 (モデル ID は実装時に最新化)
  5. Gemini モデル ID の Vertex 側対応: gemini-2.5-flash がそのまま使えるか、gemini-2.5-flash-001 等のバージョン付きが必要か確認
  6. OAuth スコープ自動検出の現状: GAS エディタ「プロジェクトの設定 → OAuth スコープ」の一覧をスクリーンショット等で保管 (oauthScopes 手動追記時の完全列挙用)
  7. 既存領収書サンプル: 取引日・取引先名・合計金額の抽出結果が正しく出ている領収書 PDF 5〜10 件を回帰テスト用に確保
  8. 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 の必須対応

人間が検討すべき事項

  1. MAS-215 完了の確認: 本案件は MAS-215 完了後に着手する前提条件。完了済 (2026-04-21) のため着手可能
  2. Gemini OCR の回帰テスト基準: 移行前後で既存の領収書 PDF 5〜10 点を用いて取引日・取引先名・合計金額の抽出結果を比較し、主要項目の一致率 95% 以上を合格基準とする。テスト資産の保管場所・管理方法・判定担当者は要検討
  3. oauthScopes 手動追記後の dev 環境動作確認: サイドバー・バックアップ・RPA の一通りの動作確認を人間が実施。失敗パターン #26 回避のため必須
  4. 並行稼働の終了タイミング: dev 2 週間安定稼働 → prod 切替、prod 1 週間観察 → 旧スクリプトプロパティ削除、という順序で進めるか要承認
  5. 旧スクリプトプロパティ削除の承認フロー: CLAUDE_API_KEY / GEMINI_API_KEY を本当に削除してよいか、他未知の参照がないかをコードレビューで最終確認
  6. Claude モデル ID の選定: claude-sonnet-4-6 が推奨だが、Vertex Model Garden で提供される最新モデル ID に合わせて実装時に調整
  7. 予算アラート: GCP Budgets で Vertex AI 向けに月額上限 (例: $50/月) アラートを設定するか検討
  8. 認証方式の最終確認: 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.7failure_patterns #26 対応で全スコープ列挙の判断が必要
Step 2 (Env 確認)Claude Haiku 4.5確認のみ、実装要素なし
Step 3 (callClaudeApiOnVertex_)Claude Sonnet 4.6仕様書でコード完全定義済、中程度の判断
Step 4 (Gemini 移行)Claude Opus 4.7Vertex 側のペイロード差分・モデル 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