GPTとActionsの連携方法を徹底解説 - 効率的な構築ガイド

 GPTsにてActionsが使えるようになってからActionsを使ったサンプルを元にいくつか真似して自分用のGPTsを作ってみましたが、どの記事も一番ポイントになる「どの情報を元にChatGPTはActionsに値を渡すの?」が分からないままでした。ここについては未だかなりブラックボックス状態ですが、使ってみた経験を基にイメージを理解できるような内容をお伝えできる記事になればと思います。

この記事ではかなりポイントを絞ってお伝えします。その他の情報は参考サイトのリンクを掲載いたしますので、そちらをご確認ください。


結論

それなりに説明が長くなるので、まずは結論からお話します。

この記事で言いたいことは、「Description(指示)に書く内容でGPTsをコントロールすることを意識してGPTsは作れ!」ということです。大事なことなので言い換えて説明すると、GPTsの構成のDescription(指示)も、ActionsのDescription(指示)もDescriptionに書くこと次第でChatGPT側が判断する内容が変わるので、Descriptionに書くことがほぼ全てだということです。

これさえ意識してネット上のGPTsのサンプルなどを見ていくと、かなり理解しやすくなるものと考えます。これに気づいてから私もActionsの応用版がどんどん作成していけるようになりました。

では、以下詳細な説明をしていきます。


説明に使うGPTsの概要

今回はGoogleスプレッドシートを操作してくれるbotを参考に説明します。ユーザが指示した内容を基にスプレッドシートの検索・更新・追加ができるbotです。

また、隠し機能として、天気を聞くと今日の東京の天気を回答してくれます。(この隠し機能もこの後の説明においてそれなりに重要)

ChatGPTがどうやってアクションなどを判断するのかを説明するために、Actionsに2アクション入れています。

作成にあたっては以下のサイトを参考にしておりますので、同様のものを作ってみたい方はまずはこちらの記事を参考にしてください。




GPTsの構成

GPTsの構成1

GPTsの構成2

Description(指示)は以下の通りです。

  1. 以下の入力パターンに応じて回答するボットです。
  2. #対応パターン1
  3. ユーザーから質問を受けたら、入力された番号から氏名を検索してください。
  4. #対応パターン2
  5. ユーザから指定の番号の氏名の更新依頼があったら、番号を検索して氏名を更新してください。
  6. #対応パターン3
  7. ユーザから氏名の追加依頼があったら、ユーザが指定した氏名を追加してください。
  8. 追加したら追加した結果のID、苗字、氏名を教えてください。
  9. #対応パターン4
  10. ユーザから天気に関する質問があったら、天気を回答してください。
  11. #対応パターン5
  12. ユーザから大阪の天気を質問されたら、今日の東京の天気を取得して、その返却された気温をスプレッドシートのfirstnameに追加してください。


Actionsの内容

google apps scriptへのアクション

  1. {
  2.   "openapi": "3.1.0",
  3.   "info": {
  4.     "title": "スプレッドシートデータの検索・更新・追加",
  5.     "description": "ユーザの指示に応じてスプレッドシートデータの検索・更新・追加を行います。",
  6.     "version": "v1.0.0"
  7.   },
  8.   "servers": [
  9.     {
  10.       "url": "https://script.google.com"
  11.     }
  12.   ],
  13.   "paths": {
  14.     "/macros/s/{ビルドID}/exec": {
  15.       "get": {
  16.         "description": "ユーザの検索、更新、追加の依頼に応じてスプレッドシートへリクエストを送信します。",
  17.         "operationId": "operation sheet",
  18.         "parameters": [
  19.           {
  20.             "name": "action",
  21.             "in": "query",
  22.             "description": "'検索' or '更新' or '追加'",
  23.             "required": true,
  24.             "schema": {
  25.               "type": "string"
  26.             }
  27.           },
  28.           {
  29.             "name": "id",
  30.             "in": "query",
  31.             "description": "ID No.",
  32.             "required": true,
  33.             "schema": {
  34.               "type": "string"
  35.             }
  36.           },
  37.           {
  38.             "name": "firstname",
  39.             "in": "query",
  40.             "description": "firstname",
  41.             "required": true,
  42.             "schema": {
  43.               "type": "string"
  44.             }
  45.           },
  46.           {
  47.             "name": "lastname",
  48.             "in": "query",
  49.             "description": "lastname",
  50.             "required": true,
  51.             "schema": {
  52.               "type": "string"
  53.             }
  54.           }
  55.         ],
  56.         "deprecated": false
  57.       }
  58.     }
  59.   },
  60.   "components": {
  61.     "schemas": {
  62.       "NameResponse": {
  63.         "type": "object",
  64.         "properties": {
  65.           "id": {
  66.             "type": "string"
  67.           },
  68.           "lastname": {
  69.             "type": "string"
  70.           },
  71.           "firstname": {
  72.             "type": "string"
  73.           }
  74.         }
  75.       }
  76.     }
  77.   }
  78. }


