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

Code

はじまり

リサちゃん
リサちゃん

あ~~、これ自動で書いてくんねえかな・・・

135ml
135ml

Gmailのやつか

リサちゃん
リサちゃん

届けてくれるのはありがたいんだけど、もう一つ欲しいよね

135ml
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キーを作りましょう。

Just a moment...

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 plain text.
 * @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を組み合わせることで、メール管理の自動化が可能となり、重要な情報を見逃すリスクを減らすことができるでしょう。

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

おしまい

リサちゃん
リサちゃん

記録できたぞ

135ml
135ml

財布的に、年がら年中は使えんかもなあ・・・

以上になります!

コメント

タイトルとURLをコピーしました