Google Apps ScriptでGPTsのCustom ActionsのAPIを作ってみた

## はじめに
GPTsのCustom Actions（カスタムアクション）をつかって、外部のAPIを呼び出して、データを取得することができるようになりました。

たとえば、郵便番号検索APIを呼び出して、ユーザーから入力された住所から、郵便番号を回答するGPTを作ることができます。

詳細については[こちら](https://qiita.com/iconss/items/abad02ad618a4e795c94)。

この記事では、Custom Actionsから呼び出されるAPIを、Google Apps Scriptを使って無料で作る方法について説明します。

この記事は[こちらのYouTube動画](https://youtu.be/J9FaJ5LHTgE?si=xn_qtxwENLEa21rn)でご紹介いただいております。

### この記事の対象者
- GPTsのCustom ActionsのAPIを自作したい方
- Google Apps Scriptで簡単なスクリプトを作ったことがある方
- Zapierの連携よりも複雑な処理を求めている方

## APIの概要
シーケンス図で表現しました。

![スクリーンショット 2023-11-13 11.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/c07ba9b6-ab50-23ea-c420-df4187b9ded6.png)

"Google Apps Script"にあたるところが、今回作成するAPIになります。
処理としては4桁の数字をキーとして、Googleスプレッドシートからその数字に対応する苗字と名前を探します。
あと、いくつかのエラー処理も入ってます。

ちなみに、上から2番目と3番目の処理
- 質問文から4桁の数字を取り出す
- 4桁の数字のデータを渡す

がCustom Actionsにあたります。

## Googleスプレッドシートを作成
こちらが今回のサンプルのGoogleスプレッドシートです。
番号と苗字・氏名はChatGPTにサンプルデータを生成してもらいました。
![スクリーンショット 2023-11-13 7.05.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/1c02b489-7737-31bd-29d8-728565f7b8f7.png)

作成したGoogleスプレッドシートの
スプレッドシートIDをテキストエディタなどにコピペしてひかえます。
→次の手順で使用します。

![スクリーンショット 2023-11-14 12.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/15675882-42c5-92c5-d88d-0c935d3531a8.png)

この記事のサンプルのGoogleスプレッドシートをシェアしました。
よろしければ、ご自身のGoogleドライブにコピーしてお使いください。
共有リンクは[こちら](https://docs.google.com/spreadsheets/d/1_UoM1S5V0ZRB92p7h3Hv9z-WQqXvS-I7IpvGtsYNq00/edit?usp=sharing)

## Google Apps Scriptを作成
スプレッドシートの拡張機能＞Apps Scriptを選択します。
![スクリーンショット 2023-11-13 9.13.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/d1adec12-bd41-b11a-df32-1001ffb68e8b.png)

Apps Scriptのエディタが開きます。
![スクリーンショット 2023-11-13 9.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/e3deeed0-47e4-fddd-405a-85fc2dbe6546.png)

こちらのコードを貼り付けます。
// スプレッドシートの準備 の SpreadsheetApp.openById の箇所に
先ほどコピーしてひかえたスプレッドシートIDを貼り付けます。
~~~ JavaScript:コード.gs
function doGet(e) {
  // リクエストからパラメータを取得
  console.log("start GPTs Custom Actions API test");
  console.log("e: " + JSON.stringify(e));
  var searchQuery = e.parameter.id;
  console.log("e.parameter.id: " + e.parameter.id);

  // 4桁の数字かどうかをチェック
  if (!searchQuery || !/^\d{4}$/.test(searchQuery)) {
    console.log("bad format: " + searchQuery);
    return ContentService.createTextOutput(
      JSON.stringify({ "error": "Invalid request. Please provide a 4-digit number." })
    ).setMimeType(ContentService.MimeType.JSON);
  }

  // スプレッドシートの準備
  var sheet = SpreadsheetApp.openById('ここにスプレッドシートIDを貼り付ける').getSheetByName('シート1');
  var data = sheet.getDataRange().getValues();
  console.log("format OK");

  // スプレッドシートを検索
  for (var i = 1; i < data.length; i++) {  // 2行目から開始
    if (data[i][0].toString() === searchQuery) {
      console.log("hit: " + i);
      console.log("last: " + data[i][1]);
      console.log("first: " + data[i][2]);
      // 見つかった場合、JSONとして返す
      return ContentService.createTextOutput(
        JSON.stringify({ "lastname": data[i][1], "firstname": data[i][2] })
      ).setMimeType(ContentService.MimeType.JSON);
    }
  }
  console.log("not found");
  // 一致するデータが見つからない場合
  return ContentService.createTextOutput(
    JSON.stringify({ "error": "No data found for the provided number." })
  ).setMimeType(ContentService.MimeType.JSON);
}
~~~ 
コードの処理内容は以下の通りです。

- リクエストからパラメータの取得: リクエストからidという名前のパラメータを取得し、searchQuery変数に格納します。

- パラメータの形式チェック: 取得したsearchQueryが4桁の数字であるかどうかをチェックします。この形式に合致しない場合、エラーメッセージを含むJSONレスポンスを返し、処理を終了します。

- スプレッドシートの準備: 特定のスプレッドシートを開き、'シート1'という名前のシートからデータを取得します。

- スプレッドシート内の検索: スプレッドシートのデータをループ処理し、各行の最初のセルの値がsearchQueryと一致するかを確認します。ループは2行目から始まります。

- データが見つかった場合の処理: 一致するデータが見つかると、その行の2番目と3番目のセルの値を姓（lastname）と名（firstname）としてJSON形式でレスポンスとして返します。

- データが見つからなかった場合の処理: スプレッドシート内で一致するデータが見つからない場合、「提供された番号に対するデータが見つからない」（英文）というエラーメッセージを含むJSONレスポンスを返します。

## Google Apps Scriptをデプロイ
![スクリーンショット 2023-11-13 9.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/26c8e0b7-5c0c-e236-ad83-ab61bbf2ea09.png)
![スクリーンショット 2023-11-13 7.01.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/948f8bce-3d28-e00e-7ad2-ebc7c42993af.png)
![スクリーンショット 2023-11-13 7.01.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/fa71955a-9ecc-e913-eeed-812a9fc96bba.png)
![スクリーンショット 2023-11-13 7.02.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/8db6c695-559f-c41f-1086-59c5982ca423.png)
![スクリーンショット 2023-11-13 7.02.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/daad3b5a-5381-b52a-3cb2-59bf44d0b685.png)
![スクリーンショット 2023-11-13 7.02.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/fe2b7471-9ede-a9c6-4ee5-6d3f0426a9e8.png)
![スクリーンショット 2023-11-13 7.02.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/f0c5954d-26d9-7509-c718-08835866b9b8.png)
![スクリーンショット 2023-11-13 7.02.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/5c947004-cf80-b56e-847f-e1a31fd51c52.png)
デプロイが完了するとこちらの画面になります。
ウェブアプリの　コピー　をクリックして、URLをコピーしてテキストエディタなどにコピペしておきます。
→次の手順で使用します。
![スクリーンショット 2023-11-13 7.03.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/a36faa0e-a150-77ad-2efa-7424aa3eba2e.png)

## GPTsを作成
![スクリーンショット 2023-11-13 9.58.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/b61da4c5-5793-cac6-9ccb-a0e49ce13997.png)
Upload files は無し。
Capabilities も何も設定しません。
一番下の
Use conversation data in your GPT to improve our models
のチェックボックスもOFFにします。
![スクリーンショット 2023-11-13 7.13.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/1072893e-8bcc-61aa-b843-2b5d7e3c5996.png)

## GPTsのCustom Actionsを設定
前の項目のスクショで説明した歯車のアイコンをクリックしてCustom Actionsの設定画面を開きます。
![スクリーンショット 2023-11-13 9.59.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/191952f6-8570-0f5f-dd83-fbf6ccdc55b0.png)

Schemaは以下のコードをコピペします。
コピペしたら、前の章でコピーしておいたデプロイしたURLの一部（/macros以下）を、貼り付けます。
上のスクショで赤文字で説明している箇所に貼り付けます。

例えばデプロイしたURLが

>https://script.google.com/macros/s/AKfycbzR2GGFg_9Ilnv55jzZDBQ3J_miH_OIQUn3qDJpPDBWSlnw-OT6H6rRZhkJ57EIijUYEQ/exec

の場合

>/macros/s/AKfycbzR2GGFg_9Ilnv55jzZDBQ3J_miH_OIQUn3qDJpPDBWSlnw-OT6H6rRZhkJ57EIijUYEQ/exec

の部分を貼り付けます。

~~~JSON:Schema
{
  "openapi": "3.1.0",
  "info": {
    "title": "Get name data",
    "description": "Get lastname and firstname by id",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "https://script.google.com"
    }
  ],
  "paths": {
    "ここにデプロイしたURLの /macros以下 のアドレスを設定": {
      "get": {
        "description": "Get lastname and firstname by id",
        "operationId": "GetNameByid",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "description": "ID No.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "NameResponse": {
        "type": "object",
        "properties": {
          "lastname": {
            "type": "string"
          },
          "firstname": {
            "type": "string"
          }
        }
      }
    }
  }
}
~~~

## ためしてみる
よろしければ、[こちら](https://chat.openai.com/g/g-nHtEXOwig-gpts-custom-actions-api)からお試しください。

![スクリーンショット 2023-11-13 9.25.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/bf9bd849-6a7e-27c1-05c8-6c7582be805b.png)
Custom Actionを実行していいか聞いてきますので、
Allow か Always allow をクリックします。
→Always allow をクリックすれば、再度聞いてきません。
![スクリーンショット 2023-11-13 7.04.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/2ad3bbc5-c9c5-7cda-c08d-8d76050591aa.png)
Custom Actionが実行され、1010の番号に対応する苗字「黒田」の回答を得ました。
![スクリーンショット 2023-11-13 9.26.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/c1a6da9d-6541-9e25-ee3d-ff69426094d6.png)
![スクリーンショット 2023-11-13 7.05.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/1c02b489-7737-31bd-29d8-728565f7b8f7.png)

Custom Actionでは番号 1010 が送られていることがわかります。
![スクリーンショット 2023-11-13 9.26.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/90a72f04-3d1a-8067-d31c-de5526df806d.png)

## Custom ActionsがあるGPTsの公開について
Custom Actions の Privacy Policy（プライバシーポリシー）が空白だと、Only people with a link や Public で第三者に公開できないです。
→Only me で自分だけ使用するならOK。
![スクリーンショット 2023-11-13 9.34.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/a4d05260-7dd9-113f-c607-57ab6d7db7e9.png)

Google Siteでプライバシーポリシーのページを用意して公開しました。
![スクリーンショット 2023-11-13 9.27.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/4bf209d9-a1ab-f92f-41fc-1bfaaa95ddcf.png)

プライバシーポリシーのサイトは[こちら](https://sites.google.com/view/gpts-api-privacy-policy/)

設定したプライバシーポリシーは、ユーザーがGPTsを実行する時に確認ができます。
![スクリーンショット 2023-11-13 9.26.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/e0fef38a-1eb8-f87a-f528-d74e2d16b9d2.png)

## 考察
### Google Apps Scriptのデプロイ方式について
デプロイ方式は「ウェブアプリ」の他に「実行可能API」もあります。
「実行可能API」で試したところ、Google Apps Scriptにデータがうまく渡されなかったので、今回は「ウェブアプリ」のデプロイ方式を採用。

### Custom ActionsからGoogle Apps ScriptのAPIにデータを渡すところ
#### Schemaの設定
"id" が4桁の数字の番号にあたります。
数字ではなく文字列として渡しています。
GPTsはこのSchemaの設定を元に、ユーザーからの質問文を解析して、4桁の数字の番号を抜き出して、APIにデータを渡します。
![スクリーンショット 2023-11-13 6.55.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/e80befe3-ab23-07f8-41db-0364dd2c7682.png)
APIに渡すデータを増やしたい場合は、赤枠で囲った部分をコピーして、カンマ "," で区切ってコピーします。

#### Google Apps Scriptのコード
JSON形式でデータを受信して、JSON形式のデータから id を取り出し、searchQuery変数に代入
![スクリーンショット 2023-11-13 12.16.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/a6f62962-3324-1c2b-7c50-36fa8c2d5782.png)

### Google Apps ScriptのAPIからCustom Actionsにデータを返すところ
#### Google Apps Scriptのコード
searchQuery変数でGoogleスプレッドシートを検索し、ヒットしたら
苗字 → lastname
名前 → firstname
でJSON形式のデータを作成して、返します。
![スクリーンショット 2023-11-13 12.19.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/16de3213-b1c1-cfb3-6949-526ddf845c4c.png)

#### Schemaの設定
受信したJSON形式のデータから
lastnameとfirstnameを取得し、GPTsがそれを日本語に置き換えて、ユーザーに回答します。
![スクリーンショット 2023-11-13 10.15.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/a9053fed-5854-6c3e-b123-c1861db8ee70.png)
やり取りするデータを増やしたい場合は、赤枠で囲った部分をコピーして、カンマ "," で区切ってコピーします。

## おわりに
今回紹介したGPTsからGoogleスプレッドシートのデータを取得するのは、Zapier使えばノーコードでできます。

Zapierの無料プランの制限を気にせず運用したい場合や、もっと複雑なことやりたい場合にGoogle Apps Scriptを使ってみるのはいかがでしょうか。

JavaScriptのプログラミングが必要ですが、ChatGPTでプログラムしてもらえる時代です。
ここで紹介しているコードもChatGPTをつかって書きました。
→もちろんデバッグや、多少の手直しは自分でやっております。

Google Apps Scriptを改造すれば、例えばGoogleスプレッドシートに独自の英単語帳を作って、ランダムにGPTsに返すことも可能です。

Googleスプレッドシートに連携するAPIのいいところは、データの更新が楽なこと。
更新の都度、GPTsの設定画面を開いて、ファイルをアップロードする必要がないので、常に最新のGoogleスプレッドシートの情報を取得できます。

GoogleスプレッドシートだけでなくGoogleドキュメントやGmail、Googleカレンダーと連携すれば、アイディア次第でGPTsの活用がいろいろ広がりそうです。

この記事を参考にご自身でCustom ActionsのAPIを開発する際の一助になれば、幸いです。

最後までご精読いただき、ありがとうございました。

## 補足
初めてデプロイ後に、Google Apps Scriptを編集した場合、こちらの手順でデプロイします。
「デプロイを管理」を選択。
![スクリーンショット 2023-11-13 9.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/68e59bb0-6e2f-a7d9-b273-ef216e6daf7c.png)
右側のペンのアイコンをクリック
![スクリーンショット 2023-11-13 11.32.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/da4cbf7f-9c8a-94b9-8b44-80d5cff37259.png)
一旦、バージョンをクリックして、
![スクリーンショット 2023-11-13 11.32.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/6dd09908-1688-d636-f193-1053b87ca9ac.png)
新バージョンを選択して、デプロイします。
![スクリーンショット 2023-11-13 11.33.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/7359d99e-7585-394f-4a56-42dfe91ed723.png)
この手順でデプロイすれば、デプロイのURLは変更されません。
→GPTsのCustom ActionsのSchemaを編集する必要はありません。

## 参考資料
Google　- Google サイトの使い方
https://support.google.com/sites/answer/6372878?hl=ja