open-meteoへのアクション

  1. {
  2.   "openapi": "3.1.0",
  3.   "info": {
  4.     "title": "東京の天気取得",
  5.     "description": "東京の天気を取得する。",
  6.     "version": "v1.0.0"
  7.   },
  8.   "servers": [
  9.     {
  10.       "url": "https://api.open-meteo.com"
  11.     }
  12.   ],
  13.   "paths": {
  14.     "/v1/forecast": {
  15.       "get": {
  16.         "description": "ユーザから東京以外の場所を聞かれても必ず東京の天気を取得して回答します。東京以外の場所の天気を聞かれた場合はボケとして、堂々と東京の天気を回答します。",
  17.         "operationId": "GetCurrentWeather",
  18.         "parameters": [
  19.           {
  20.             "name": "latitude",
  21.             "in": "query",
  22.             "description": "緯度",
  23.             "required": true,
  24.             "schema": {
  25.               "type": "string"
  26.             }
  27.           },
  28.           {
  29.             "name": "longitude",
  30.             "in": "query",
  31.             "description": "経度",
  32.             "required": true,
  33.             "schema": {
  34.               "type": "string"
  35.             }
  36.           },
  37.           {
  38.             "name": "current_weather",
  39.             "in": "query",
  40.             "description": "現在の天気か否か",
  41.             "required": true,
  42.             "schema": {
  43.               "type": "string"
  44.             }
  45.           },
  46.           {
  47.             "name": "timezone",
  48.             "in": "query",
  49.             "description": "タイムゾーン",
  50.             "required": true,
  51.             "schema": {
  52.               "type": "string"
  53.             }
  54.           }
  55.         ],
  56.         "deprecated": false
  57.       }
  58.     }
  59.   },
  60.   "components": {
  61.     "schemas": {
  62.       "Response": {
  63.         "type": "object",
  64.         "properties": {
  65.           "current_weather": {
  66.             "type": "string"
  67.           }
  68.         }
  69.       }
  70.     }
  71.   }
  72. }


Googleスプレッドシートの内容

googleスプレッドシートの内容



