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

2314 語
12 分
【GAS、GPT-4】OpenAI APIでGmailに届くエラーメールをスプレッドシートに記録する

はじまり#

リサちゃん avatar
リサちゃん
あ~~、これ自動で書いてくんねえかな・・・
135ml avatar
135ml
Gmailのやつか
リサちゃん avatar
リサちゃん
届けてくれるのはありがたいんだけど、もう一つ欲しいよね
135ml avatar
135ml
じゃあ、作るか

今回やりたいこと#

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で抽出する処理を実装しました。

  1. Gmail のメッセージを取得する
  2. OpenAI の Chat Completions API を使う
  3. Parserで欲しい情報を抽出する
  4. スプレッドシートに記録する

Gmail に届くメッセージから必要な情報を効率的に抽出することは、日々の業務効率化に大きく寄与します。 Gmail API と OpenAI API を組み合わせることで、メール管理の自動化が可能となり、重要な情報を見逃すリスクを減らすことができるでしょう。

しかし、お財布と相談するのを忘れずに。

おしまい#

リサちゃん avatar
リサちゃん
記録できたぞ
135ml avatar
135ml
財布的に、年がら年中は使えんかもなあ・・・

以上になります!

記事を共有

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

【GAS、GPT-4】OpenAI APIでGmailに届くエラーメールをスプレッドシートに記録する
https://endorphinbath.com/posts/gas-record-gamil-with-openai/
著者
kinkinbeer135ml
公開日
2024-02-24
ライセンス
CC BY-NC-SA 4.0
関連記事 スマート
1
【GAS】Googleカレンダーに曜日を指定してスケジュールを登録するスプレッドシートの構築
Code Google Apps Scriptを使い、スプレッドシートからGoogleカレンダーに曜日指定でスケジュールを追加するシステムを作りました。繰り返し入力する版と個別に入力する版があります。
2
【GAS、Google Spreadsheet】Googleドキュメントで日記を付けるために毎日Docファイルを作ってくれるスクリプトです
Code GoogleDriveにあるファイルを毎日決まった時間にコピーしてくれるスクリプトを書きました。日記をつける場合に毎回ファイルをコピーしてレイアウトを変えて・・・といった作業を効率化してしまいましょう!
3
【Google Apps Script】自分がGASで使うIDとかトークンを1つのシートで管理するライブラリを作ろうとしたけど、断念した話
Code Google Apps Scriptで使うフォルダIDやスプレッドシートIDなどを一括管理するライブラリをスプレッドシート上で作ろうと思ったのですが、とある理由により頓挫しました。貴方もお気を付け下さい。
4
【GAS】AppSheetでグラフを描くために数値データを線形補間する
Code AppSheetで折れ線チャートを描くと、空欄の値は0に判定されてしまいます。場合によってはグラフが見にくくなる原因にもなります。そこで、値がないセルにデータを補間する方法を書きました。
5
【Google Apps Script】onOpen時に「Spreadsheet.openByIdを呼び出す権限がありません」となり、メニューが追加されない
Code onOpenなどのSImple Triggerを使ったスクリプト実行時に、「Spreadsheet.openByIdを呼び出す権限がありません」みたいな権限エラーが発生した時に試したことを紹介します。
ランダム記事 ランダム
Profile Image of the Author
kinkinbeer135ml
SIerをやめて、プログラミングを勉強しています。※Amazonアソシエイトに参加しています。
お知らせ
私のブログへようこそ!これはサンプルのお知らせです。
音楽
カバー

音楽

再生中なし

0:00 0:00
歌詞なし
カテゴリ
タグ
サイト統計
記事
287
カテゴリー
8
タグ
93
総文字数
486,174
運用日数
0
最終活動
0 日前

目次