【実録】Gemini API × GASで領収書OCRから勘定科目を自動分類
はじめに
この記事では、「領収書のOCRテキストから、最適な勘定科目をAI判定し、スプレッドシートに自動入力する」までの実装過程を、実際に起きたエラー対応も含めて紹介します。
使用技術:
- Google Apps Script(GAS)
- Gemini API(Generative Language API v1beta)
- モデル:Gemini 1.5 Pro
ステップ1|要求仕様まとめ
- Gemini APIキーを定数管理
- 対象スプレッドシートIDを指定
- 2行目以降を処理(1行目はヘッダー)
- L列OCRあり、M列フラグなしの行だけ処理
- 勘定科目をK列へ、完了をM列へ記載
- エラー時にはメール通知
ステップ2|試行錯誤の記録
問題点 | 原因 | 解決策 |
---|---|---|
401エラー | Authorizationヘッダー誤り | URLにAPIキー付与 |
404エラー | モデル名ミス | 正しいモデルに変更 |
400エラー | payload形式ミス | contents形式に修正 |
コーディング知識のある方には何のこと無いと思いますが、試行錯誤を繰り返しました。
ステップ3|最終版スクリプト(Gemini 1.5 Pro対応版)
<!-- GASスクリプト --> const GEMINI_API_KEY = 'XXXX'; const SPREADSHEET_ID = 'YYYY'; const SHEET_NAME = '経費明細'; const ERROR_EMAIL = 'your-email@example.com'; const MODEL_NAME = 'gemini-1.5-pro-002'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/' + MODEL_NAME + ':generateContent?key=' + encodeURIComponent(GEMINI_API_KEY); function processReceiptCategory() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; const doneFlag = values[i][12]; if (ocrText && !doneFlag) { try { const prompt = ['以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', '会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。'].join('\n'); const payload = JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }); const options = { method: 'post', contentType: 'application/json', payload, muteHttpExceptions: true }; const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const json = JSON.parse(body); const parts = json.candidates[0].content.parts; const category = parts.map(p => p.text).join('').trim(); sheet.getRange(i + 1, 11).setValue(category); sheet.getRange(i + 1, 13).setValue('完了'); } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GASエラー通知】領収書処理エラー', body: e.toString() }); } break; } } }
ステップ4|チャット履歴まとめ
今回はChatGPT 4oを使用
初回プロント
以下の手順を Google Apps Script のスクリプトにしてほしい。 1. 定数として「自分の Gemini API キー」を設定できる変数を用意する キーは”XXXX" 2. 対象のスプレッドシート ID を指定して開き、アクティブなシートを取得する IDは”YYYY” シート”経費明細” 3. シートの1行目はヘッダー行であり、データ処理の対象外とする。 4. 2行目以降の全データを対象に、 ・処理完了フラグ M2:Mが空欄 かつ ・領収書写真OCR L2:Lが空欄ではない最初の1行だけを処理する 5. 当該行の領収書写真OCR L2;Lを GeminiAPIを経由し伝送。 以下は領収書のOCRテキストです(ノイズが含まれています)。 この中から、**会計処理に必要な情報のみ抽出して**、最も適切な「勘定科目」を1つだけ日本語で出力してください。 と指示 6. AI から返ってきた勘定科目をKK2:K 勘定科目 に書き込み、M2:M 処理完了フラグに"完了"を書き込む 7. AI 呼び出しでエラーが発生した場合は、エラーメッセージをログに書き出すとともに、任意のEメールアドレス○○にエラー内容を送付する 8. 1行だけ処理したら必ずループを抜ける
生成結果
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理 */ const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス function processReceiptCategory() { // スプレッドシートとシートを取得 const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // 全データを取得(1行目ヘッダーを含む) const values = sheet.getDataRange().getValues(); // 2行目以降をループ for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) // 未処理かつOCRテキストがある行だけを対象 if (!doneFlag && ocrText) { try { // Gemini API 呼び出し用ペイロード const url = 'https://generativelanguage.googleapis.com/v1beta/models/text-bison-001:generateText'; const prompt = '以下は領収書のOCRテキストです(ノイズが含まれています)。\n' + ocrText + '\n\n' + 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。'; const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', headers: { Authorization: 'Bearer ' + GEMINI_API_KEY }, payload: JSON.stringify(payload), muteHttpExceptions: true }; const response = UrlFetchApp.fetch(url, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const json = JSON.parse(body); const category = json.candidates[0].output.trim(); // K列(勘定科目)と M列(処理完了フラグ)に書き込み sheet.getRange(i + 1, 11).setValue(category); // K 列 sheet.getRange(i + 1, 13).setValue('完了'); // M 列 } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラーをログに出力し、メールで通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 必ず1行だけ処理したらループを抜ける break; } } }
Error: Gemini API Error (401): { "error": { "code": 401, "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED" } } こんなエラー出たよ。
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(APIキー認証版) */ const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス function processReceiptCategory() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 const doneFlag = values[i][12]; // M列 if (!doneFlag && ocrText) { try { // APIキーをクエリにセット const url = 'https://generativelanguage.googleapis.com/v1beta/models/text-bison-001:generateText' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; const response = UrlFetchApp.fetch(url, options); if (response.getResponseCode() === 200) { const category = JSON.parse(response.getContentText()) .candidates[0].output.trim(); sheet.getRange(i + 1, 11).setValue(category); // K列 sheet.getRange(i + 1, 13).setValue('完了'); // M列 } else { throw new Error(`Gemini API Error (${response.getResponseCode()}): ` + response.getContentText()); } } catch (e) { Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } break; // 1行だけ処理したら抜ける } } }
Error: Gemini API Error (404): { "error": { "code": 404, "message": "Requested entity was not found.", "status": "NOT_FOUND" } } またでた
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta2) */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // モデル名 & エンドポイント(v1beta2 + text-bison@001) const MODEL_NAME = 'text-bison@001'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta2/models/' + encodeURIComponent(MODEL_NAME) + ':generateText?key=' + encodeURIComponent(GEMINI_API_KEY); /** * main: 未処理のレシートOCR行をひとつだけ処理する */ function processReceiptCategory() { // スプレッドシートとシートを取得 const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // 全データを取得(1行目ヘッダー行を含む) const values = sheet.getDataRange().getValues(); // 2行目以降をループして、最初の未処理行を処理 for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) // OCRテキストあり & 未処理 の行だけを対象 if (ocrText && !doneFlag) { try { // プロンプト作成 const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); // API 呼び出し用ペイロード const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; // Gemini API 呼び出し const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const json = JSON.parse(body); const category = json.candidates[0].output.trim(); // K列(勘定科目)・M列(処理完了フラグ)に書き込み sheet.getRange(i + 1, 11).setValue(category); // K列 (1-based) sheet.getRange(i + 1, 13).setValue('完了'); // M列 (1-based) } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラー時はログ & メール通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 1行だけ処理したら必ずループを抜ける break; } } } /** * デバッグ用: 利用可能なモデル一覧を取得してログに出力する */ function listGeminiModels() { const listUrl = 'https://generativelanguage.googleapis.com/v1beta2/models?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(listUrl); Logger.log(res.getContentText()); }
現在利用可能なモデル一覧を調べたうえで、最適なものを提案してほしい
以下に、Generative Language API(v1beta2)および Gemini API で現在利用可能な主なモデルをまとめ、その上で「領収書OCRテキストから勘定科目を分類・抽出する」用途に最適と思われるモデルを提案します。
現在利用可能なモデル一覧
-
models/text-bison@001: 一般的なテキスト生成・分類タスク向け。数ショット分類や情報抽出に高い精度を発揮し、最大入力8,192トークン、出力1,024トークンをサポートします (PaLM 2 models | Google AI for Developers - Gemini API, Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
models/chat-bison@001: 対話形式のテキスト生成向け。チャットボットやエージェントの実装に最適化されています (PaLM 2 models | Google AI for Developers - Gemini API, Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
models/embedding-gecko@001: テキスト埋め込み(ベクトル変換)用。類似度検索やクラスタリングなどに利用可能で、最大1,024トークンの入力に対応します (PaLM 2 models | Google AI for Developers - Gemini API, Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
models/code-bison@001: コード生成・補完タスク向け。プログラム生成やリファクタリング支援に利用できます (Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
models/codechat-bison@001: コードに特化した対話型モデル。開発者とのインタラクティブなコードレビューやデバッグ支援に適しています (Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
gemini-2.5-flash-preview-04-17: 最新のマルチモーダル実験モデル。音声・画像・動画を含む入力に対応し、複雑な推論に強みがあります (Gemini models | Gemini API | Google AI for Developers).
-
gemini-2.5-pro-preview-03-25 / gemini-2.5-pro-exp-03-25: 最先端の思考モデル。マルチモーダル理解や大規模データ解析に向いています (Gemini models | Gemini API | Google AI for Developers).
-
gemini-2.0-flash: コスト・パフォーマンスに優れた第2世代モデル。高速・高スループットのテキスト生成およびマルチモーダル機能を提供します (Gemini models | Gemini API | Google AI for Developers).
-
gemini-2.0-flash-lite: 低レイテンシ・低コスト運用に最適化されたモデル。大量リクエストにも対応しやすいです (Gemini models | Gemini API | Google AI for Developers).
-
gemini-1.5-pro: 複雑な推論タスク向けの第1.5世代プロフェッショナルモデル。高度な言語理解に適しています (Gemini models | Gemini API | Google AI for Developers).
モデル選定のポイント
-
タスク適合性: 単一行のテキストから「勘定科目」を抽出し分類する用途では、汎用テキスト生成モデルが最適です。
-
コスト効率: 頻繁にセルごとに呼び出すため、高レイテンシ・高コストなプロモデルよりライトモデルを優先します。
-
スループットとレイテンシ: シート処理はバッチ的に1行ずつ行うため、100ms程度のレスポンスタイムを維持できるモデルが望ましいです。
-
精度: 分類精度を担保するため、プロファイルされた調整済みモデルを利用するか、zero/few-shot プロンプトで安定した出力を得られるモデルを選びます。
推奨モデル
models/text-bison@001 を第一候補とします。
-
用途適合: 会計情報の抽出・分類など、自由形式のテキストから特定ラベルを生成するタスクに強みがあります (PaLM 2 models | Google AI for Developers - Gemini API, Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
コスト・性能バランス: プロモデルに比べてリクエストレート限界(90 rpm)は十分であり、応答も比較的迅速です (PaLM 2 models | Google AI for Developers - Gemini API, Pathways Language Model (PaLM) | Generative AI on Vertex AI).
-
導入の容易さ: 既存のスクリプトで
text-bison@001
のままエンドポイントを指定するだけで利用可能です。
もし将来的にマルチモーダル対応(例えば領収書画像の直接解析など)を検討する場合は、gemini-2.0-flash または gemini-2.0-flash-lite の利用を検討するとよいでしょう (Gemini models | Gemini API | Google AI for Developers).
と返答あり。自らもGEMINI API 公式documentをななめ読みしたうえで、Gemini 1.5 Proが良いかと思われたので、
1.5proは?
と質問。 返答は以下
Gemini 1.5 Pro の概要と利用可否についてまとめます。
1. モデルの特徴
-
リリース時期:2024年2月に “Gemini 1.5” として公開され、その後「Pro」モデル(Gemini 1.5 Pro-002)が2024年9月24日にリリースされました (Gemini (language model), Gemini (language model)).
-
アーキテクチャ:新アーキテクチャ+Mixture-of-Experts により、前世代(Gemini 1.0 Pro)よりも幅広い推論や大規模コンテキスト処理が可能。最大 1,000,000トークン のコンテキストウィンドウをサポートしています (Gemini (language model)).
-
Vertex AI 呼び出し:
-
gemini-1.5-pro:generateContent
(同期生成) -
gemini-1.5-pro:streamGenerateContent
(ストリーミング生成)
といったメソッドをサポートし、大規模データ解析や長い対話にも対応できます (Getting started with Vector Embeddings in Cloud SQL for PostgreSQL).
-
2. 利用上の制約
-
新規プロジェクトでの利用不可:2025年4月29日以降、Gemini 1.5 Pro(および1.5 Flash)は“以前に利用実績のあるプロジェクト”でなければ呼び出せなくなっています (Vertex AI Pricing | Generative AI on Vertex AI - Google Cloud, Gemini API FAQ | Generative AI | Google Cloud).
-
コスト・レイテンシ:Proモデルは text-bison@001 等の汎用モデルに比べて 高コスト・高レイテンシ な傾向があります(詳細は Vertex AI Generative AI の料金ページ を参照) (Vertex AI Pricing | Generative AI on Vertex AI - Google Cloud).
3. 用途別の選択ポイント
-
領収書OCRテキストから「勘定科目」を一行ずつ抽出するような 軽量・単純分類タスク には、むしろ text-bison@001 がコスト・性能のバランスに優れ、安定した分類結果を得やすいです。
-
ただし、
-
大規模コンテキスト(長文や複数ドキュメントの同時解析)、
-
複雑な推論(マルチステップのビジネスロジックを含む要約や分類)、
-
あるいは既に Gemini 1.5 Pro を利用中で高い精度を追求したい場合
は Gemini 1.5 Pro が威力を発揮します。
-
結論
-
新規プロジェクトかつ経費分類のみ:
text-bison@001
をおすすめ。 -
既存プロジェクトで1.5 Proを有効化済、かつ複雑タスクにも拡張予定:
gemini-1.5-pro
を利用検討。 -
これから新規に高性能モデルを使いたい場合:
gemini-2.0-flash
やgemini-2.5-pro
(プレビュー)も合わせてご検討ください。
1.5proをお願いしたのに、text-bison@001 をおすすめしてくるので、やむなく、
text-bison@001 で実装をお願い。 全scriptを提示して?
と依頼。返答結果は下記。
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta2 + text-bison@001) */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // ——— モデル名 & エンドポイント ——— const MODEL_NAME = 'text-bison@001'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta2/models/' + encodeURIComponent(MODEL_NAME) + ':generateText?key=' + encodeURIComponent(GEMINI_API_KEY); /** * メイン関数: * 未処理の行を1行だけ処理し、 * 勘定科目をK列に、完了フラグをM列に書き込む */ function processReceiptCategory() { // スプレッドシート・シート取得 const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // 全データ取得(1行目はヘッダー) const values = sheet.getDataRange().getValues(); // 2行目以降をループ for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) // OCRテキストありかつ未処理の行を対象 if (ocrText && !doneFlag) { try { // プロンプト作成 const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); // API 呼び出し用ペイロード const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; // Gemini API 呼び出し const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const category = JSON.parse(body) .candidates[0].output .trim(); // K列(勘定科目)に出力、M列(処理完了フラグ)に「完了」を書き込み sheet.getRange(i + 1, 11).setValue(category); // K列 (1-based) sheet.getRange(i + 1, 13).setValue('完了'); // M列 (1-based) } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラー時はログ & メール通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 1行だけ処理したら必ずループを抜ける break; } } } /** * デバッグ用: 利用可能なモデル一覧を取得してログ出力 */ function listGeminiModels() { const listUrl = 'https://generativelanguage.googleapis.com/v1beta2/models?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(listUrl); Logger.log(res.getContentText()); }
でもうまくいかない。。。 下記エラーを修正依頼
Error: Gemini API Error (400): { "error": { "code": 400, "message": "* GenerateTextRequest.model: unexpected model name format\n", "status": "INVALID_ARGUMENT" } }
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta2 + text-bison-001) */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // ——— モデル名 & エンドポイント ——— // モデル名を "@001" ではなく "-001" に修正 const MODEL_NAME = 'text-bison-001'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta2/models/' + MODEL_NAME + ':generateText?key=' + encodeURIComponent(GEMINI_API_KEY); /** * メイン関数: * 未処理の行を1行だけ処理し、 * 勘定科目をK列に、完了フラグをM列に書き込む */ function processReceiptCategory() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 const doneFlag = values[i][12]; // M列 if (ocrText && !doneFlag) { try { const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const category = JSON.parse(body) .candidates[0].output .trim(); sheet.getRange(i + 1, 11).setValue(category); // K列 sheet.getRange(i + 1, 13).setValue('完了'); // M列 } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } break; } } } /** * デバッグ用: 利用可能なモデル一覧を取得してログ出力 */ function listGeminiModels() { const listUrl = 'https://generativelanguage.googleapis.com/v1beta2/models?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(listUrl); Logger.log(res.getContentText()); }
Error: Gemini API Error (404): { "error": { "code": 404, "message": "Requested entity was not found.", "status": "NOT_FOUND" } } モデルがおかしいんじゃない?
まだ違うモデルでスクリプトを書いてくる・・・
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理 * * ── フロー ── * 1) listModels() で利用可能モデルを取得 * 2) "text-bison" を含むモデル名を自動選択 * 3) 選択したモデルで generateText を呼び出し * 4) K列に勘定科目、M列に"完了"をセット */ const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス /** * 利用可能モデル一覧を取得し、 * name に "text-bison" を含む最初のモデルID を返す */ function getTextBisonModel() { const url = 'https://generativelanguage.googleapis.com/v1beta2/models' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); if (res.getResponseCode() !== 200) { throw new Error('モデル一覧取得エラー(' + res.getResponseCode() + '): ' + res.getContentText()); } const data = JSON.parse(res.getContentText()); if (!data.models || !Array.isArray(data.models)) { throw new Error('models フィールドが見つかりませんでした。'); } // "models/text-bison-001" などの形で返ってくる for (let m of data.models) { if (m.name.indexOf('text-bison') !== -1) { // "models/text-bison-001" → ["models","text-bison-001"][1] return m.name.split('/')[1]; } } throw new Error('利用可能な "text-bison" モデルが見つかりませんでした。'); } /** * メイン関数: * 未処理の行を1行だけ処理し、 * 勘定科目をK列に、完了フラグをM列に書き込む */ function processReceiptCategory() { // スプレッドシート & シート取得 const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); // モデル名を自動取得 const modelId = getTextBisonModel(); const endpoint = 'https://generativelanguage.googleapis.com/v1beta2/models/' + modelId + ':generateText?key=' + encodeURIComponent(GEMINI_API_KEY); // 全データ取得(1行目ヘッダー) const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 const doneFlag = values[i][12]; // M列 if (ocrText && !doneFlag) { try { // プロンプト作成 const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; // API 呼び出し const res = UrlFetchApp.fetch(endpoint, options); const code = res.getResponseCode(); const txt = res.getContentText(); if (code === 200) { const category = JSON.parse(txt).candidates[0].output.trim(); sheet.getRange(i + 1, 11).setValue(category); // K列 sheet.getRange(i + 1, 13).setValue('完了'); // M列 } else { throw new Error(`Gemini API Error (${code}): ${txt}`); } } catch (e) { Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } break; // 1行だけ処理したら抜ける } } } /** * デバッグ用: 実際にモデル一覧を Logger に出力する */ function listModels() { const url = 'https://generativelanguage.googleapis.com/v1beta2/models' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(url); Logger.log(res.getContentText()); }
Gemini 1.5 Pro で全script修正をお願い
と指示。
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta + Gemini 1.5 Pro) */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // ——— モデル名 & エンドポイント ——— const MODEL_NAME = 'gemini-1.5-pro-latest'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/' + MODEL_NAME + ':generateContent?key=' + encodeURIComponent(GEMINI_API_KEY); /** * メイン関数: * 未処理の行を1行だけ処理し、 * 勘定科目をK列に、完了フラグをM列に書き込む */ function processReceiptCategory() { // スプレッドシート・シート取得 const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // 全データ取得(1行目はヘッダー行) const values = sheet.getDataRange().getValues(); // 2行目以降をループし、最初の未処理行を処理 for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) if (ocrText && !doneFlag) { try { // プロンプト組み立て const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); // API 呼び出し用ペイロード const payload = { prompt: { text: prompt }, temperature: 0, candidateCount: 1 }; const options = { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }; // Gemini 1.5 Pro API 呼び出し const res = UrlFetchApp.fetch(ENDPOINT, options); const code = res.getResponseCode(); const body = res.getContentText(); if (code === 200) { const category = JSON.parse(body) .candidates[0].output .trim(); // K列(勘定科目)・M列(処理完了フラグ)に書き込み sheet.getRange(i + 1, 11).setValue(category); // K列 sheet.getRange(i + 1, 13).setValue('完了'); // M列 } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラー時はログ & メール通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 1行だけ処理したらループを抜ける break; } } } /** * デバッグ用: 実際に利用可能なモデル一覧をログ出力 */ function listModels() { const url = 'https://generativelanguage.googleapis.com/v1beta/models' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); Logger.log(res.getContentText()); }
再度実行処理を行うも、まだエラー
Error: Gemini API Error (400): { "error": { "code": 400, "message": "Invalid JSON payload received. Unknown name \"prompt\": Cannot find field.\nInvalid JSON payload received. Unknown name \"temperature\": Cannot find field.\nInvalid JSON payload received. Unknown name \"candidateCount\": Cannot find field.", "status": "INVALID_ARGUMENT", "details": [ { "@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [ { "description": "Invalid JSON payload received. Unknown name \"prompt\": Cannot find field." }, { "description": "Invalid JSON payload received. Unknown name \"temperature\": Cannot find field." }, { "description": "Invalid JSON payload received. Unknown name \"candidateCount\": Cannot find field." } ] } ] } }
エラー修正後下記スクリプトを実行
/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta + Gemini 1.5 Pro) * * モデル呼び出し時の JSON ペイロードには `contents` フィールドを使います :contentReference[oaicite:0]{index=0}。 * レスポンスからは最初の候補の content.parts[0].text を抽出します :contentReference[oaicite:1]{index=1}。 */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // ——— モデル名 & エンドポイント ——— const MODEL_NAME = 'gemini-1.5-pro-002'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/' + MODEL_NAME + ':generateContent?key=' + encodeURIComponent(GEMINI_API_KEY); /** * メイン関数: * 2行目以降の未処理行を1行だけ処理し、 * K列に勘定科目、M列に「完了」を書き込む */ function processReceiptCategory() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // データ取得(1行目はヘッダー) const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) // OCRテキストあり & 未処理 の行を処理 if (ocrText && !doneFlag) { try { // プロンプト作成 const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); // リクエストペイロード const payload = JSON.stringify({ contents: [ { parts: [ { text: prompt } ] } ] // generation_config などを追加したい場合はここに }); const options = { method: 'post', contentType: 'application/json', payload, muteHttpExceptions: true }; // API 呼び出し const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const json = JSON.parse(body); // 最初の候補のテキストを抽出 const parts = json.candidates[0].content.parts; const category = parts.map(p => p.text).join('').trim(); // K列(勘定科目)と M列(処理完了フラグ)に書き込み sheet.getRange(i + 1, 11).setValue(category); // K列 (1-based) sheet.getRange(i + 1, 13).setValue('完了'); // M列 (1-based) } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラー時はログ & メール通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 1行だけ処理したら必ずループを抜ける break; } } } /** * デバッグ用: 利用可能なモデル一覧を Logger に出力 */ function listModels() { const url = 'https://generativelanguage.googleapis.com/v1beta/models' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); Logger.log(res.getContentText()); }
最後に
今回のポイント:
- エラー文を冷静に読む
- API仕様変更に注意
- レスポンス構造を把握
Generative Language APIは進化が非常に早いため、公式ドキュメントを常にチェックすることをおすすめします!
0 件のコメント:
コメントを投稿