【GAS、GPT-4】OpenAI APIでGmailに届くエラーメールをスプレッドシートに記録する
はじまり




今回やりたいこと
Google Apps Scirpt を使っていると、 Gmail にエラーメッセージやその周辺の情報が送られてくることがあります。しかし、メールとして断片的になっているデータって、扱いづらいですよねえ・・・
そこで本記事では、 Gmail に届くメッセージから必要な情報を抽出し、管理を効率化する方法について紹介します。
今回、こんな風に送られてくるエラー情報を・・・

スプレッドシートに書き込んでいく機能を実装していきます。

手順
それでは、実装の手順です。
1.Gmailのメッセージの取得
まずは、 Gmail にあるメッセージを取得しましょう。
最初にメールのリストを取得します。ここでは、エラーメールの件名と未読かどうかでフィルタリングしています。
/** * @description Write error log from Gamil into the sheet with GPT. * @return {boolean}*/function writeErrorLogFromGmailWithGpt(){ const sheetName = SHEET_NAME_1ST; const colOfNumber = 1; const targetSubject = `Summary of failures for Google Apps Script: `; let query = `label:inbox is:unread subject:"${targetSubject}"`; // 検索条件を設定 let threads = GmailApp.search(query); // Gmailのスレッドを検索
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); let startRow = 2; let numbers = sheet.getRange(startRow, colOfNumber, 1, sheet.getLastRow()).getValues().map(record => { return record; }); let maxNumber = Math.max(numbers); let i = 0; let valuesToWrite = []; console.log(`writeErrorLog: 222222222222222222222222222222222222222222222222222222222222222222`); threads.forEach(function(thread) { console.log(thread.getMessageCount()); // スレッド内のメッセージの個数 let messages = thread.getMessages(); // スレッド内のメッセージを取得 messages.forEach(function(message) { console.log(message.getSubject()); // 使う console.log(`writeErrorLog: 333333333333333333333333333333333333333333333333333333333333333`); console.log(message.getDate()); // 使う console.log(message.getPlainBody());
}); });
return true;}2.OpenAIのChat Completions APIを使う
次に、取得したメッセージを GPT-4 に与えましょう。
まだ使ったことがなければ、以下の OpenAI の Developer Platform で、 API キーを作りましょう。
API キーを作ったら、 GAS を書いていきます。
追加した処理の中で、 GPT-4 にプロンプトを与えていきます。(generatedMsgは後で使います。)
/** * @description Write error log from Gamil into the sheet with GPT. * @return {boolean}*/function writeErrorLogFromGmailWithGpt(){ const sheetName = SHEET_NAME_1ST; const colOfNumber = 1; const targetSubject = `Summary of failures for Google Apps Script: `; let query = `label:inbox is:unread subject:"${targetSubject}"`; // 検索条件を設定 let threads = GmailApp.search(query); // Gmailのスレッドを検索
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); let startRow = 2; let numbers = sheet.getRange(startRow, colOfNumber, 1, sheet.getLastRow()).getValues().map(record => { return record; }); let maxNumber = Math.max(numbers); let i = 0; let valuesToWrite = []; console.log(`writeErrorLog: 222222222222222222222222222222222222222222222222222222222222222222`); threads.forEach(function(thread) { console.log(thread.getMessageCount()); // スレッド内のメッセージの個数 let messages = thread.getMessages(); // スレッド内のメッセージを取得 messages.forEach(function(message) { console.log(message.getSubject()); // 使う console.log(`writeErrorLog: 333333333333333333333333333333333333333333333333333333333333333`); console.log(message.getDate()); // 使う console.log(message.getPlainBody());
// 追加: START --------------------------------------------------------------------- let plainText = message.getPlainBody(); const keywords = ["function", "error_message", "trigger"]; let extractedInfo = extractErrorInfoInPlainText(plainText, keywords) console.log(typeof extractedInfo); console.log(extractedInfo); console.log(extractedInfo["choices"]); console.log(extractedInfo["choices"][0]["message"]); let generatedMsg = extractedInfo["choices"][0]["message"]["content"]; // 追加: END ---------------------------------------------------------------------
}); });
return true;}それでは、extractErrorInfoInPlainText()の中身を見ていきましょう。
この関数で、 OpenAI の Chat Completions API にリクエストする内容を作ります。
/** * @description Extract error info in txt. * @param {string} plainText * @param {string[]} keywords * @return {string{}}*/function extractErrorInfoInPlainText(plainText, keywords){ // 抽出した情報を格納するオブジェクト const prompt = `この[# プレーンテキスト]から[# 欲しい情報]を[# 形式]の形式で取得してください\n\n[# プレーンテキスト]\n${plainText}\n\n[# 欲しい情報]\n- ${keywords.reduce((prev, curr) => `${prev}\n- ${curr}`)}\n\n[# 形式]\nHTMLのTable形式で、[# 欲しい情報]がヘッダーになる。`
const messages = [ {"role": "system", "content": "You are a helpful assistant."} , {"role": "user", "content": prompt} ] const modelName = "gpt-4"; const maxTokens = 2048; const temperature = 0.2; // 生成する文章のランダム性(0:完全に確定的、2:完全にランダム) let resObj = generateChatCompletionWithOpenaiApi(messages, modelName, maxTokens, temperature) console.log(resObj); console.log(`extractErrorInfoInPlainText: 66666666666666666666666666666666666666666666666666666666666666666666`); return resObj;}
/** * @description Generate chat completions like ChatGPT. * @param {string[]{}} messages * @param {string} modelName * @param {number} maxTokens * @param {number} temperature * @return {any{}}*/function generateChatCompletionWithOpenaiApi(messages, modelName="gpt-4", maxTokens=2048, temperature=1){ const endpoint = "https://api.openai.com/v1/chat/completions"; const apiKey = API_KEY_OPENAI; const payload = { "messages": messages , "model": modelName , "max_tokens": maxTokens , "temperature": temperature }; const options = { "method": "POST" , "headers": { "Content-Type": "application/json" , "Authorization": "Bearer " + apiKey } , "payload": JSON.stringify(payload) }; const res = UrlFetchApp.fetch(endpoint, options); return JSON.parse(res.getContentText());}この関数の中の、prompt変数にはプロンプトしたい文章が入っています。
ChatGPT で入力してみるとこんな感じ。
この[# プレーンテキスト]から[# 欲しい情報]を[# 形式]の形式で取得してください
[# プレーンテキスト]
スクリプト TestFunctionSheet を正常に完了できませんでした。失敗のまとめを下記に示します。このスクリプトのトリガーを設定したり、今後の失敗の通知の受信設定を変更したりするには、ここをクリックしてください。
起動 関数 エラー メッセージ トリガー End
2024/02/18 5時00分55秒 日本標準時 testfunction TypeError:statement.replace is not a function time-based 2024/02/18 5時01分15秒 日本標準時
よろしくお願いいたします。Google Apps Script
サポートが必要な場合は、Google Apps Script のドキュメントをご覧ください。本メールは配信専用です。ご返信なさらぬようご注意ください。(c) 2024 Google
[# 欲しい情報]- function- error_message- trigger
[# 形式]HTMLのTable形式で、[# 欲しい情報]がヘッダーになる。
GAS を実行してみて、こんなエラーメッセージが表示されたら、おそらくクレジットが0になっている可能性があるので、さっきの Developer Platform でクレジットカードを登録しましょう。。。(5 ドルくらい払えばとりあえず問題ないと思います。)
"error": { "message": "The model `gpt-4` does not exist or you do not have access to it. Learn more: https://help.openai.com/en/artic... (use muteHttpExceptions option to examine full response)3.Parserで欲しい情報を抽出する
次に、 GPT-4 に生成してもらった内容を切り抜いていきましょう。
今回は、GAS のParserライブラリを使っていきます。スクリプトのライブラリに入れていきましょう。
(ID は、1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw)
そして、情報を抽出していきます。このために、 GPT-4 には HTML 形式で情報を出してもらったのでした。
/** * @description Write error log from Gamil into the sheet with GPT. * @return {boolean}*/function writeErrorLogFromGmailWithGpt(){ const sheetName = SHEET_NAME_1ST; const colOfNumber = 1; const targetSubject = `Summary of failures for Google Apps Script: `; let query = `label:inbox is:unread subject:"${targetSubject}"`; // 検索条件を設定 let threads = GmailApp.search(query); // Gmailのスレッドを検索
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); let startRow = 2; let numbers = sheet.getRange(startRow, colOfNumber, 1, sheet.getLastRow()).getValues().map(record => { return record; }); let maxNumber = Math.max(numbers); let i = 0; let valuesToWrite = []; console.log(`writeErrorLog: 222222222222222222222222222222222222222222222222222222222222222222`); threads.forEach(function(thread) { console.log(thread.getMessageCount()); // スレッド内のメッセージの個数 let messages = thread.getMessages(); // スレッド内のメッセージを取得 messages.forEach(function(message) { console.log(message.getSubject()); // 使う console.log(`writeErrorLog: 333333333333333333333333333333333333333333333333333333333333333`); console.log(message.getDate()); // 使う console.log(message.getPlainBody());
let plainText = message.getPlainBody(); const keywords = ["function", "error_message", "trigger"]; let extractedInfo = extractErrorInfoInPlainText(plainText, keywords) console.log(typeof extractedInfo); console.log(extractedInfo); console.log(extractedInfo["choices"]); console.log(extractedInfo["choices"][0]["message"]); let generatedMsg = extractedInfo["choices"][0]["message"]["content"];
// 追加: START --------------------------------------------------------------------- let extractedErrorInfo = extractErrorInfoFromChatGptMsg(generatedMsg); // 追加: END ---------------------------------------------------------------------
}); });
return true;}それでは、extractErrorInfoFromChatGptMsg()の中身を見ていきましょう。
/** * @description Extract Error info from messages ChatGPT-4 generated. * @param {string} message * @return {string[]}*/function extractErrorInfoFromChatGptMsg(message){ const markToParse1 = "th"; const markToParse2 = "td";
let texts1 = Parser.data(message).from(`<${markToParse1}>`).to(`</${markToParse1}>`).iterate(); let texts2 = Parser.data(message).from(`<${markToParse2}>`).to(`</${markToParse2}>`).iterate(); console.log(texts1); console.log(texts2); console.log(`extractErrorInfoFromChatGptMsg: 11111111111111111111111111111111111111111111111111111111111111`);
let returnObj = {}; returnObj["is_table_deformed"] = false; if(texts1.length !== texts2.length || texts2.length !== 3){ console.log(`extractErrorInfoFromChatGptMsg: 22222222222222222222222222222222222222222222222222222222222222222`); returnObj["is_table_deformed"] = true; }
let returns = []; for(let i = 0; i < texts2.length; i++){ returns.push(texts2[i].replaceAll(" ","_")); } returnObj["info_list"] = returns; console.log(`extractErrorInfoFromChatGptMsg: 99999999999999999999999999999999999999999999999999999999999999`);
return returnObj;}すると、returnObj["info_list"]の中に、必要な情報が入った配列が入ります。
[ 'testfunction', 'TypeError: statement.replace is not a function', 'time-based' ]4.スプレッドシートに記録する
書き込みたい情報は取れたので、シートに書きます。
/** * @description Write error log from Gamil into the sheet with GPT. * @return {boolean}*/function writeErrorLogFromGmailWithGpt(){ const sheetName = SHEET_NAME_1ST; const colOfNumber = 1; const targetSubject = `Summary of failures for Google Apps Script: `; let query = `label:inbox is:unread subject:"${targetSubject}"`; // 検索条件を設定 let threads = GmailApp.search(query); // Gmailのスレッドを検索
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); let startRow = 2; let numbers = sheet.getRange(startRow, colOfNumber, 1, sheet.getLastRow()).getValues().map(record => { return record; }); let maxNumber = Math.max(numbers); let i = 0; let valuesToWrite = []; console.log(`writeErrorLog: 222222222222222222222222222222222222222222222222222222222222222222`); threads.forEach(function(thread) { console.log(thread.getMessageCount()); // スレッド内のメッセージの個数 let messages = thread.getMessages(); // スレッド内のメッセージを取得 messages.forEach(function(message) { console.log(message.getSubject()); // 使う console.log(`writeErrorLog: 333333333333333333333333333333333333333333333333333333333333333`); console.log(message.getDate()); // 使う console.log(message.getPlainBody());
let plainText = message.getPlainBody(); const keywords = ["function", "error_message", "trigger"]; let extractedInfo = extractErrorInfoInPlainText(plainText, keywords) console.log(typeof extractedInfo); console.log(extractedInfo); console.log(extractedInfo["choices"]); console.log(extractedInfo["choices"][0]["message"]); let generatedMsg = extractedInfo["choices"][0]["message"]["content"];
let extractedErrorInfo = extractErrorInfoFromChatGptMsg(generatedMsg);
// 追加: START --------------------------------------------------------------------- valuesToWrite.push([ maxNumber + i , generateFormulaForConId(colOfNumber, maxNumber + i + 1) , message.getDate() , message.getDate() , message.getSubject().replace(targetSubject, "") , extractedErrorInfo["info_list"][0] , extractedErrorInfo["info_list"][1] , extractedErrorInfo["info_list"][2] ]); message.markRead(); console.log(message.isUnread()); console.log(`writeErrorLog: 6666666666666666666666666666666666666666666666666666666666666666666`); // 追加: END ---------------------------------------------------------------------
}); }); // 追加: START --------------------------------------------------------------------- console.log(valuesToWrite); console.log(`writeErrorLog: 777777777777777777777777777777777777777777777777777777777777777777`); sheet.getRange(maxNumber + 2, colOfNumber, valuesToWrite.length, valuesToWrite[0].length).setValues(valuesToWrite); // 追加: END ---------------------------------------------------------------------
return true;}そしたら、こんな風にシートに書き込まれます。

5.運用コストがさぁ・・・
これでツールは完成!
・・・ですが、実装する過程で 7 回くらい Chat Completions API にリクエストしたわけですが、
その直後に Developer Platform で消費したクレジットの額面を見てみると・・・

たったの7リクエストで 18 円も掛かっている・・・
例えば、 1 日に 3 通のエラーメッセージをシートに記録するとなると、 3 × 365 × 18 ÷ 7 ≒ 2815 円も年額として掛かりますよ!? うっ、うひゃあ・・・。
これは実際に運用するかどうかというのは、別問題になってくるぞ・・・
まとめ
今回は、以下の流れでGmailの内容をGPT-4で抽出する処理を実装しました。
- Gmail のメッセージを取得する
- OpenAI の Chat Completions API を使う
Parserで欲しい情報を抽出する- スプレッドシートに記録する
Gmail に届くメッセージから必要な情報を効率的に抽出することは、日々の業務効率化に大きく寄与します。 Gmail API と OpenAI API を組み合わせることで、メール管理の自動化が可能となり、重要な情報を見逃すリスクを減らすことができるでしょう。
しかし、お財布と相談するのを忘れずに。
おしまい


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!