前回は、「GASとOpenAI APIを使って質問に答えてもらう」を紹介しました。
今回の開発方針と進捗の確認です。
- Drive APIのOCR機能でレシート内のテキストを抽出
- 抽出したテキストから必要な情報をGPT APIで抽出
- ↑で取得した情報をもとにPDFファイルをリネーム
OpenAIのAPIにリクエストを送れるところまで前回実装したので、あとはレシート内のテキストから「支払日・支払総額・支払先」を抽出してもらうように調整したいと思います。
プロンプトとモデルの調整
前回のデモでは、デスク周りのお片付けアドバイスを「こんまり流片付けコンサルタント」にもらいました。
function demoAskChatGPT() {
// システムの役割を指定するメッセージ
const systemRole = 'あなたは、こんまり流片づけコンサルタントです。';
// ユーザーからのプロンプト(質問)
const prompt = 'デスクの周りを掃除したいです。何からはじめたらよいでしょうか?';
// ChatGPT APIに質問し、応答を取得
const objChat = askChatGPT_(systemRole, prompt);
// 応答をログに出力
const answer = objChat.choices[0].message.content;
console.log('ChatGPTの応答:', answer);
}
今回は、
- APIで指定する役割:あなたは、領収書やレシートを仕訳入力する事業会社の会計責任者です。
- プロンプト:以下の領収書・レシートをOCRしたテキストから支払日, 支払総額, 支払先を抽出し出力してください。出力形式は”支払日_支払総額_支払先”としてください。
というリクエストを送りたいと思います。
もとになるテキストはこちら
7 セブン-イレブン
菅平高原店
長野県上田市菅平高原字菅平122
3-1641
電話: 0268-74-2322 レジ#3
事業者登録番号T8100002009906
2024年01月07日 (日) 16:53
001
領収書
ヤッホー 山ノ上ニューイ
350ml
288
バクの初夢2024
350ml
317
サッポロエビス缶
350ml
241
サントリー天然水1L
*168
小計 (税抜 8%)
¥168
消費税等(8%) 小計(税抜10%) 消費税等 (10%)
¥13
¥846
¥84
合計
¥1, 111
(税率 8% 対象
(税率10% 対象
¥181) ¥930)
(内消費税等8%
¥13)
(内消費税等10%
¥84)
クレジット支払
¥1. 111
お買上明細は上記のとおりです。
[*] マークは軽減税率対象です。
ご利用日
会員番号
クレジット売上票
(お客様控)
2024年01月07日
デモ用に用意した関数はこちら↓
function demoExtractText() {
// レシートからOCRしたテキスト
const text = `ここにテキストを入力`;
// システムの役割を指定するメッセージ
const systemRole = 'あなたは、領収書やレシートを仕訳入力する事業会社の会計責任者です。';
// ユーザーからのプロンプト(質問)
const prompt =
'以下の領収書・レシートをOCRしたテキストから支払日, 支払総額, 支払先を抽出し出力してください。出力形式は"支払日_支払総額_支払先"としてください。'
+ '\n\n###\n'
+ text;
// ChatGPT APIに質問し、応答を取得
const objChat = askChatGPT_(systemRole, prompt);
// 応答をログに出力
const answer = objChat.choices[0].message.content;
console.log(answer);
}
出力結果はこうなりました。
2024年01月07日_¥1111_セブン-イレブン
いい線いってますが、理想的な出力形式は、“yyyyMMdd_数値のみ_支払先”という形式なので、ここから怒涛のプロンプトエンジニアリング*がはじまります。
プロンプトエンジニアリングとは、AI(特に言語モデル)に対して、望ましい応答を得るために最適化された質問や指示を作成する技術です。
ChatGPT
最終的には、こうなりました。
#制約条件に従いつつ、以下の領収書・レシートをOCRしたテキストから支払日, 支払総額, 支払先を抽出し{"payment_date": 支払日,"total_amount": 支払総額,"payment_to": 支払先}のJSON形式の文字列を出力してください。
#制約条件
- 支払日はyyyyMMdd形式の文字列
- 支払総額は数値のみ
- 支払先はスペースなしの文字列
#テキスト
###
レシートからOCRしたテキスト
JSON形式*の文字列での出力を指定することで、余計なコメントや解説などをつかないように調整しています。またAPIで使用するモデルをgpt-3.5-turbo-1106またはgpt-4-1106-previewにすることで出力をJSON形式に制限するresponse_format = { type: ‘json_object’ } パラメータが指定できます。
注意点は、JSONモードを使用する場合、会話中の何らかのメッセージ、例えばシステムメッセージを介してJSONを生成するように常にモデルに指示する必要があること。JSONを生成する明示的な指示を含めない場合、トークン制限に達するまでリクエストが継続的に実行される可能性があるとのこと。
JSON(JavaScript Object Notation)は、データを軽量なテキスト形式で表現する規格。ウェブアプリケーション間のデータ交換に広く使用され、その構造は人間にも機械にも読みやすい。
ChatGPT
出力結果、完璧ですね。
askChatGPT_関数の調整
出力JSON縛りを実現するためにaskChatGPT_関数を調整しました。
/**
* ChatGPT APIに質問する関数
*
* @param {string} systemRole - システムの役割を指定するメッセージ
* @param {string} prompt - ユーザーからのプロンプト(質問)
* @param {string} [model='gpt-3.5-turbo'] - 使用するモデル(デフォルトは 'gpt-3.5-turbo')
* @return {Object} ChatGPTからの応答を含むオブジェクト
*/
function askChatGPT_(systemRole, prompt, model = 'gpt-3.5-turbo') {
// ChatGPT APIのURL
const urlChatGPT = 'https://api.openai.com/v1/chat/completions';
// APIキーの取得
const apiKey = 'APIキ';
// メッセージの配列を作成
const aryMessage = [
{ role: 'system', content: systemRole },
{ role: 'user', content: prompt }
];
// APIリクエストのペイロードを作成
const payload = {
model: model,
messages: aryMessage,
};
// JSON形式に出力を制限する場合にpayloadにプロパティを追加
if (model === 'gpt-4-1106-preview' || model === 'gpt-3.5-turbo-1106') {
if (prompt.includes('JSON')) {
payload.response_format = { type: 'json_object' }
};
};
// APIリクエストのパラメータを設定
const params = {
contentType: 'application/json',
headers: { Authorization: `Bearer ${apiKey}` },
payload: JSON.stringify(payload),
muteHttpExceptions: false // HTTP例外を無視しない
};
// APIリクエストを送信し、応答を取得
const response = UrlFetchApp.fetch(urlChatGPT, params).getContentText();
const objChat = JSON.parse(response);
return objChat;
}
ポイントとなるのはこの部分
// JSON形式に出力を制限する場合にpayloadにプロパティを追加
if (model === 'gpt-4-1106-preview' || model === 'gpt-3.5-turbo-1106') {
if (prompt.includes('JSON')) {
payload.response_format = { type: 'json_object' }
};
};
この部分は、モデルが ‘gpt-4-1106-preview’ または ‘gpt-3.5-turbo-1106’ が指定されている場合かつ、ユーザーのプロンプトに ‘JSON’ という文字列が含まれている場合に、payloadオブジェクトに response_format プロパティを追加し、そのタイプを ‘json_object’ に設定しています。
こうすることで、ChatGPT APIがJSON形式のデータ構造で応答するよう指示しています。
今回は、デモ用のdemoExtractText関数のaskChatGPT_関数の第3引数にgpt-3.5-turbo-1106モデルを指定しました。
// ChatGPT APIに質問し、応答を取得
const objChat = askChatGPT_(systemRole, prompt, 'gpt-3.5-turbo-1106');
おわりに
ChatGPT同様にプロンプトをどうするかが、望ましい出力を得るためのコツになります。また、この得られた応答をプログラミング内で再活用することを考えると、出力をJSON形式に限定するほうが便利です。
ここまでくればゴールまであと少しですね。
シリーズ目次
- Googleドライブに保存したレシートを自動リネームする その1 – ドライブに保存したPDFからOCRでテキスト取得
- Googleドライブに保存したレシートを自動リネームする その2 – GASとOpenAI APIを使って質問に答えてもらう
- Googleドライブに保存したレシートを自動リネームする その3 – プロンプトとモデルを調整してJSON形式で応答結果を得る
- Googleドライブに保存したレシートを自動リネームする その4 – フォルダ内のすべてのPDF・画像ファイルをリネームする
Google Apps Scriptを勉強したい方へ
この記事を見て、GASを勉強したいなと思われた方はぜひノンプログラマーのためのスキルアップ研究会(通称 ノンプロ研)にご参加ください。私も未経験からこの学習コミュニティに参加し、講座を受講したことでGASが書けるようになりました。
学習コミュニティ「ノンプログラマーのためのスキルアップ研究会」
挫折しがちなプログラミングの学習も、コミュニティの力で継続できます。ノンプロ研でお待ちしております!
※入会時にfreeelover.comを見て入りました!と言うと、私がちょっぴりお小遣い的なのがもらえるので、私が喜びます。
Amazon欲しい物リスト公開しています。
ブログのモチベーションアップのためにAmazon欲しい物リストを公開しております。役に立ったよ!という方で、感謝の気持ちを示したい方は、何かいただけると嬉しいです笑