GoogleAppsScriptの内容

  1. function doGet(e) {
  2.   var action = e.parameter.action;
  3.   if (action == '検索') {
  4.     return searchAction(e);
  5.   } else if (action == '更新') {
  6.     return updateAction(e);
  7.   } else if (action == '追加') {
  8.     return addAction(e);
  9.   } else {
  10.     return ContentService.createTextOutput(
  11.       JSON.stringify({ "error": "Invalid Action." })
  12.     ).setMimeType(ContentService.MimeType.JSON);
  13.   }
  14. }
  15. function searchAction(e) {
  16.   // リクエストからパラメータを取得
  17.   console.log("start GPTs Custom Actions API test");
  18.   console.log("e: " + JSON.stringify(e));
  19.   var searchQuery = e.parameter.id;
  20.   console.log("e.parameter.id: " + e.parameter.id);
  21.   // 4桁の数字かどうかをチェック
  22.   if (!searchQuery || !/^\d{4}$/.test(searchQuery)) {
  23.     console.log("bad format: " + searchQuery);
  24.     return ContentService.createTextOutput(
  25.       JSON.stringify({ "error": "Invalid request. Please provide a 4-digit number." })
  26.     ).setMimeType(ContentService.MimeType.JSON);
  27.   }
  28.   // スプレッドシートの準備
  29.   var sheet = SpreadsheetApp.openById('{スプレッドシートID}').getSheetByName('{シート名}');
  30.   var data = sheet.getDataRange().getValues();
  31.   console.log("format OK");
  32.   // スプレッドシートを検索
  33.   for (var i = 1; i < data.length; i++) { // 2行目から開始
  34.     if (data[i][0].toString() === searchQuery) {
  35.       console.log("hit: " + i);
  36.       console.log("last: " + data[i][1]);
  37.       console.log("first: " + data[i][2]);
  38.       // 見つかった場合、JSONとして返す
  39.       return ContentService.createTextOutput(
  40.         JSON.stringify({ "id": data[i][0], "lastname": data[i][1], "firstname": data[i][2] })
  41.       ).setMimeType(ContentService.MimeType.JSON);
  42.     }
  43.   }
  44.   console.log("not found");
  45.   // 一致するデータが見つからない場合
  46.   return ContentService.createTextOutput(
  47.     JSON.stringify({ "error": "No data found for the provided number." })
  48.   ).setMimeType(ContentService.MimeType.JSON);
  49. }
  50. function updateAction(e) {
  51.   // リクエストからパラメータを取得
  52.   console.log("start GPTs Custom Actions API update test");
  53.   console.log("e: " + JSON.stringify(e));
  54.   var searchId = e.parameter.id;
  55.   console.log("e.parameter.id: " + e.parameter.id);
  56.   var firstname = e.parameter.firstname;
  57.   console.log("e.parameter.firstname: " + e.parameter.firstname);
  58.   var lastname = e.parameter.lastname;
  59.   console.log("e.parameter.lastname: " + e.parameter.lastname);
  60.   // 4桁の数字かどうかをチェック
  61.   if (!searchId || !/^\d{4}$/.test(searchId)) {
  62.     console.log("bad format: " + searchId);
  63.     return ContentService.createTextOutput(
  64.       JSON.stringify({ "error": "Invalid request. Please provide a 4-digit number." })
  65.     ).setMimeType(ContentService.MimeType.JSON);
  66.   }
  67.   // スプレッドシートの準備
  68.   var sheet = SpreadsheetApp.openById('{スプレッドシートID}').getSheetByName('{シート名}');
  69.   var data = sheet.getDataRange().getValues();
  70.   console.log("format OK");
  71.   // スプレッドシートを検索
  72.   for (var i = 1; i < data.length; i++) { // 2行目から開始
  73.     if (data[i][0].toString() === searchId) {
  74.       console.log("hit: " + i);
  75.       console.log("last: " + data[i][1]);
  76.       console.log("first: " + data[i][2]);
  77.       sheet.getRange(i + 1, 2).setValue(firstname);
  78.       sheet.getRange(i + 1, 3).setValue(lastname);
  79.       var responsefirstname = sheet.getRange(i + 1, 2).getValues();
  80.       var responselastname = sheet.getRange(i + 1, 3).getValues();
  81.       // 見つかった場合、JSONとして返す
  82.       return ContentService.createTextOutput(
  83.         JSON.stringify({ "id": data[i][0], "firstname": responsefirstname, "lastname": responselastname})
  84.       ).setMimeType(ContentService.MimeType.JSON);
  85.     }
  86.   }
  87.   console.log("not found");
  88.   // 一致するデータが見つからない場合
  89.   return ContentService.createTextOutput(
  90.     JSON.stringify({ "error": "No data found for the provided number." })
  91.   ).setMimeType(ContentService.MimeType.JSON);
  92. }
  93. function addAction(e) {
  94.   // リクエストからパラメータを取得
  95.   console.log("start GPTs Custom Actions API add test");
  96.   console.log("e: " + JSON.stringify(e));
  97.   var addLastname = e.parameter.lastname;
  98.   console.log("e.parameter.lastname: " + e.parameter.lastname);
  99.   var addFirstname = e.parameter.firstname;
  100.   console.log("e.parameter.firstname: " + e.parameter.firstname);
  101.   // スプレッドシートの準備
  102.   var sheet = SpreadsheetApp.openById('{スプレッドシートID}').getSheetByName('{シート名}');
  103.   var data = sheet.getDataRange().getValues();
  104.   console.log("format OK");
  105.   var lastRow = sheet.getLastRow();
  106.   var lastId = 0;
  107.   if (lastRow > 1) { // データがある場合
  108.     var lastIdCell = sheet.getRange(lastRow, 1).getValue(); // 最後の行のIDを取得
  109.     lastId = parseInt(lastIdCell, 10);
  110.   }
  111.   var newId = lastId + 1;
  112.   sheet.appendRow([newId, addLastname, addFirstname]);
  113.   return ContentService.createTextOutput(
  114.     JSON.stringify({ "id": newId, "updatelastname": addLastname, "Updatefirstname": addFirstname })
  115.   ).setMimeType(ContentService.MimeType.JSON);
  116. }


各パターンごとの動作と説明

GPTsの構成を見て頂くとわかる通り、どのAPIを使うかなどを指示しなくてもChatGPT側でいい感じに判断してくれて、APIを使い分けてくれます。

