この記事は WHITEPLUS Advent Calendar 2023 の 12月8日の記事です。
はじめに
こんにちは、CX開発グループでWeb開発を担当している德廣です!
本記事では、GPTsに興味を持ち、チーム運営にどのように活用できるかを探った経験をお話します
GPTsとは
- 端的にいうと、特定の業務に特化したChatGPTを作ることができるものです。 openai.com
背景
- 弊社ではナレッジツールとして esa.io を利用しています。
- 以降 esa と呼びます。
- CX開発グループでは開発の決め事をesaにADR(Architectural Decision Records)として残しています。
- ADRを残しているのはいいものの、増えてくるとその決まり事を守ろうとするのが非常に難しくなっていきます。
- そこで、開発したコードや作業がその決まりに沿っているのかを教えてくれる仕組みを作ろう思いGPTsに頼ってみました。
作ったもの
- コードを入力すると、そのコードがADRに準拠しているかどうかを確認してくれるアシスタント
どうやって作るのか
作ったものとは別の「サンプルGPT」を作って紹介します
「esaと連携をする」に焦点をあてて紹介します!
1. カスタムGPTを作成する
左下のユーザー名をクリック -> [MyGPTs] を開く
Create a GPT でカスタムGPTを作成
2. カスタムGPTにしてほしいことを教える
Configure タブについて
- カスタムGPTは Create タブを用いて対話式でカスタマイズできます。
- しかし対話式で作成する記事は既にたくさんある事と、再現性も鑑みて今回は記事が少ないConfigureを自分で編集する方法をお伝えします
- Name : カスタムGPTの名前
- Description : カスタムGPTの説明
- Instructions : このGPTについて教える
- このGPTは何をするのか?
- どのような動作をするのか?
- 避けるべきことは何か?
- Conversation starters : ユーザーとの会話のきっかけ
- Knowledge : ナレッジ資料のアップロード (今回は使いません)
- PDFなどの資料を学習させることができます
- Capabilities : カスタムGPTの能力 (今回は使いません)
- Web Browsing Webにアクセスできるようにする
- DALL·E Image Generation 画像生成できるようにする
- Code Interpreter コードインタプリターできるようにする
- コードインタプリターとは、回答に「プログラミングを用いる必要がある」と判断した時にPython言語でコードを生成し、仮想環境でコードを処理して結果を返す機能です。
- Actions : 外部APIとの連携設定
Configure でカスタムGPTの設定を書く
Preview で設定したカスタムGPTの動作を試す
- 画面右側に表示されている Preview は実際にカスタムGPTを使う時の画面です。
- ここで Conversation starters を試したり、会話をしてカスタムGPTの挙動を確認できます。
3. esaをカスタムGPTで使う準備をする(アクセストークンの発行)
- [SETTINGS] -> [その他] の中から [外部アプリ連携] から発行画面を開く
- Personal access tokens から Generate new token ボタンから発行する
- Token description : 発行するアクセストークンの説明
- Select scopes : 発行するアクセストークンの権限
- Read : 読み込み権限
- Write : 書き込み権限
- 発行されたアクセストークンが表示される
- 発行されたアクセストークン と書かれた欄のアクセストークンを控えておく
- ※このイメージはトークンをマスクしています。
4. カスタムGPTでesaを使えるようにする
カスタムGPTの Configure に戻り Actions の Create new action をクリックする
Add actions 画面の説明
- Schema : 使用するAPIのスキーマ(詳細は後述します)
- Authentication : 使用するAPIの認証設定
- Privacy policy : 使用するAPIのプライバシーポリシーページの設定
Authentication の歯車ボタンをクリックして、認証を設定する
- 以下の設定を行い Save で保存する
- Authentication Type : API Key を選択
- API Key : esaで発行したアクセストークンを設定
- Auth Type : Bearer を選択
- 以下の設定を行い Save で保存する
Schema にesa.ioのスキーマ仕様を読み込む
- 参考にさせていただいた仕様やJsonSchemaそのままでは使えなかったので、自作して埋め込み
$ref
が使えなかったので、 使わない形式に変更。GET
以外のメソッドは削除(読み込みのみの利用のため)posts
などの使いそうなPath以外は削除
- 参考
自作したSchemaは以下においておきます
クリックして開く(長いので注意)
{ "openapi": "3.11.0", "info": { "title": "esa API v1", "description": "チームのナレッジ共有サービス[esa.io](https://esa.io/)のAPI v1の仕様書", "version": "1.0.0" }, "paths": { "/teams": { "get": { "summary": "所属するチーム一覧を取得する", "operationId": "getTeams", "tags": [ "team", "esa" ], "parameters": [ { "name": "page", "in": "query", "description": "ページ番号", "schema": { "type": "integer", "example": 1 } }, { "name": "per_page", "in": "query", "description": "1ページあたりに含まれる要素数", "schema": { "type": "integer", "example": 100 } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedTeams" } } } } } } }, "/user": { "get": { "summary": "認証中のユーザーを取得する", "description": "現在のアクセストークンで認証中のユーザーの情報を取得します。", "operationId": "getAuthenticatedUser", "tags": [ "user", "esa" ], "parameters": [ { "name": "include", "description": "teams を指定すると所属するチームの配列を含んだレスポンスを返します。", "in": "query", "schema": { "type": "string", "enum": [ "teams" ] } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthenticatedUser" } } }, "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } } } } } }, "/teams/{team_name}/members": { "get": { "summary": "メンバーを取得する", "operationId": "getMembers", "tags": [ "member", "esa" ], "parameters": [ { "name": "team_name", "in": "path", "description": "チーム名", "required": true, "schema": { "type": "string", "example": "docs" } }, { "name": "sort", "in": "query", "schema": { "type": "string", "enum": [ "posts_count", "joined", "last_accessed" ], "description": "設定可能な値:\n\n- `posts_count`: チーム内での記事の作成数 (default)\n- `joined`: チームへ参加日時\n- `last_accessed`: 最終アクセス日時" } }, { "name": "order", "in": "query", "schema": { "type": "string", "enum": [ "asc", "desc" ] }, "description": "設定可能な値:\n\n- `desc`: 降順 (default)\n- `asc`: 昇順" }, { "name": "page", "in": "query", "description": "ページ番号", "schema": { "type": "integer", "example": 1 } }, { "name": "per_page", "in": "query", "description": "1ページあたりに含まれる要素数", "schema": { "type": "integer", "example": 100 } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedMembers" } } }, "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } } } } } }, "/teams/{team_name}/posts": { "get": { "summary": "記事一覧を取得する", "operationId": "getPosts", "tags": [ "post", "esa" ], "parameters": [ { "name": "team_name", "in": "path", "description": "チーム名", "required": true, "schema": { "type": "string", "example": "docs" } }, { "name": "q", "in": "query", "description": "記事を絞り込むための条件を指定します", "schema": { "type": "string" } }, { "name": "include", "in": "query", "description": "- `comments` を指定するとコメントの配列を含んだレスポンスを返します。\n- `comments,comments.stargazers`を指定するとコメントとコメントに対するStarの配列を含んだレスポンスを返します。\n- `stargazers` を指定するとStarの配列を含んだレスポンスを返します。\n- `stargazers,comments` のように `,` で区切ることで複数指定できます", "schema": { "type": "array", "items": { "type": "string", "enum": [ "comments", "comments.stargazers", "stargazers" ] } }, "style": "form", "explode": false }, { "name": "sort", "in": "query", "description": "記事の並び順を指定します\n\n- 設定可能な値\n - `updated` (default)\n - 記事の更新日時\n - `created`\n - 記事の作成日時\n - `number`\n - 記事番号\n - `stars`\n - 記事へのStarの数\n - `watches`\n - 記事へのWatchの数\n - `comments`\n - 記事へのCommentの数\n - `best_match`\n - 総合的な記事のスコア", "schema": { "type": "string", "enum": [ "updated", "created", "number", "stars", "watches", "comments", "best_match" ] } }, { "name": "order", "in": "query", "schema": { "type": "string", "enum": [ "asc", "desc" ] }, "description": "設定可能な値:\n\n- `desc`: 降順 (default)\n- `asc`: 昇順" }, { "name": "page", "in": "query", "description": "ページ番号", "schema": { "type": "integer", "example": 1 } }, { "name": "per_page", "in": "query", "description": "1ページあたりに含まれる要素数", "schema": { "type": "integer", "example": 100 } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedPosts" } } }, "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } } } } } }, "/teams/{team_name}/tags": { "get": { "summary": "タグ一覧をタグ付けされた記事数の降順で取得する", "operationId": "getTags", "tags": [ "tag", "esa" ], "parameters": [ { "name": "team_name", "in": "path", "description": "チーム名", "required": true, "schema": { "type": "string", "example": "docs" } }, { "name": "page", "in": "query", "description": "ページ番号", "schema": { "type": "integer", "example": 1 } }, { "name": "per_page", "in": "query", "description": "1ページあたりに含まれる要素数", "schema": { "type": "integer", "example": 100 } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaginatedTags" } } }, "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } } } } } } }, "components": { "schemas": { "AuthenticatedUser": { "title": "AuthenticatedUser", "description": "現在のアクセストークンで認証中のユーザの情報を表します。", "type": "object", "properties": { "id": { "type": "integer", "description": "サービス内で一意なユーザIDです", "example": 1 }, "name": { "type": "string", "description": "ユーザの名前です", "example": "Atsuo Fukaya" }, "screen_name": { "type": "string", "description": "ユーザのスクリーンネームです", "example": "fukayatsu" }, "created_at": { "type": "string", "description": "ユーザの作成日時です", "format": "date-time", "example": "2014-05-10T11:50:07+09:00" }, "updated_at": { "type": "string", "description": "ユーザの更新日時です", "format": "date-time", "example": "2016-04-17T12:35:16+09:00" }, "icon": { "type": "string", "description": "ユーザのアイコンのURLです", "format": "url", "example": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png" }, "email": { "type": "string", "description": "ユーザのemailアドレスです", "format": "email", "example": "fukayatsu@esa.io" }, "teams": { "type": "array", "description": "所属するチームです", "items": { "$ref": "#/components/schemas/Team" } } }, "required": [ "id", "name", "screen_name", "created_at", "updated_at", "icon", "email" ] }, "BatchMoveOptions": { "title": "BatchMoveOptions", "description": "", "type": "object", "properties": { "from": { "type": "string", "description": "変更元のカテゴリです", "example": "/foo/bar/" }, "to": { "type": "string", "description": "変更先のカテゴリです", "example": "/baz/" } }, "required": [ "from", "to" ] }, "BatchMoveResult": { "title": "BatchMoveResult", "description": "", "type": "object", "properties": { "from": { "type": "string", "description": "変更元のカテゴリです", "example": "/foo/bar/" }, "to": { "type": "string", "description": "変更先のカテゴリです", "example": "/baz/" }, "count": { "type": "integer", "description": "サブカテゴリを含む変更されたカテゴリの数を表します", "example": 3 } }, "required": [ "from", "to", "count" ] }, "Comment": { "title": "Comment", "description": "ユーザが作成したコメントを表します。", "type": "object", "properties": { "id": { "type": "integer", "description": "コメントを一意に識別するIDです" }, "body_md": { "type": "string", "description": "Markdownで書かれたコメントの本文です" }, "body_html": { "type": "string", "description": "HTMLに変換されたコメントの本文です" }, "created_at": { "type": "string", "format": "date-time", "description": "コメントが作成された日時です" }, "updated_at": { "type": "string", "format": "date-time", "description": "コメントが更新された日時です" }, "post_number": { "type": "integer", "description": "" }, "url": { "type": "string", "format": "url", "description": "コメントのpermalinkです" }, "created_by": { "allOf": [ { "$ref": "#/components/schemas/User" } ], "description": "コメントを作成したユーザです" }, "stargazers_count": { "type": "integer", "description": "コメントにStarをしている人数を表します" }, "star": { "type": "boolean", "description": "ユーザーがコメントをStarしているかどうかを表します" }, "stargazers": { "type": "array", "description": "コメントにStarをしたユーザ一覧です", "items": { "$ref": "#/components/schemas/Stargazer" } } }, "required": [ "id", "body_md", "body_html", "created_at", "updated_at", "post_number", "url", "created_by", "stargazers_count", "star" ] }, "CreateCommentBody": { "title": "CreateCommentBody", "type": "object", "properties": { "comment": { "$ref": "#/components/schemas/NewComment" } }, "required": [ "comment" ] }, "CreateEmojiBody": { "title": "CreateEmojiBody", "description": "新たに登録する絵文字を表します。", "type": "object", "properties": { "emoji": { "$ref": "#/components/schemas/NewEmoji" } }, "required": [ "emoji" ] }, "CreatePostBody": { "title": "CreatePostBody", "description": "新たに投稿する記事を含んだリクエストボディです。", "type": "object", "properties": { "post": { "$ref": "#/components/schemas/NewPost" } }, "required": [ "post" ] }, "CreatedEmoji": { "title": "CreatedEmoji", "description": "新たに登録された絵文字を表します", "type": "object", "properties": { "code": { "type": "string", "description": "絵文字を入力する際に使うコードです", "example": "team_emoji" } }, "required": [ "code" ] }, "EditPost": { "title": "EditPost", "type": "object", "properties": { "name": { "type": "string", "description": "記事のタイトル\n\n- タイトル自体に`#`を含めたい場合は`#`, `/`を含めたい場合は`/`へそれぞれ置換処理をお願いします。", "example": "hi!" }, "body_md": { "type": "string", "description": "Markdownで書かれた記事の本文です", "example": "# Getting Started" }, "tags": { "type": "array", "description": "記事に紐付けられたタグです。", "items": { "type": "string" }, "example": [ "api", "dev" ] }, "category": { "type": "string", "nullable": true, "description": "記事が属するカテゴリです\n\n設定されていない場合は`null`になります", "example": "日報/2015/05/09" }, "wip": { "type": "boolean", "description": "記事がWIP(Working In Progress)状態かどうかを表します。", "example": false, "default": true }, "message": { "type": "string", "description": "記事更新時の変更メモです。", "example": "Add Getting Started section" }, "created_by": { "type": "string", "description": "記事の投稿者\n\n- チームメンバーのscreen_nameもしくは \"esa_bot\" を指定することで記事の **作成者** を上書きすることができます。\n- このパラメータは **team の owner** だけ が使用することができます。", "example": "esa_bot" }, "updated_by": { "type": "string", "description": "記事の更新者\n\n- チームメンバーのscreen_nameもしくは \"esa_bot\" を指定することで記事の **更新者** を上書きすることができます。\n- このパラメータは **team の owner** だけ が使用することができます。", "example": "esa_bot" }, "original_revision": { "title": "OriginalRevision", "type": "object", "description": "リクエストに正常な `post.body_md` パラメータと `post.original_revision.*` パラメータが存在する場合、記事更新時に3 way mergeが行われます。original_revisionパラメータが存在しない場合は、変更は常に後勝ちになります。\n\n> [release_note/2014/12/23/記事保存時の自動マージ - docs.esa.io](https://docs.esa.io/posts/35)", "properties": { "body_md": { "type": "string", "description": "変更前の記事の本文です", "example": "# Getting Started" }, "number": { "type": "integer", "description": "変更前の記事のrevision_numberを指定します", "example": 1 }, "user": { "type": "string", "description": "変更前の記事の最終更新者のscreen_nameを指定します", "example": "esa_bot" } }, "required": [ "body_md", "number", "user" ] } } }, "Emoji": { "title": "Emoji", "description": "絵文字を表します", "type": "object", "properties": { "code": { "type": "string", "description": "絵文字を入力する際に使うコードです", "example": "grinning" }, "aliases": { "type": "array", "description": "絵文字に対するエイリアスコードです", "items": { "type": "string" }, "example": [ "grinning" ] }, "category": { "type": "string", "description": "絵文字のカテゴリです\n\nカスタム絵文字は`Custom`です。", "example": "People" }, "url": { "type": "string", "format": "url", "description": "絵文字の画像URLです", "example": "https://assets.esa.io/images/emoji/unicode/1f600.png" } }, "required": [ "code", "aliases", "category", "url" ] }, "EmojiList": { "title": "EmojiList", "description": "絵文字のリストを表します", "type": "object", "properties": { "emojis": { "type": "array", "items": { "$ref": "#/components/schemas/Emoji" } } }, "required": [ "emojis" ] }, "Invitation": { "title": "Invitation", "description": "Emailによる招待を表します。", "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "招待したEメールアドレスです", "example": "foo@example.com" }, "code": { "type": "string", "description": "招待の識別子です\n\n削除時に利用します", "example": "mee93383edf699b525e01842d34078e28" }, "expires_at": { "type": "string", "format": "date-time", "description": "招待の有効期限です", "example": "2017-08-17T12:00:41+09:00" }, "url": { "type": "string", "format": "url", "description": "招待されたメンバーがチームへ参加する際に使うURLです", "example": "https://docs.esa.io/team/invitations/mee93383edf699b525e01842d34078e28/join" } }, "required": [ "email", "code", "expires_at", "url" ] }, "InvitationList": { "title": "InvitationList", "description": "Emailによる招待のリストを表します", "type": "object", "properties": { "invitations": { "type": "array", "items": { "$ref": "#/components/schemas/Invitation" } } }, "required": [ "invitations" ] }, "InviteBody": { "title": "InviteBody", "description": "招待したいメンバーのEメールアドレスを表します", "type": "object", "properties": { "member": { "title": "EmailsToInvite", "type": "object", "properties": { "emails": { "type": "array", "description": "招待したいメンバーのEメールアドレスです", "items": { "type": "string", "format": "email" }, "example": [ "foo@example.com", "bar@example.com" ] } }, "required": [ "emails" ] } }, "required": [ "member" ] }, "Member": { "title": "Member", "description": "チームのメンバーを表します。", "type": "object", "properties": { "myself": { "type": "boolean", "description": "自分自身であるかどうかのフラグです。", "example": true }, "name": { "type": "string", "description": "メンバーの名前です", "example": "Atsuo Fukaya" }, "screen_name": { "type": "string", "description": "メンバーのスクリーンネームです", "example": "fukayatsu" }, "icon": { "type": "string", "format": "url", "description": "メンバーのアイコンのURLです", "example": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png" }, "role": { "type": "string", "enum": [ "owner", "member" ], "description": "メンバーのロール(owner, member)です。", "example": "owner" }, "posts_count": { "type": "integer", "description": "チーム内でメンバーが作成した記事数です", "example": 222 }, "joined_at": { "type": "string", "format": "date-time", "description": "チームにメンバーが参加した日時です", "example": "2014-07-01T08:10:55+09:00" }, "last_accessed_at": { "type": "string", "format": "date-time", "description": "チームにメンバーがアクセスした最後の日時です", "example": "2019-12-27T16:23:23+09:00" }, "email": { "type": "string", "format": "email", "description": "メンバーのemailです。このフィールドは team の owner だけが取得可能です", "example": "fukayatsu@esa.io" } }, "required": [ "myself", "name", "screen_name", "icon", "role", "posts_count", "joined_at", "last_accessed_at" ] }, "NewComment": { "title": "NewComment", "type": "object", "properties": { "body_md": { "type": "string" }, "user": { "type": "string", "description": "コメントの投稿者\n\n- チームメンバーのscreen_nameもしくは \"esa_bot\" を指定することでコメントの投稿者を上書きすることができます。\n- このパラメータは **team の owner** だけ が使用することができます。", "example": "esa_bot" } }, "required": [ "body_md" ] }, "NewEmoji": { "title": "NewEmoji", "type": "object", "properties": { "code": { "type": "string", "description": "登録したい絵文字のコードです。絵文字を入力する際の両端の**:**を含めずに指定して下さい。", "example": "team_emoji" }, "origin_code": { "type": "string", "description": "既に登録されている絵文字に対するエイリアス絵文字を作成する際に指定して下さい。" }, "image": { "type": "string", "format": "binary", "description": "絵文字の画像データです。\n\n- BASE64でencodeしたStringを指定して下さい。\n- 新しい絵文字を作成する場合に指定して下さい。エイリアス絵文字を作成する際には不要です。\n- 画像の条件\n - 64KB以下\n - 128px x 128px以上\n - GIF or PNG", "example": "BASE64 String" } }, "required": [ "code" ] }, "NewPost": { "title": "NewPost", "description": "新たに投稿する記事を表します。", "type": "object", "properties": { "name": { "type": "string", "description": "記事のタイトル\n\n- タイトル自体に`#`を含めたい場合は`#`, `/`を含めたい場合は`/`へそれぞれ置換処理をお願いします。", "example": "hi!" }, "body_md": { "type": "string", "description": "Markdownで書かれた記事の本文です", "example": "# Getting Started" }, "tags": { "type": "array", "description": "記事に紐付けられたタグです。", "items": { "type": "string" }, "example": [ "api", "dev" ] }, "category": { "type": "string", "nullable": true, "description": "記事が属するカテゴリです\n\n設定されていない場合は`null`になります", "example": "日報/2015/05/09" }, "wip": { "type": "boolean", "description": "記事がWIP(Working In Progress)状態かどうかを表します。", "example": false, "default": true }, "message": { "type": "string", "description": "記事更新時の変更メモです。", "example": "Add Getting Started section" }, "user": { "type": "string", "description": "記事の投稿者\n\n- チームメンバーのscreen_nameもしくは \"esa_bot\" を指定することで記事の投稿者を上書きすることができます。\n- このパラメータは **team の owner** だけ が使用することができます。", "example": "esa_bot" }, "template_post_id": { "type": "integer", "description": "チーム内のテンプレート記事のID(URLのこの部分: /posts/**{id}**)を指定すると、そのテンプレートが適用された**name**と**body**を持つ記事を作成することが出来ます。", "example": 2 } }, "required": [ "name" ] }, "NewStar": { "title": "NewStar", "description": "新たにStarする内容を表します。", "type": "object", "properties": { "body": { "type": "string", "description": "引用Starの本文です。", "example": "foo bar" } } }, "PaginatedComments": { "title": "PaginatedComments", "type": "object", "properties": { "comments": { "type": "array", "description": "Commentのリスト", "items": { "$ref": "#/components/schemas/Comment" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "comments", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedMembers": { "title": "PaginatedMembers", "type": "object", "properties": { "members": { "type": "array", "description": "Memberのリスト", "items": { "$ref": "#/components/schemas/Member" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "members", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedPosts": { "title": "PaginatedPosts", "type": "object", "properties": { "posts": { "type": "array", "description": "Postのリスト", "items": { "$ref": "#/components/schemas/Post" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "posts", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedStargazers": { "title": "PaginatedStargazers", "type": "object", "properties": { "stargazers": { "type": "array", "description": "Stargazerのリスト", "items": { "$ref": "#/components/schemas/Stargazer" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "stargazers", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedTags": { "title": "PaginatedTags", "type": "object", "properties": { "tags": { "type": "array", "description": "Tagのリスト", "items": { "$ref": "#/components/schemas/Tag" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "tags", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedTeams": { "title": "PaginatedTeams", "type": "object", "properties": { "teams": { "type": "array", "description": "Teamのリスト", "items": { "$ref": "#/components/schemas/Team" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "teams", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "PaginatedWatchers": { "title": "PaginatedWatchers", "type": "object", "properties": { "watchers": { "type": "array", "description": "Watcherのリスト", "items": { "$ref": "#/components/schemas/Watcher" } }, "prev_page": { "type": "integer", "nullable": true, "description": "1つ前のpage番号。存在しない場合はnull", "example": null }, "next_page": { "type": "integer", "nullable": true, "description": "1つ先のpage番号。存在しない場合はnull", "example": 2 }, "total_count": { "type": "integer", "description": "リソースの総数", "example": 30 }, "page": { "type": "integer", "description": "現在のページ番号", "example": 1 }, "per_page": { "type": "integer", "description": "1ページあたりに含まれる要素数", "example": 20 }, "max_per_page": { "type": "integer", "description": "per_pageに指定可能な数の最大値", "example": 100 } }, "required": [ "watchers", "prev_page", "next_page", "total_count", "page", "per_page", "max_per_page" ] }, "Post": { "title": "Post", "description": "ユーザが作成した記事を表します。", "type": "object", "properties": { "number": { "type": "integer", "description": "チーム内で記事を特定するためのIDです", "example": 123 }, "name": { "type": "string", "description": "記事名です。タグやカテゴリー部分は含みません。", "example": "hi!" }, "full_name": { "type": "string", "description": "カテゴリとタグを含む、記事名です。", "example": "日報/2015/05/09/hi! #api #dev" }, "wip": { "type": "boolean", "description": "記事がWIP(Working In Progress)状態かどうかを表します。", "example": true }, "body_md": { "type": "string", "description": "Markdownで書かれた記事の本文です", "example": "# Getting Started" }, "body_html": { "type": "string", "description": "HTMLに変換された記事の本文です。", "example": "<h1>Getting Started</h1>" }, "created_at": { "type": "string", "format": "date-time", "description": "記事が作成された日時です", "example": "2014-05-10T12:08:55+09:00" }, "message": { "type": "string", "description": "記事更新時の変更メモです。", "example": "Add Getting Started section" }, "url": { "type": "string", "format": "url", "description": "記事のURLです。", "example": "https://docs.esa.io/posts/1" }, "updated_at": { "type": "string", "format": "date-time", "description": "記事が更新された日時です。", "example": "2014-05-11T19:21:00+09:00" }, "tags": { "type": "array", "description": "記事に紐付けられたタグです。", "items": { "type": "string" }, "example": [ "api", "dev" ] }, "category": { "type": "string", "nullable": true, "description": "記事が属するカテゴリです\n\n設定されていない場合は`null`になります", "example": "日報/2015/05/09" }, "revision_number": { "type": "integer", "description": "記事のリビジョン番号です。", "example": 47 }, "created_by": { "allOf": [ { "$ref": "#/components/schemas/User" } ], "description": "記事を作成したユーザを表します。" }, "updated_by": { "allOf": [ { "$ref": "#/components/schemas/User" } ], "description": "記事を最後に更新したユーザを表します。" }, "kind": { "type": "string", "enum": [ "stock", "flow" ], "description": "記事の種別を表します", "example": "flow" }, "comments_count": { "type": "integer", "description": "記事へのコメント数を表します", "example": 1 }, "tasks_count": { "type": "integer", "description": "記事中のタスクの総数を表します", "example": 1 }, "done_tasks_count": { "type": "integer", "description": "記事中の完了済みのタスクの総数を表します", "example": 1 }, "stargazers_count": { "type": "integer", "description": "記事にStarをしている人数を表します", "example": 1 }, "watchers_count": { "type": "integer", "description": "記事をWatchしている人数を表します", "example": 1 }, "star": { "type": "boolean", "description": "ユーザーが記事をStarしているかどうかを表します", "example": true }, "watch": { "type": "boolean", "description": "ユーザーが記事をWatchしているかどうかを表します", "example": true }, "sharing_urls": { "title": "SharingUrls", "type": "object", "description": "外部公開のURLです。外部公開が有効になっていない場合`null`になります。", "nullable": true, "properties": { "html": { "type": "string", "description": "HTMLページのURLです。", "example": "https://esa-pages.io/p/sharing/11666/posts/2/1c6cad6275b311d53dbe.html" }, "slides": { "type": "string", "description": "スライドページのURLです。", "example": "https://esa-pages.io/p/sharing/11666/posts/2/1c6cad6275b311d53dbe-slides.html" } }, "required": [ "html", "slides" ] }, "comments": { "type": "array", "description": "記事のコメント一覧を更新日の降順で返却します。", "items": { "$ref": "#/components/schemas/Comment" } }, "stargazers": { "type": "array", "description": "指定された記事にStarをしたユーザ一覧です", "items": { "$ref": "#/components/schemas/Stargazer" } } }, "required": [ "number", "name", "full_name", "wip", "body_md", "body_html", "created_at", "message", "kind", "comments_count", "tasks_count", "done_tasks_count", "url", "updated_at", "tags", "category", "revision_number", "created_by", "updated_by", "stargazers_count", "watchers_count", "star", "watch", "sharing_urls" ] }, "Stargazer": { "title": "Stargazer", "description": "記事にStarをしている人", "type": "object", "properties": { "created_at": { "type": "string", "format": "date-time", "description": "Starをした日時です。", "example": "2016-05-05T11:40:54+09:00" }, "body": { "type": "string", "nullable": true, "description": "引用Starの本文です。", "example": null }, "user": { "allOf": [ { "$ref": "#/components/schemas/User" } ], "description": "Starをしたユーザです。" } }, "required": [ "created_at", "body", "user" ] }, "Tag": { "title": "Tag", "description": "記事のタグ", "type": "object", "properties": { "name": { "type": "string", "description": "タグ名です。", "example": "markdown" }, "posts_count": { "type": "integer", "description": "タグ付けされた記事数です。", "example": 1 } }, "required": [ "name", "posts_count" ] }, "Team": { "title": "Team", "description": "esa上で所属しているチームを表します。", "type": "object", "properties": { "name": { "type": "string", "description": "チームを特定するための一意なIDです。サブドメインになります。", "example": "docs" }, "privacy": { "type": "string", "enum": [ "closed", "open" ], "description": "チームの公開範囲です。closed: \"チームメンバーだけが情報にアクセスできます。open: \"ShipItされた記事はインターネット上に公開されます。\"", "example": "closed" }, "description": { "type": "string", "description": "チームの説明です。登録がない場合には空文字列(\"\")になります。", "example": "esa.io official documents" }, "icon": { "type": "string", "format": "url", "description": "チームのアイコンです。", "example": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png" }, "url": { "type": "string", "format": "url", "description": "チームのURLです。", "example": "https://docs.esa.io/" } }, "required": [ "name", "privacy", "description", "icon", "url" ] }, "TeamStats": { "title": "TeamStats", "description": "チームの統計情報を表します。", "type": "object", "properties": { "members": { "type": "integer", "description": "チーム内のメンバーの総数です", "example": 20 }, "posts": { "type": "integer", "description": "チーム内の記事の総数です", "example": 1959 }, "posts_wip": { "type": "integer", "description": "チーム内の記事(wip)の総数です", "example": 59 }, "posts_shipped": { "type": "integer", "description": "チーム内の記事(shipped)の総数です", "example": 1900 }, "comments": { "type": "integer", "description": "チーム内の記事につけられたコメントの総数です", "example": 2695 }, "stars": { "type": "integer", "description": "チーム内の記事につけられたスターの総数です", "example": 3115 }, "daily_active_users": { "type": "integer", "description": "過去24時間以内に記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。", "example": 8 }, "weekly_active_users": { "type": "integer", "description": "過去7日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。", "example": 14 }, "monthly_active_users": { "type": "integer", "description": "過去30日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。", "example": 15 } }, "required": [ "members", "posts", "posts_wip", "posts_shipped", "comments", "stars", "daily_active_users", "weekly_active_users", "monthly_active_users" ] }, "updateCommentBody": { "title": "updateCommentBody", "type": "object", "properties": { "comment": { "title": "EditComment", "type": "object", "properties": { "body_md": { "type": "string" }, "user": { "type": "string", "description": "コメントの投稿者\n\n- チームメンバーのscreen_nameもしくは \"esa_bot\" を指定することでコメントの投稿者を上書きすることができます。\n- このパラメータは **team の owner** だけ が使用することができます。", "example": "esa_bot" } } } }, "required": [ "comment" ] }, "UpdatePostBody": { "title": "UpdatePostBody", "description": "記事の更新内容を表します。", "type": "object", "properties": { "post": { "$ref": "#/components/schemas/EditPost" } }, "required": [ "post" ] }, "User": { "title": "User", "description": "記事を作成したユーザ、記事を最後に更新したユーザ、コメントを作成したユーザ、スターをしたユーザなどを表します。", "type": "object", "properties": { "myself": { "type": "boolean", "description": "自分自身であるかどうかのフラグです。", "example": true }, "name": { "type": "string", "description": "ユーザ名です。", "example": "Atsuo Fukaya" }, "screen_name": { "type": "string", "description": "ユーザを一意に識別するIDです。", "example": "fukayatsu" }, "icon": { "type": "string", "format": "url", "description": "ユーザのアイコンのURLです。", "example": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png" } }, "required": [ "myself", "name", "screen_name", "icon" ] }, "Watcher": { "title": "Watcher", "description": "記事をWatchしているユーザーを表します。", "type": "object", "properties": { "created_at": { "type": "string", "format": "date-time", "description": "Watchをした日時です。", "example": "2014-05-10T12:08:55+09:00" }, "user": { "allOf": [ { "$ref": "#/components/schemas/User" } ], "description": "Watchをしたユーザです。" } }, "required": [ "created_at", "user" ] } }, "headers": { "X-RateLimit-Limit": { "description": "ユーザ毎に15分間に75リクエストまで受け付けます。", "schema": { "type": "integer" }, "required": true, "example": 75 }, "X-RateLimit-Remaining": { "description": "残りのリクエスト数", "schema": { "type": "integer" }, "required": true, "example": 73 }, "X-RateLimit-Reset": { "description": "利用制限が解除される日時のUnixタイムスタンプ", "schema": { "type": "integer" }, "required": true, "example": 1491543000 } } }, "tags": [ { "name": "esa", "description": "", "x-displayName": " ", "x-traitTag": true }, { "name": "category", "description": "記事のカテゴリの管理を行うAPIです。", "x-displayName": "カテゴリ" }, { "name": "comment", "description": "記事のコメントの投稿や取得などを行うAPIです。", "x-displayName": "コメント" }, { "name": "emoji", "description": "絵文字の管理を行うAPIです。", "x-displayName": "絵文字" }, { "name": "invitation", "description": "メンバーの招待を行うAPIです。", "x-displayName": "招待" }, { "name": "member", "description": "メンバーの管理を行うAPIです。", "x-displayName": "メンバー" }, { "name": "post", "description": "記事の投稿や取得などを行うAPIです。", "x-displayName": "記事" }, { "name": "star", "description": "記事やコメントのStarに関するAPIです。", "x-displayName": "Star" }, { "name": "tag", "description": "記事のタグに関するAPIです。", "x-displayName": "タグ" }, { "name": "team", "description": "所属するチームに関するAPIです。", "x-displayName": "チーム" }, { "name": "user", "description": "認証中のユーザーを参照するAPIです。", "x-displayName": "ユーザー" }, { "name": "watch", "description": "記事のWatchに関するAPIです。", "x-displayName": "Watch" } ], "servers": [ { "url": "https://api.esa.io/v1" } ], "externalDocs": { "description": "esa API v1公式ドキュメント", "url": "https://docs.esa.io/posts/102" }, "security": [ { "AccessTokenHeader": [ "read" ] }, { "AccessTokenQueryParam": [ "read" ] }, { "OAuth2": [ "read" ] } ] }
- 参考にさせていただいた仕様やJsonSchemaそのままでは使えなかったので、自作して埋め込み
会話データをGPTの会話モデルの改善に使うのを外す
- Actions を追加することで表示されるので、チェックを外す。
- Use conversation data in your GPT to improve our models : 会話データをGPTの会話モデルの改善に使用する
- 社内用の会話が入る可能性があるのでオフ
- ※この改善の対象がカスタムGPT内だけなのか、GPT全体なのかが(私が理解しておらず)不明なためオフとしています。
5. 動作確認をしてみる
Preview で質問を投げてみる
APIの初回利用時に信用できるドメインとして許可する Always allow をクリック
するとエラーが返ってくる
- esaに問い合わせた内容はあるはずですが、エラーを返してしまう
- ポイント : エラーの内容を確認してみる
- Preview ではレスポンス内容が確認できる
- レスポンス結果が *ResponseTooLargeError** と言われているようです。
- つまり「esaに問い合わせた結果が大きすぎる!」ようです。
- 1回の問い合わせ結果を絞った方が良さそう だと判明しました。
6. カスタムGPTに細かくesaの使い方を教える
- 前項で「問い合わせる内容によってレスポンスが大きすぎてエラーになる」事がわかりました。
- 当項では、「カスタムGPT自身でレスポンス量を制御する」ように教えていこうと思います。
Instructions にAPIの使い方について教えてみると、工夫して使ってくれるようになります。
以下を追記してみました。
- `ResponseTooLargeError` が返却された時 - クエリの `per_page` を減らして再リクエストしてください - `per_page` の最小は `1` です - 最小の `1` までは再リクエストを試みてください - `1` でもエラーとなる場合は回答不可として回答してください
もう一度質問を投げてみます
成功です!うまくいきましたので中身を少し詳しくみてみます。
※一部マスキングしています
- 1度目のリクエストでは
per_page
の設定なし(デフォルト20ページ)で問い合わせを行い失敗しています。 - 続いて2度目のリクエストでは、目論見通り
per_page
を10ページ(半分にしてくれました)にしてますが、これも失敗しています。 - 続いて3度目のリクエストでは、
per_page
を1ページ(最小)にして、ついに取得に成功しました! - 出してくれている内容も想定通りのものが出てくれました。
- 1度目のリクエストでは
GPTsの所感
- esaのAPI側で制御できるレスポンスパラメータが少なく、ちょっと問い合わせると直ぐに
ResponseTooLargeError
エラーとなってしまった。body_html
が大きくなりがちだったので、body
はbody_md
だけ取得したい(願望)- 今後、esa側のアップデートで制御できるレスポンスパラメータが増えることを期待しています🙏
- ChatGPT側の128k制限ももっと拡張される事を期待しています!
- 特定の資料を教える時は工夫が必要でした。
- ADRの記事はesaの中でも特定のパスに保管していたので、その情報を Instructions で教えました。
今後の展望
- 開発アシスタントにADR以外にも沢山のことを教えたい。(ドキュメントの整備が完了次第…!)
- 開発標準
- 実装ガイドライン
- GitHubと連携し、PRを出したら「開発アシスタントが自動レビュー」をしたい
- 現時点ではカスタムGPTを起動し「開発アシスタントを呼ぶ」を人間としないとサポートしてくれない。
- ここまでできると、立派な仕組み化と言えそうです。
- 他にもこんな事に使えそう
- オンボーディングに特化したGPT
- オンボーディング資料を常に最新化し続けるのは難しい。。。
- 普段メンバーが使ってる資料を、オンボーディング用に読み直しつつ、順番に読む資料を提供してくれるなど
- プログラミング言語やフレームワークのドキュメントを読ませてアドバイザーになってもらう
- 「ドキュメントにない(自分が作りたいものの)チュートリアル」を作ってもらいたいですね。
- 例えばUnityのようなゲーム開発ツールで「都市開発シミュレーションゲームのチュートリアル」などコアなジャンルのチュートリアルを作ってもらえたりすると個人的にはすごく嬉しい。
- オンボーディングに特化したGPT
さいごに
この記事を通して、GPTsの可能性と実際の使用例をお伝えできたと思います。
皆さんの日々の作業やチームの運営に新たな可能性をもたらすことを願っています!
エンジニア募集中!
ホワイトプラスはリネットという宅配クリーニングのサービスを開発・運営している会社です。
www.lenet.jp
向上心の強いメンバーと一緒に成長したい方はぜひご応募ください。カジュアル面談も可能ですので、お気軽に! www.wh-plus.co.jp