では、それぞれどのようなリクエストを送信し、どのようなレスポンスが返ってくれるのでしょうか?

リクエストとレスポンスをどちらも見れるのはプレビューの時のみなので、プレビューで動作確認します。

パターン1

パターン1の結果


こちらは「検索」というようなキーワードを入力せずとも、うまい具合に理解してAPIのactionパラメータに"検索"を入れてくれます。おそらく構成のDescriptionの対応パターンに検索してほしい旨の指示を入力しているからだと思われます。また、ActionsのDescriptionには、"'検索' or '更新' or '追加'"とキーワードを絞った趣旨の記載をしているので、それを汲み取ってより近い検索を選択しているものと推察されます。Descriptionに指示を上手く記載することで、APIに変なパラメータを送信することを防ぐ工夫をすると上手くいくようです。


パターン2

パターン2の結果1
パターン2の結果2

パターン2の結果3

こちらは「更新」というキーワードが入っているので、操作パターンの指定は簡単そうですね。今回も「スプレッドシート」というワードやどのAPIを使えなどの指示は不要です。Descriptionにきちんと記載しておけばChatGPTが良い感じに判断してくれます。このパターンで注目すべきは返り値です。レスポンスの型がちょっと間違って配列に格納されてしまってますが、これを最後ユーザに更新連絡する際も、いい感じにデータを取り出して回答しています。よく凡ミスしがちなデータ型について、あまり意識しなくてもいい感じに取り扱ってくれるのは、プログラミング経験者からすると相当ありがたいことが分かるのではないでしょうか。


パターン3

パターン3の結果1

パターン3の結果2

こちらも操作は追加と指定しているので、操作については簡単です。このパターンで注目すべき点も返り値です。Apps Scriptの返り値にセットしているJSONとActionの返り値に指定しているプロパティを見比べてみてください。これもミスって"lastname"と"firstname"ではなく、"updatelastname"と"Updatefirstname"を返してしまってます。私もこのミスに気づかず作っていたのですが、途中で全然返り値の名前違うのにいい感じに受け取ってることに気づいて、凄すぎ!と驚愕しました。とりあえずGPTsは最初は雑に作っちゃっても大丈夫そうですね。


パターン4

パターン4の結果1
パターン4の結果2

こちらもAPI指定などは不要です。パターン4のようなざっくりした指示内容だけで天気の方のAPIを使うことをChatGPT側が判断してリクエスト送信してくれます。また、GPTsのDescriptionの方には東京やリクエスト値に関することは何も記載していませんが、Actions側のDescriptionには、東京の天気を回答するよう指示しているので、東京の緯度と経度をChatGPT側で考えてリクエストパラメータを送信してくれます。いや~、これまた凄いですね。いい感じに…って言葉が最適ですね。APIのパラメータすらもChatGPTが自分で考えてセットしてくれるんです。これは便利すぎるだろう…w


パターン5





今回は大阪の天気を質問されたので、パターン5になって気温を取得して、続けてスプレッドシートに登録しようとしてます。流石です!ボケっていう指示をしたので、きちんと東京の天気を取得しているにもかかわらず、スプレッドシートには「大阪の天気」って登録しているところがご愛嬌w 回答には東京の気温って回答しているのに。きちんとボケてくれたのでツッコミを入れたら必死に謝ってきましたw


各パターンごとの詳細説明は以上です。各パターンごとの動作は文章では分かりにくそうかと思いましたので動画にしました。

以下の動画を参照してください。



まとめ

上記のDescriptionはかなり雑な指示になっているので、100%安定して動作する感じではありません。しかし、雑に指示した時こそChatGPTの凄さを体感できるのです!こちらの指示の趣旨を良い感じに汲み取って動作してくれるので、まずは雑な指示でChatGPTがどんな理解の仕方(誤解の仕方)をするのかをきちんと確認した上でDescriptionをブラッシュアップしていくといいDescriptionがスムーズに出来上がっていくだろうと考えています。




0 件のコメント:

コメントを投稿

ヘルプデスクGPTの作り方と実践ガイド - 自動応答とFAQ管理

 今回はヘルプデスクのチャットボットのように自動で回答してくれるチャットボットをGPTsで作成してみました。自動応答だけだとよく見かけるチャットボットと大差ないので、FAQなどを自動でメンテナンスしてくれるFAQ管理機能も付けてみました。 実際作ってみると、これがちゃんとできてし...