【ChatGPT】GPTs + 外部API連携 + Supabase　自然言語でDBの読み書きをするGPTsを作った

## はじめに

DBに外部API連携をし、自然言語でDBの読み書きをする、GPTsアプリケーションを作りました。

DBは、オンライン上でRDBMSとして使用できるSupabase、
Supabase側でAPIで起動するFunction（ストアドプロシージャのようなもの）を作り、
GPTsから、外部API連携の機能「Actions」でそれらFunctionを自然言語だけで駆動し、DBの読み書きをする、というものです。
※このGPTsは非公開です。
Supabase
https://supabase.com/

### 対象とする読者
- GPTs、GPT Builderの基本や操作手順をある程度知っている人（本稿では、ここら辺の説明はありません）
- GPTsから外部API連携することに興味がある人
- 自然言語だけで何かを駆動させることに興味がある人
- RDBMSの基本操作ができる人（Supabaseは初見殺しのUIではないのでご安心を）

### 本稿に書いてあること

1. 環境の構築
・DBの構築　SupabaseでのテーブルやFunctionの作成
・GPT BuilderのInstructionsなどの設定
・GPT Builderの外部API連携「Actions」での認証設定
・GPTsから外部API連携するためのOpenAPI schemaの作成手順
を記載しています。
**やることを全て掲載**していますので、自分なりのアレンジを加えることができます。
<font color="red">**この環境の構築は長いので、構築に興味が無い方は、次の「[実行](#実行実際に使ってみる)」だけ見ることも可能です。**</font>

2. 実行
実際にGPTsに自然言語のプロンプトを投入して、
・その自然言語からどのようなデータがDBに読み書きされるか
・API経由でDBの読み書きが行われたこと
を見ることができます。

3. 解説
「自然言語でDBの読み書き」の核となる「Function calling」について。
・GPTsにおけるFunction callingの仕組み
・それが本実装においてどう働いて、「自然言語でDBの読み書き」を実現しているか
を説明しています。

### なぜDBにSupabaseを選んだか

**Supabaseでは、個々のテーブルやFunctionから、API認証付き処理のcurlコマンドをその場でいきなり出力することができます。**
後は、このcurlコマンドからOpenAPIのschemaを作成し、GPT Builderの「Actions」にこのschemaを設定するだけです。
![SupabaseのFunctionのcurlコマンド出力イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/f461cd01-5ef6-9e08-4e95-a0ae14f0c593.png)
DB外部から呼びたいFunctionのcurlコマンドを容易に取得できるのはとてもありがたく、これでSupabaseを選びました。

## 「自然言語でDB読み書き」GPTsアプリケーションの概要

Supabaseへ外部API連携をして自然言語でDBの読み書きをする、GPTsアプリケーションの概要を記します。

ある業務を想定し、その業務で使われることを考えて作りました。

### 想定する業務

今、ある会社の、顧客問い合わせ対応担当部署の業務を考えます。

「問い合わせ対応DB」なるものがあるとします。
ここには、
顧客からの問い合わせの概要と、
それに対する社の対応の概要が、
1セット1データで格納されているとします。

「問い合わせ対応DB」にある「問い合わせ対応データ」1件の概念図
![「問い合わせ対応データ」概念図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/49b52e11-a4d0-9c77-9788-7b881e5e43d2.png)

問い合わせ対応担当者がやることは、
- 顧客からの問い合わせがあれば、その問い合わせと1次対応それぞれの概要を「問い合わせ対応DB」に新規登録して報告
- 「問い合わせ対応DB」の検索
- 事後対応を行ったら、その事後対応の概要で「問い合わせ対応DB」を更新して報告

**これらを全て自然言語で行う**ことを想定しています。

![想定する業務_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/073dfe60-5846-556a-ee6f-e9d602c354dc.png)

### 全体構成

問い合わせ対応の新規登録、検索、更新のそれぞれに対し、別個のGPTsを作りました。
問い合わせ対応DB（Supabase）内にも同様に、新規登録、検索、更新のそれぞれに対し、別個のFunctionを作りました。
ある用途のGPTs1個と、同じ用途のFunctionが、1：1に対応しています。
下にある「全体概要図」を参照のこと。

![全体構成_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/a3538d93-5379-f08b-abbc-a7987b24757d.png)

#### GPTs

| GPTs名|GPTsロゴ| 　　　　　　　役割 | 対応するSupabaseのFunction（GPTsのAPI連携先） |
|:-----------:|:-----------:|:-----------|:------------:|
|問い合わせ対応DBに新規登録|![新規登録_ロゴ._小png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/ea7d8a38-e297-c4e8-0368-861ec11e95d0.png)|顧客から問い合わせがあった際、その問い合わせの概要と、その問い合わせに対する1次対応の概要をDBに記録する。<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|addinquiry|
|問い合わせ対応DBの検索|![検索_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/286c7cfe-3193-fbc0-5904-ae2ee9dc6888.png)|問い合わせ対応データを問い合わせ対応DBから収集する。<BR>**その検索条件は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|getinquiries|
|問い合わせ対応DBを更新|![更新_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/b08b3189-1b80-fe93-0464-c8fc4a790ea3.png)|顧客からの問い合わせに対する1次対応後、事後対応を行い進捗が生じれば、その事後対応の概要でDBを更新する。<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|updateinquiry|

<details><summary>＜補足＞なぜ、用途ごとにGPTsを分割したのか</summary>

確かに、新規登録、検索、更新といった3つの用途を１つのGPTsで受け持った方が、プロンプトを入力する問い合わせ対応担当者の操作は楽だと思います。
が、以下の理由で、敢えてGPTsを用途毎に分けました。

- シンプルさ
この3つの用途は、それぞれ大きく異なり、DB側が要求する情報も大きく異なります。
これを１つのGPTsにまとめてしまうと、
**GPT BuilderのInstructionsが複雑になってしまう。**
どの用途なのかGPTsに明確にわかるようなプロンプトにする必要があり、プロンプトの簡便さを失う→**プロンプトを入力する問い合わせ対応担当者の負担増につながってしまう**
ということになります。

- GPTsのメンション機能によりユーザー操作負荷軽減
最近リリースされたGPTsのメンション機能を使用すれば、あるGPTsのスレッドから、いきなり他のGPTsを呼べるので、**複数のGPTsを同時に使用することのユーザー操作負荷の軽減ができます。**
なので、GPTsが用途ごとに分かれていても、それほど操作は苦ではないだろう、と考えました。
あるGPTsのスレッドで「@」を入力し、別GPTsの候補一覧が表示されたところ：
![GPTsメンション機能_検索GPTsを選択しようとしている_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/95671760-f4e9-1586-e13b-afb803fb102f.png)
　　　　　　　　↓　GPTs「問い合わせ対応DBに新規登録」を選択
![GPTsメンション機能_話している相手_新規登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/74fe0417-56bf-e4cb-a31c-d9e47c8d1c3e.png)

</details>

## 【構築】実際に作ってみる

実際に環境構築をします。

大きく、
・問い合わせ対応DB（Supabase）側の構築
・GPTsの作成
の2つに分かれます。

### 問い合わせ対応DB（Supabase）側の構築

<!-- <details><summary><b>【実装したい人限定】問い合わせ対応DB（Supabase）側の構築の全貌を見る</b></summary> -->
<!-- DB構築全体の折りたたみのはじめ -->

:::note warn
少し長いので、構築に興味が無い方は、ずっと下の「[実行](#実行実際に使ってみる)」まで進んでください。
:::

:::note warn
実際にこの流れに沿って構築する場合、Supabaseで、プロジェクトの作成とAPIキーの取得までやっておく必要があります。
https://supabase.com/
:::

#### 顧客データテーブル「CUSTOMER」の作成

<details><summary>詳細を見る</summary>

##### このテーブルにあるもの

顧客データテーブルは、顧客データを持ちます。
その内容は、主に、
顧客IDと顧客コード、
顧客名、
その他住所などの顧客の情報
です。

##### テーブルの定義

|列名|型|NULL許容|既定値|　　　　　　　　意味|
|:-----------:|:-----------:|:-----------:|:-----------:|:------------|
|ID|bigint|×|自動連番|主キー。顧客のID。システム上の数値でユーザーは関知せず。|
|CODE|varchar|×|NULL|顧客のコード。ユーザーはこれで顧客を一意認識する。|
|NAME|varchar|×|空文字|顧客の名前。|
|ADDRESS|varchar|×|空文字|顧客の住所。|
|TEL|varchar|〇|空文字|顧客の電話番号。|
|CREATED_AT|timestamptz|×|now()|この行が生成された日時。|

##### テーブルの作成

このテーブル定義に従って、GUIのTable Editorで地道にテーブルを作ってもいいですが、
SQL Editorで、以下のクエリを実行したほうが楽です。

```SQL:顧客データテーブル「CUSTOMER」作成クエリ
create table
  public."CUSTOMER" (
    "ID" bigint generated by default as identity,
    "CODE" character varying not null,
    "NAME" character varying not null default ''::character varying,
    "ADDRESS" character varying not null default ''::character varying,
    "TEL" character varying null,
    "CREATED_AT" timestamp with time zone not null default now(),
    constraint CUSTOMER_pkey primary key ("ID"),
    constraint CUSTOMER_CODE_key unique ("CODE"),
    constraint CUSTOMER_id_key unique ("ID")
  ) tablespace pg_default;
```

</details>

#### 問い合わせ対応データテーブル「INQUIRY」の作成

<details><summary>詳細を見る</summary>

##### このテーブルにあるもの

問い合わせ対応データテーブルは、問い合わせ対応データを持ちます。
その内容は、主に、
問い合わせID、
顧客からの問い合わせ概要、
それに対する会社の対応概要、
対応が完了したか未完了か、
問い合わせ発生日時と対応完了日時
です。

##### テーブルの定義

|列名|型|NULL許容|既定値|　　　　　　　　意味|
|:-----------:|:-----------:|:-----------:|:-----------:|:------------|
|ID|bigint|×|自動連番|主キー。問い合わせ対応のID。システム上の数値でもあり、ユーザーもこの数値を「問い合わせID」として一意認識する。|
|ID_CUSTOMER|bigint|×|NULL|問い合わせをした顧客のID。CUSTOMER.IDに対する外部制約。|
|INQUIRY_SUMMARY|varchar|×|空文字|問い合わせの概要。|
|RESPONSE_SUMMARY|varchar|×|空文字|問い合わせ対応の概要。|
|IS_COMPLETED|boolean|×|false|問い合わせ対応が完了したか未完了か。|
|CREATED_AT|timestamptz|×|now()|問い合わせが発生した日時。この行が生成された日時。|
|COMPLETED_AT|timestamptz|〇|NULL|問い合わせ対応が完了した日時。|

##### テーブルの作成

同様に、このテーブル定義に従って、GUIのTable Editorで地道にテーブルを作ってもいいですが、
SQL Editorで、以下のクエリを実行したほうが楽です。

```SQL:問い合わせ対応データテーブル「INQUIRY」作成クエリ
create table
  public."INQUIRY" (
    "ID" bigint generated by default as identity,
    "ID_CUSTOMER" bigint not null,
    "INQUIRY_SUMMARY" character varying not null default ''::character varying,
    "RESPONSE_SUMMARY" character varying not null default ''::character varying,
    "IS_COMPLETED" boolean not null default false,
    "CREATED_AT" timestamp with time zone not null default now(),
    "COMPLETED_AT" timestamp with time zone null,
    constraint INQUIRY_pkey primary key ("ID"),
    constraint INQUIRY_ID_CUSTOMER_fkey foreign key ("ID_CUSTOMER") references "CUSTOMER" ("ID")
  ) tablespace pg_default;
```

#### 「CUSTOMER」「INQUIRY」両テーブルの関係

INQUIRYのID_CUSTOMERは、CUSTOMERのIDです。外部キー制約です。
![CUSTOMERとINQUIRYの関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/9fed70e9-bd31-f8a1-9e3b-2141b149bfc5.png)

</details>

#### 問い合わせ対応データ新規登録Function「addinquiry」の作成

<details><summary>詳細を見る</summary>

##### Function「addinquiry」の役割

GPTs「問い合わせ対応DBに新規登録」からAPI経由で受け取った、顧客コード、問い合わせ概要、1次対応概要、対応完了/未完了　をもとに、
問い合わせ対応データテーブル「INQUIRY」にレコードを追加します。

##### Functionが受け取るパラメーター

|パラメーター名|型|必須/任意|　　　　　　意味|
|:-----------:|:-----------:|:-----------:|:------------|
|code_customer|varchar|必須|問い合わせをした顧客のコード。|
|inquiry_summary|varchar|必須|問い合わせ概要。|
|response_summary|varchar|必須|1次対応概要。|
|is_completed|boolean|必須|対応完了/未完了。|

##### Functionの戻り値

今しがたテーブル「INQUIRY」に追加したレコードの内容をもとに、
問い合わせ対応データを形成し、それを呼び出し元に返します。

|変数名|型|　　　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|id_inquiry_out|bigint|問い合わせID。問い合わせ対応データの一意識別子。|
|code_customer_out|varchar|問い合わせをした顧客のコード。|
|name_customer_out|varchar|問い合わせをした顧客の名前。|
|inquiry_summary_out|varchar|問い合わせ概要。|
|response_summary_out|varchar|1次対応概要。|
|is_completed_out|boolean|対応完了/未完了。|
|created_at_out_str|text|問い合わせ発生年月日と時分の文字列。このレコードの作成日時。|
|completed_at_out_str|text|問い合わせ対応完了年月日と時分の文字列。未完了ならNULL。|

##### Functionの作成

以下のクエリをSQL Editorで実行します。Function「addinquiry」が作成されます。
```SQL:問い合わせ対応データ新規登録Function「addinquiry」作成クエリ
CREATE OR REPLACE FUNCTION public.addinquiry(
    code_customer character varying,
    inquiry_summary character varying,
    response_summary character varying,
    is_completed boolean
)
RETURNS TABLE(
    id_inquiry_out bigint,
    code_customer_out character varying,
    name_customer_out character varying,
    inquiry_summary_out character varying,
    response_summary_out character varying,
    is_completed_out boolean,
    created_at_out_str text,
    completed_at_out_str text
)
LANGUAGE plpgsql
AS $$
DECLARE
    id_customer bigint;
    returned_id_inquiry bigint;
    returned_customer_name character varying;
    returned_created_at timestamp with time zone;
    returned_completed_at timestamp with time zone;
BEGIN
    -- 顧客IDと名前の検索
    SELECT "ID", "NAME" INTO id_customer, returned_customer_name FROM public."CUSTOMER" WHERE "CODE" = code_customer;

    -- 顧客が存在しない場合のエラー処理
    IF id_customer IS NULL THEN
        RAISE EXCEPTION 'Customer not found for code: %', code_customer;
    END IF;

    -- INQUIRYテーブルへの行の挿入と、IDとCREATED_AT、COMPLETED_ATの取得
    IF is_completed = true THEN
        INSERT INTO public."INQUIRY" ("ID_CUSTOMER", "INQUIRY_SUMMARY", "RESPONSE_SUMMARY", "IS_COMPLETED", "CREATED_AT", "COMPLETED_AT")
        VALUES (id_customer, inquiry_summary, response_summary, is_completed, NOW(), NOW())
        RETURNING "ID", "CREATED_AT", "COMPLETED_AT" INTO returned_id_inquiry, returned_created_at, returned_completed_at;
    ELSE
        INSERT INTO public."INQUIRY" ("ID_CUSTOMER", "INQUIRY_SUMMARY", "RESPONSE_SUMMARY", "IS_COMPLETED", "CREATED_AT")
        VALUES (id_customer, inquiry_summary, response_summary, is_completed, NOW())
        RETURNING "ID", "CREATED_AT" INTO returned_id_inquiry, returned_created_at;
        returned_completed_at := NULL;
    END IF;

    -- 戻り値
    RETURN QUERY SELECT
        returned_id_inquiry,
        code_customer,
        returned_customer_name,
        inquiry_summary,
        response_summary,
        is_completed,
        TO_CHAR(returned_created_at, 'YYYY-MM-DD HH24:MI') AS created_at_out_str,
        CASE
            WHEN returned_completed_at IS NOT NULL THEN TO_CHAR(returned_completed_at, 'YYYY-MM-DD HH24:MI')
            ELSE NULL
        END AS completed_at_out_str;
END;
$$;
```
</details>

#### 問い合わせ対応データ検索Function「getinquiries」の作成

<details><summary>詳細を見る</summary>

##### Function「getinquiries」の役割

GPTs「問い合わせ対応DBの検索」からAPI経由で受け取った様々な検索条件をもとに、問い合わせ対応データを収集して返します。

##### Functionが受け取るパラメーター

検索条件として使用されます。
|パラメーター名|型|必須/任意|　　　　　　　　　　意味|
|:-----------:|:-----------:|:-----------:|:------------|
|id_inquiry|bigint|任意|問い合わせID。問い合わせ対応データの一意識別子。|
|code_customer|varchar|任意|問い合わせをした顧客のコード。|
|name_customer|varchar|任意|問い合わせをした顧客の名前。|
|is_completed|boolean|任意|対応完了/未完了。|
|created_at_begin|date|任意|問い合わせ発生日の期間検索の”以後（from）”の年月日。|
|created_at_end|date|任意|問い合わせ発生日の期間検索の”以前（to）”の年月日。|
|completed_at_begin|date|任意|問い合わせ対応完了日の期間検索の”以後（from）”の年月日。|
|completed_at_end|date|任意|問い合わせ対応完了日の期間検索の”以前（to）”の年月日。|
|keyword|varchar|任意|キーワード検索のキーワード。問い合わせ概要（INQUIRY.INQUIRY_SUMMARY）と対応概要（INQUIRY.RESPONSE_SUMMARY）をキーワード検索の対象とする。|

##### Functionの戻り値

検索条件に合致する問い合わせ対応データ（群）を返します。
以下は、問い合わせ対応データ1個分です。

|変数名|型|　　　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|id_inquiry_out|bigint|問い合わせID。問い合わせ対応データの一意識別子。|
|code_customer_out|varchar|問い合わせをした顧客のコード。|
|name_customer_out|varchar|問い合わせをした顧客の名前。|
|inquiry_summary_out|varchar|問い合わせ概要。|
|response_summary_out|varchar|1次対応概要。|
|is_completed_out|boolean|対応完了/未完了。|
|created_at_out_str|text|問い合わせ発生年月日と時分の文字列。このレコードの作成日時。|
|completed_at_out_str|text|問い合わせ対応完了年月日と時分の文字列。未完了ならNULL。|

##### Functionの作成

以下のクエリをSQL Editorで実行します。Function「getinquiries」が作成されます。
```SQL:問い合わせ対応データ検索Function「getinquiries」作成クエリ
CREATE OR REPLACE FUNCTION public.getinquiries(
    id_inquiry bigint DEFAULT NULL,
    code_customer character varying DEFAULT NULL,
    name_customer character varying DEFAULT NULL,
    is_completed boolean DEFAULT NULL,
    created_at_begin date DEFAULT NULL,
    created_at_end date DEFAULT NULL,
    completed_at_begin date DEFAULT NULL,
    completed_at_end date DEFAULT NULL,
    keyword character varying DEFAULT NULL
)
RETURNS TABLE(
    id_inquiry_out bigint,
    code_customer_out character varying,
    name_customer_out character varying,
    inquiry_summary_out character varying,
    response_summary_out character varying,
    is_completed_out boolean,
    created_at_out_str text,
    completed_at_out_str text
)
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN QUERY
    SELECT
        i."ID",
        c."CODE",
        c."NAME",
        i."INQUIRY_SUMMARY",
        i."RESPONSE_SUMMARY",
        i."IS_COMPLETED",
        TO_CHAR(i."CREATED_AT", 'YYYY-MM-DD HH24:MI') AS created_at_out_str,
        TO_CHAR(i."COMPLETED_AT", 'YYYY-MM-DD HH24:MI') AS completed_at_out_str
    FROM
        public."INQUIRY" i
    INNER JOIN public."CUSTOMER" c ON i."ID_CUSTOMER" = c."ID"
    WHERE
        (id_inquiry IS NULL OR i."ID" = id_inquiry)
        AND (code_customer IS NULL OR c."CODE" = code_customer)
        AND (name_customer IS NULL OR c."NAME" ILIKE '%' || name_customer || '%')
        AND (is_completed IS NULL OR i."IS_COMPLETED" = is_completed)
        AND (created_at_begin IS NULL OR i."CREATED_AT" >= created_at_begin)
        AND (created_at_end IS NULL OR i."CREATED_AT" < created_at_end + INTERVAL '1 day')
        AND (completed_at_begin IS NULL OR i."COMPLETED_AT" >= completed_at_begin)
        AND (completed_at_end IS NULL OR i."COMPLETED_AT" < completed_at_end + INTERVAL '1 day')
        AND (keyword IS NULL OR (i."INQUIRY_SUMMARY" ILIKE '%' || keyword || '%' OR i."RESPONSE_SUMMARY" ILIKE '%' || keyword || '%'))
    ORDER BY i."CREATED_AT" ASC;
END;
$$;
```
</details>

#### 問い合わせ対応データ更新Function「updateinquiry」の作成

<details><summary>詳細を見る</summary>

##### Function「updateinquiry」の役割

GPTs「問い合わせ対応DBを更新」からAPI経由で受け取った、問い合わせID、事後対応概要、対応完了/未完了　をもとに、
その問い合わせIDをキーにして、問い合わせ対応データテーブル「INQUIRY」の該当するレコードを更新します。

##### Functionが受け取るパラメーター

|パラメーター名|型|必須/任意|　　　　　　　　　　意味|
|:-----------:|:-----------:|:-----------:|:------------|
|id_inquiry|bigint|必須|問い合わせID。更新対象の問い合わせ対応データレコードの一意識別子。|
|response_summary|varchar|任意|事後対応概要。|
|is_completed|boolean|任意|対応完了/未完了。|

##### Functionの戻り値

今しがたテーブル「INQUIRY」中で更新したレコードの内容をもとに、
問い合わせ対応データを形成し、それを呼び出し元に返します。

|変数名|型|　　　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|id_inquiry_out|bigint|問い合わせID。問い合わせ対応データの一意識別子。|
|code_customer_out|varchar|問い合わせをした顧客のコード。|
|name_customer_out|varchar|問い合わせをした顧客の名前。|
|inquiry_summary_out|varchar|問い合わせ概要。|
|response_summary_out|varchar|事後対応概要。|
|is_completed_out|boolean|対応完了/未完了。|
|created_at_out_str|text|問い合わせ発生年月日と時分の文字列。このレコードの作成日時。|
|completed_at_out_str|text|問い合わせ対応完了年月日と時分の文字列。未完了ならNULL。|

##### Functionの作成

以下のクエリをSQL Editorで実行します。Function「updateinquiry」が作成されます。
```SQL:問い合わせ対応データ更新Function「updateinquiry」作成クエリ
CREATE OR REPLACE FUNCTION public.updateinquiry(
    id_inquiry bigint,
    response_summary character varying DEFAULT NULL,
    is_completed boolean DEFAULT NULL
)
RETURNS TABLE(
    id_inquiry_out bigint,
    code_customer_out character varying,
    name_customer_out character varying,
    inquiry_summary_out character varying,
    response_summary_out character varying,
    is_completed_out boolean,
    created_at_out_str text,
    completed_at_out_str text
)
LANGUAGE plpgsql
AS $$
DECLARE
    row_count integer;
BEGIN
    -- INQUIRYテーブルの更新
    UPDATE public."INQUIRY"
    SET
        "RESPONSE_SUMMARY" = COALESCE(response_summary, "RESPONSE_SUMMARY"),
        "IS_COMPLETED" = COALESCE(is_completed, "IS_COMPLETED"),
        "COMPLETED_AT" = CASE 
                            WHEN is_completed IS NOT NULL THEN 
                                CASE 
                                    WHEN is_completed = true THEN now() 
                                    ELSE NULL 
                                END
                            ELSE "COMPLETED_AT" -- is_completed が NULL の場合、COMPLETED_AT 列は変更しない
                         END
    WHERE "ID" = id_inquiry;

    GET DIAGNOSTICS row_count = ROW_COUNT;

    -- 更新された行がない場合のエラー処理
    IF row_count = 0 THEN
        RAISE EXCEPTION 'No inquiry data found with ID: %', id_inquiry;
    END IF;

    -- 更新した行の戻り値
    RETURN QUERY
    SELECT
        i."ID",
        c."CODE",
        c."NAME",
        i."INQUIRY_SUMMARY",
        i."RESPONSE_SUMMARY",
        i."IS_COMPLETED",
        TO_CHAR(i."CREATED_AT", 'YYYY-MM-DD HH24:MI') AS created_at_out_str,
        CASE WHEN i."COMPLETED_AT" IS NOT NULL THEN TO_CHAR(i."COMPLETED_AT", 'YYYY-MM-DD HH24:MI') ELSE NULL END AS completed_at_out_str
    FROM
        public."INQUIRY" i
    INNER JOIN public."CUSTOMER" c ON i."ID_CUSTOMER" = c."ID"
    WHERE i."ID" = id_inquiry;
END;
$$;
```
</details>

これで、問い合わせ対応DB（Supabase）側の構築が終わりました。

<!-- DB構築全体の折りたたみの終わり -->
<!-- </details> -->

### GPTsの作成
<!-- <details><summary><b>【実装したい人限定】GPTsの作成の全貌を見る</b></summary> -->
<!-- GPTs作成全体の折りたたみのはじめ -->

:::note warn
ここも少し長いので、構築に興味が無い方は、ずっと下の「[実行](#実行実際に使ってみる)」まで進んでください。
:::

:::note warn
<font color="red">GPTsやGPT Builderの基本や操作手順といったことは、本稿の範囲外</font>としています。具体的な設定値やその意味、そうする理由のみを記載しています。
:::

ここでは、
問い合わせ対応データの新規登録を受け持つGPTs「問い合わせ対応DBに新規登録」
問い合わせ対応データの検索を受け持つGPTs「問い合わせ対応DBを検索」
問い合わせ対応データの更新を受け持つGPTs「問い合わせ対応DBを更新」
の3つのGPTsを作成します。

#### 3つのGPTsに共通の設定

<details><summary>詳細を見る</summary>

GPTsを実際に作る前に、全3つのGPTsに共通の設定を切り出して、説明します。

##### GPT Builderの設定（Actions以外）

|項目|　　　設定内容|
|:-----------:|:------------|
|Name|（各GPTs毎に設定）|
|Capabilities|全てOFF。（お好みで）|
|Instructions|（各GPTs毎に設定）|
|Knowledge|無し。（お好みで）|

##### GPT Builderの設定（Actions）

|項目|　　　　　　　　　 　設定内容|
|:-----------:|:------------|
|Authentication（認証設定）|別記。|
|OpenAPIのschema|（各GPTs毎に設定）|
|OpenAPIのschemaの中<br>「servers」の「url」のドメイン部分|Supabaseの「Project Settings」にある「Reference ID」。<br>以下の画像参照。|

OpenAPIのschemaの中　「servers」の「url」のドメイン部分：
![GPTBuilder_Action_schema中のドメイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/ce136da2-518d-7d8d-4ec3-c5ce64105f85.png)

##### GPT Builderの設定（Actions）：Authentication（認証設定）

以下の画像の通りにしてください。
Authentication（認証設定）：
![GPTBuilder_Action_認証設定ダイアログ_説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/7211223c-9287-99d1-699a-ac6c05dd2c0a.png)

|項目|設定内容|
|:-----------:|:-----------:|
|Authentication Type|API Key|
|API Key|（取得したSupabaseのAPIキー文字列）|
|Auth Type|Custom|
|Custom Header Name|apikey|

Authentication（認証設定）の「Auth Type」でのこの設定の意味について説明します。

Supabaseから出力されるAPI認証付き処理curlコマンドの
**「-H "apikey: SUPABASE_KEY" \」**
に注目します。
これは、
**「apikey」という名前のカスタムHTTPヘッダ**を設け、その値としてSUPABASE_KEY（あなたのSupabaseのAPIキー文字列）を送信する、
という意味です。
Authenticationの「Custom Header Name」は、そのカスタムHTTPヘッダの名前をセットするところです。
curlコマンドと、認証設定と、schemaの関連箇所の関係を、画像で記します。
![GPTBuilder_Action_Authentication_「apikey」.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/6c820e3d-e4e8-3813-e293-3cae51395e22.png)

##### OpenAPI schemaの作成手順（Supabaseの場合）

筆者は、以下の手順で行いました。
以下は、主にGPTs「問い合わせ対応DBに新規登録」を例にしていますが、全3つのGPTsでやることは同じです。

１．API認証付き処理のcurlコマンドの取得

まず、OpenAPI schemaの材料になるAPI認証付き処理のcurlコマンドを取得します。

Supabaseで、左のメニュー「Database」→「Functions」、その中の「addinquiry」「getinquiries」「updateinquiry」それぞれの「Client API Docs」→「Bash」→API認証付き処理のcurlコマンド表示
![SupabaseのFunctionのcurlコマンド出力イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/d300d478-370e-641d-1b4f-88d06fe5d5ae.png)

２．OpenAPI schemaの原形を取得

GPTs「BetterActionsGPT」にそのcurlコマンドを投入し、schema全文を生成させます。

OpenAPI 3.0.0 のschemaにするのか、3.1.0のschemaにするのかも、同時に指定します。
どっちでもいいと思います。何も指定しないと、3.0.0でschemaが生成されます。

ただし、curlコマンドは必要最低限の情報しかないので、そのままではGPTs「BetterActionsGPT」が生成するschema原形もチープです。
せめてこの段階で、必須パラメーターの明示も同時に依頼するのがいいでしょう。
![GPTs「BetterActionsGPT」にcurl投入してschema作成を依頼_いろいろついでに_使用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/9dda05b6-6574-4499-64d4-e96de20522a3.png)

:::note info
GPTs「BetterActionsGPT」がデフォルトで生成するschema原形はチープです。
（このGPTsが悪いのではなく、そもそも材料のcurl自体がチープだから）
また、OpenAPI schemaは、手動で修正しにくいものです。
このschemaの原形生成の段階で、はじめからGPTs「BetterActionsGPT」にいろいろ追加でリクエストを出して、最初からschema原形に盛り込んでもらうのがコツです。
:::

３．schema原形の修正

①「summary」と全パラメーターの「description」を手直し

<FONT COLOR="RED"> **これは事実上必須です！**</FONT>
「summary」と全パラメーターの「description」を、実際の役割や意味といったものに修正します。
schema原形では、これらは何の意味もない文言しか書いてありません（そもそもcurlコマンドに何も書いていないから）。
**実際の役割や意味といったものにしないと、GPTsがAPIを呼ぶべき時に呼ばない、正しくパラメーターを生成しない、などが起こります。**
GPTsは、これら「summary」と全パラメーターの「description」を読み、APIを呼び、パラメーターを生成するからです。
この仕組みを<b>「Function calling」</b>と言います。これについては、本稿の後段で詳細に説明します。
修正された「summmary」と全パラメーターの「description」：
![新規登録schemaのaddInquiryのdescriptionとsummary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/f4c326ef-0ea7-aed3-1bf4-170a8fc944aa.png)

:::note warn
これは必ずやる必要があります。
ここを怠けると、後々、プロンプトを投入しても正しく動作しない可能性が高いです。
:::

②schemaの最後に「schemas: {}」を追記

これを書かないと、GPT Builderが文法エラー「<font color="red">In components section, schemas subsection is not an object</font>」と認識して、通してくれません。
「schemas: {}」を追記：
![schemaのrschemas{}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/1c82afbc-7449-75a5-7c1f-19b91de8e628.png)

４．修正したschemaをGPT BuilderのActionsに設定

</details>

#### GPTs「問い合わせ対応DBに新規登録」の作成

<details><summary>詳細を見る</summary>

|![新規登録_ロゴ._小png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/400177ec-0c6b-5b0c-737d-6c8c340f9595.png)|GPTs「問い合わせ対応DBに新規登録」<BR>顧客から問い合わせがあった際、その問い合わせの概要と、その問い合わせに対する1次対応の概要をDBに記録する。|
|:-----------:|:------------|

問い合わせ対応DBに問い合わせ対応データを新規登録するGPTs「問い合わせ対応DBに新規登録」を作成します。

##### GPTs「問い合わせ対応DBに新規登録」の役割

顧客から問い合わせがあった際、その問い合わせの概要と、その問い合わせに対する1次対応の概要をDBに記録します。
具体的には、以下の情報を、API経由で問い合わせ対応DBのFunction「addinquiry」に渡し、問い合わせ対応データの新規登録をさせます。
・顧客コード
・問い合わせ概要
・その1次対応概要
・対応完了/未完了
**これらの情報は、問い合わせ対応担当が発した自然言語のプロンプトからGPTsが割り出します。**

その際、**「対応完了/未完了」については、このGPTsが「1次対応概要」から自動で判定**します。
問い合わせ対応担当者は、わざわざプロンプトに対応が完了したか未完了かを含める必要はありません。
要するに、**どんな対応をしたか、の内容をもとに、GPTsが、その対応で完了したか、まだ対応が完了していないかを、自動で判定する**、ということです。

DB処理後、新規登録した問い合わせ対応データの詳細を、画面に表示します。

##### GPT Builderの設定（Actions以外）

|項目|　　　　　設定内容|
|:-----------:|:------------|
|Name|問い合わせ対応DBに新規登録<BR>（お好みで）|
|Instructions|以下に別記。|

Instructions全文：
```:GPTs「問い合わせ対応DBに新規登録」　Instructions全文
顧客からの問い合わせ概要と、その問い合わせに対する対応概要を、データベースに記録せよ。

＜データベースに記録＞

顧客からの問い合わせの概要と、それに対する対応の概要、対応が完了したか未完了か（これは明示されない可能性がある）
を、問い合わせ対応データベースに新規登録する。
ただし、対応が完了したか未完了か、明示されなかった場合は、君自身が、対応の概要から判断せよ。
※表現を簡潔にする程度の改変は許すが、情報を削る要約は禁止する。

ただし、プロンプト中で、明示的に「登録せよ」という文言でデータの登録の指示がされない場合がある。
君に何を求めているのか明示されず、上記のような情報を示されたら、新規登録を指示されている、と認識せよ。

＜データベースに記録　終わり＞

＜正常終了時の表示＞

正常終了した場合、以下の情報を含む確認メッセージを表示せよ。
※null値の場合、「-」と表記せよ。

・問い合わせID（APIレスポンス中の「id_inquiry_out」から得られる。）
・顧客コード（APIレスポンス中の「code_customer_out」から得られる。）
・顧客名（APIレスポンス中の「name_customer_out」から得られる。）
・問い合わせ概要（APIレスポンス中の「inquiry_summary_out」から得られる。）
・対応概要（APIレスポンス中の「response_summary_out」から得られる。）
・対応完了/未完了（APIレスポンス中の「is_completed_out」から得られる。trueなら「完了」、falseなら「未完了」と表示。）
・発生日時（APIレスポンス中の「created_at_out_str」から得られる。）
・完了日時（APIレスポンス中の「completed_at_out_str」から得られる。）

＜正常終了時の表示　終わり＞
```
##### GPT Builderの設定（Actions）

|項目|　　 　　　設定内容|
|:-----------:|:------------|
|Action名（1個のみ）|addInquiry<br>（schemaの「operationId」の値）|
|OpenAPIのschema|以下に別記。|

OpenAPIのschema全文：
※[前述](#3つのgptsに共通の設定)の通り、「servers」の「url」のドメイン部分は、ご自分のものにする必要があります。
```yaml:GPTs「問い合わせ対応DBに新規登録」　schema全文
openapi: 3.0.0
info:
  title: Supabase Inquiry API
  description: "API for recording a new inquiry from a customer and the response to it in the database."
  version: 1.0.0
servers:
  - url: https://(あなたのドメイン).supabase.co/rest/v1
    description: Supabase API server
paths:
  /rpc/addinquiry:
    post:
      operationId: addInquiry
      summary: Record a new inquiry from a customer and the response to it in the database.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                code_customer:
                  type: string
                  description: The code of the customer submitting the inquiry.4 digits.
                  example: "0010"
                inquiry_summary:
                  type: string
                  description: A summary of the inquiry.
                  example: "Inquiry about the details of product number 1234."
                response_summary:
                  type: string
                  description: A summary of the response to the inquiry.
                  example: "I responded by saying I would look into it and inform them."
                is_completed:
                  type: boolean
                  description: Whether the inquiry response has been completed(true if completed, false if not completed).If a value is not specified, it will be determined from the response_summary.
                  example: false
              required:
                - code_customer
                - inquiry_summary
                - response_summary
                - is_completed
      responses:
        '200':
          description: Inquiry successfully recorded in the database.
        '400':
          description: Bad request, e.g., invalid input data.
      security:
        - apikeyAuth: []
components:
  securitySchemes:
    apikeyAuth:
      type: apiKey
      in: header
      name: apikey
  schemas: {}
```
以下のように、<b>Action「addInquiry」</b>ができているはずです。
![新規登録_GPTBuilder_Action_available_actions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/550a098e-4d6e-b562-015b-2007c01aa2bb.png)

</details>

#### GPTs「問い合わせ対応DBの検索」の作成

<details><summary>詳細を見る</summary>

|![検索_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/0c926db6-9a95-a10a-db80-cbd27b15934a.png)|GPTs「問い合わせ対応DBの検索」<BR>問い合わせ対応データをDBから収集する|
|:-----------:|:------------|

問い合わせ対応DBから問い合わせ対応データを検索・収集するGPTs「問い合わせ対応DBの検索」を作成します。

##### GPTs「問い合わせ対応DBの検索」の役割

以下の情報を検索条件として、API経由で問い合わせ対応DBのFunction「getinquiries」に渡し、問い合わせ対応データを収集します。
・顧客コード
・顧客名
・対応完了/未完了
・問い合わせ発生日（期間で指定）
・問い合わせ対応完了日（期間で指定）
・キーワード（問い合わせ概要と対応概要を対象とする）
**これらの情報は、問い合わせ対応担当が発した自然言語のプロンプトからGPTsが割り出します。**

##### GPT Builderの設定（Actions以外）

|項目|　　　　設定内容|
|:-----------:|:------------|
|Name|問い合わせ対応DBを検索<BR>（お好みで）|
|Instructions|以下に別記。|

Instructions全文：
```:GPTs「問い合わせ対応DBを検索」　Instructions全文
問い合わせ対応データベースを、指定された検索条件で検索して、検索結果を表示せよ。

＜データベースを検索しデータ収集＞

問い合わせ対応データベースの中身は、以下である。
・問い合わせID
・顧客コードと顧客名
・問い合わせの概要
・問い合わせに対する対応の概要
・対応完了/未完了
・問い合わせ発生日時
・問い合わせ対応の完了日時

これらを組み合わせた検索条件が示されるので、問い合わせ対応データベースに問い合わせて、検索・収集せよ。
ただし、プロンプト中で、明示的に「検索せよ」という文言で検索の指示がされない場合がある。
君に何を求めているのか明示されず、問い合わせ対応データベースにある諸項目の情報を示されたら、検索を指示されている、と認識せよ。

＜データベースを検索しデータ収集　終わり＞

＜正常終了時の表示＞

正常終了した場合、以下の検索結果1件分の情報を、検索結果の件数分表示せよ。
表示は、表形式とする。
検索結果1件を表の1行とする。
先頭行を列見出しの行とする。各列の見出しは、以下の通り。
※null値の場合、「-」と表記せよ。

・問い合わせID（APIレスポンス中の「id_inquiry_out」から得られる。）
・顧客コード（APIレスポンス中の「code_customer_out」から得られる。）
・顧客名（APIレスポンス中の「name_customer_out」から得られる。）
・問い合わせ概要（APIレスポンス中の「inquiry_summary_out」から得られる。）
・対応概要（APIレスポンス中の「response_summary_out」から得られる。）
・対応完了/未完了（APIレスポンス中の「is_completed_out」から得られる。trueなら「完了」、falseなら「未完了」と表示。）
・発生日時（APIレスポンス中の「created_at_out_str」から得られる。）
・完了日時（APIレスポンス中の「completed_at_out_str」から得られる。）

＜正常終了時の表示　終わり＞
```
##### GPT Builderの設定（Actions）

|項目|　　 　　　設定内容|
|:-----------:|:------------|
|Action名（1個のみ）|getInquiries<br>（schemaの「operationId」の値）|
|OpenAPIのschema|以下に別記。|

OpenAPIのschema全文：
※[前述](#3つのgptsに共通の設定)の通り、「servers」の「url」のドメイン部分は、ご自分のものにする必要があります。
```yaml:GPTs「問い合わせ対応DBを検索」　schema全文
openapi: 3.0.0
info:
  title: Supabase Inquiry Search API
  description: "API for searching inquiry response data based on various criteria."
  version: 1.0.0
servers:
  - url: https://（あなたのドメイン）.supabase.co/rest/v1
    description: Supabase API server
paths:
  /rpc/getinquiries:
    post:
      operationId: getInquiries
      summary: Search for inquiry response data based on specific criteria.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                id_inquiry:
                  type: string
                  description: "ID of the inquiry, used as a search criterion."
                code_customer:
                  type: string
                  description: "Customer code, used as a search criterion.4 digits."
                name_customer:
                  type: string
                  description: "Customer name, used as a search criterion, supports partial string matches."
                is_completed:
                  type: boolean
                  description: "Indicates whether the response to the inquiry is completed (true) or not (false), used as a search criterion."
                created_at_begin:
                  type: string
                  format: date
                  description: "The earliest date to include inquiries that were initiated. This parameter allows you to filter inquiries starting from this date onwards. It's useful for isolating inquiries based on their initiation date, accommodating prompts like 'on or after 2024-01-20'."
                created_at_end:
                  type: string
                  format: date
                  description: "The latest date to include inquiries that were initiated. This parameter enables you to limit the search to inquiries that started on or before this date, effectively setting an upper boundary for the initiation date of inquiries."
                completed_at_begin:
                  type: string
                  format: date
                  description: "The earliest date to include inquiries that were completed. This parameter is essential for filtering inquiries that concluded starting from this date onwards, accommodating scenarios where the focus is on completion within a certain timeframe."
                completed_at_end:
                  type: string
                  format: date
                  description: "The latest date to include inquiries that were completed. This parameter helps in narrowing down the search to inquiries that were resolved on or before this date, suitable for identifying inquiries completed by this date."
                keyword:
                  type: string
                  description: "A keyword for searching that yields results if it partially matches the summary of either an inquiry or a response."
      responses:
        '200':
          description: Inquiry data retrieved successfully.
        '400':
          description: Bad request, e.g., invalid input data.
      security:
        - apikeyAuth: []
components:
  securitySchemes:
    apikeyAuth:
      type: apiKey
      in: header
      name: apikey
  schemas: {}
```
以下のように、<b>Action「getInquiries」</b>ができているはずです。
![GPTs「検索」_GPTBuilder_Action_available_actions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/4dda909d-3611-16a2-e574-fb4214751b5d.png)

</details>

#### GPTs「問い合わせ対応DBの更新」の作成

<details><summary>詳細を見る</summary>

|![更新_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/8c3b6947-72f2-e628-c40f-d8e273a840c3.png)|GPTs「問い合わせ対応DBを更新」<BR>顧客からの問い合わせに対する1次対応後、事後対応を行い進捗が生じれば、その事後対応の概要でDBを更新する。|
|:-----------:|:------------|

問い合わせ対応DBにある問い合わせ対応データを更新するGPTs「問い合わせ対応DBを更新」を作成します。

##### GPTs「問い合わせ対応DBを更新」の役割

顧客からの問い合わせに対する1次対応後、事後対応を行い進捗が生じれば、その事後対応の概要でDBを更新します。
具体的には、以下の情報を、API経由で問い合わせ対応DBのFunction「updateinquiry」に渡し、問い合わせ対応データの更新をさせます。
・問い合わせID
・事後対応概要
・対応完了/未完了

![「問い合わせ対応データ」概念図_更新時に渡される情報.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/0a76214c-73be-ec0f-2131-6aaddb46f095.png)
**これらの情報は、問い合わせ対応担当が発した自然言語のプロンプトからGPTsが割り出します。**

その際、**「対応完了/未完了」については、このGPTsが「事後対応概要」から自動で判定**します。
※ただし、新規登録と異なり、更新時は、GPTsによる対応完了/未完了の判定はハズれることが多くなります。
更新時には、この更新GPTsは、もともとの問い合わせ概要を知らないため、「事後対応概要」のみで判定せざるを得ないからです。
プロンプトをどうすればいいかは、後述します。

DB処理後、更新した問い合わせ対応データの詳細を、画面に表示します。

##### GPT Builderの設定（Actions以外）

|項目|　　　　設定内容|
|:-----------:|:------------|
|Name|問い合わせ対応DBを更新<BR>（お好みで）|
|Instructions|以下に別記。|

Instructions全文：
```:GPTs「問い合わせ対応DBを更新」　Instructions全文
顧客からの問い合わせとその対応を記録したデータベースを、指定された値で更新せよ。

＜データベースを更新＞

既存の問い合わせに対する事後対応の概要、対応が完了したか未完了か（これは明示されない可能性がある）、
の情報で、問い合わせ対応データベースを更新する。
ただし、対応が完了したか未完了か、明示されなかった場合は、君自身が、事後対応の概要から判断せよ。
※表現を簡潔にする程度の改変は許すが、情報を削る要約は禁止する。

ただし、プロンプト中で、明示的に「登録せよ」「更新せよ」という文言でデータの更新の指示がされない場合がある。
君に何を求めているのか明示されず、上記のような情報を示されたら、更新を指示されている、と認識せよ。

＜データベースを更新　終わり＞

＜正常終了時の表示＞

正常終了した場合、以下の情報（実際に更新した行の各列の値）を含む確認メッセージを表示せよ。
※null値の場合、「-」と表記せよ。

・問い合わせID（APIレスポンス中の「id_inquiry_out」から得られる。）
・顧客コード（APIレスポンス中の「code_customer_out」から得られる。）
・顧客名（APIレスポンス中の「name_customer_out」から得られる。）
・問い合わせ概要（APIレスポンス中の「inquiry_summary_out」から得られる。）
・対応概要（APIレスポンス中の「response_summary_out」から得られる。）
・対応完了/未完了（APIレスポンス中の「is_completed_out」から得られる。trueなら「完了」、falseなら「未完了」と表示。）
・発生日時（APIレスポンス中の「created_at_out_str」から得られる。）
・完了日時（APIレスポンス中の「completed_at_out_str」から得られる。）

＜正常終了時の表示　終わり＞
```
##### GPT Builderの設定（Actions）

|項目|　　 　　　設定内容|
|:-----------:|:------------|
|Action名（1個のみ）|updateInquiry<br>（schemaの「operationId」の値）|
|OpenAPIのschema|以下に別記。|

OpenAPIのschema全文：
※[前述](#3つのgptsに共通の設定)の通り、「servers」の「url」のドメイン部分は、ご自分のものにする必要があります。
```yaml:GPTs「問い合わせ対応DBを更新」　schema全文
openapi: 3.0.0
info:
  title: Supabase Inquiry Update API
  description: "API for updating an existing inquiry response record in the database."
  version: 1.0.0
servers:
  - url: https://（あなたのドメイン）.supabase.co/rest/v1
    description: Supabase API server
paths:
  /rpc/updateinquiry:
    post:
      operationId: updateInquiry
      summary: Updates an existing inquiry response record in the database.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                id_inquiry:
                  type: string
                  description: "Unique identifier of the inquiry response record to be updated."
                response_summary:
                  type: string
                  description: "Summary of the response to the inquiry, to update the inquiry record."
                is_completed:
                  type: boolean
                  description: "Indicates whether the response to the inquiry is completed (true) or not (false), to update the inquiry record.If a value is not specified, it will be determined from the response_summary."
              required:
                - id_inquiry
                - is_completed
      responses:
        '200':
          description: Inquiry record updated successfully.
        '400':
          description: Bad request, e.g., invalid input data.
      security:
        - apikeyAuth: []
components:
  securitySchemes:
    apikeyAuth:
      type: apiKey
      in: header
      name: apikey
  schemas: {}
```
以下のように、<b>Action「updateInquiry」</b>ができているはずです。
![GPTs「更新」_GPTBuilder_Action_available_actions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/e06977bb-34cd-887b-2fdc-ad19b7c4ce84.png)

</details>

これで、全3つのGPTsの作成が終わりました。

<!-- GPTs作成全体の折りたたみの終わり -->
<!-- </details> -->

## 【実行】実際に使ってみる

今まで構築した環境を、実際に動かしてみます。
以下の3つのGPTsを使い分け、それぞれに対して**実際に自然言語のプロンプトを投入し、
「自然言語だけでどういうデータが読み書きされるか」見てみます。**

| GPTs名|GPTsロゴ| 　　　　　　　　　　　役割|
|:-----------:|:-----------:|:-----------|
|問い合わせ対応DBに新規登録|![新規登録_ロゴ._小png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/ea7d8a38-e297-c4e8-0368-861ec11e95d0.png)|顧客から問い合わせがあった際、その問い合わせの概要と、その問い合わせに対する1次対応の概要をDBに記録する。<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|
|問い合わせ対応DBの検索|![検索_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/286c7cfe-3193-fbc0-5904-ae2ee9dc6888.png)|問い合わせ対応データを問い合わせ対応DBから検索・収集する。<br>**その検索条件は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|
|問い合わせ対応DBを更新|![更新_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/b08b3189-1b80-fe93-0464-c8fc4a790ea3.png)|顧客からの問い合わせに対する1次対応後、事後対応を行い進捗が生じれば、その事後対応の概要でDBを更新する。<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|

### 自然言語で、問い合わせ対応データの新規登録をしてみる

| GPTs名|GPTsロゴ| 　　　　　　　　　　　役割|
|:-----------:|:-----------:|:-----------|
|問い合わせ対応DBに新規登録|![新規登録_ロゴ._小png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/ea7d8a38-e297-c4e8-0368-861ec11e95d0.png)|顧客から問い合わせがあった際、その問い合わせの概要と、その問い合わせに対する1次対応の概要をDBに記録する。<BR>具体的には、以下の情報を、問い合わせ対応DBに新規登録する。<BR>・顧客コード<BR>・問い合わせ概要<BR>・その1次対応概要<BR>・対応完了/未完了<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|

顧客から問い合わせが来て、問い合わせ対応担当が実際に対応する、という事象を想定してみます。
顧客からの問い合わせが来て、
問い合わせ概要と、それに対する1次対応の概要を記録するので、
**GPTs「問い合わせ対応DBに新規登録」**
を使用します。

#### プロンプトのルール

<details><summary>詳細を見る</summary>

問い合わせ対応担当がプロンプトに含める情報は、以下の通りです。
|プロンプトに含める情報|必須/任意|　　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|顧客コード|必須|問い合わせをした顧客のコード。|
|問い合わせ概要|必須|問い合わせの内容の概要。|
|1次対応概要|必須|その問い合わせに対して行った1次対応の概要。|
|対応完了/未完了|任意|1次対応完了したか、まだ未完了か。<br>**プロンプト中で明示されない場合は、「1次対応概要」をもとに、GPTsが判定。**|

問い合わせ対応担当のプロンプト例）
「顧客コード0003のお客様より、カタログのP10の商品番号1234について詳細を知りたい、とお問い合わせあり。当該商品についてご説明しました。」
↓　GPTsからDBに渡される情報は・・
顧客コード：「0003」
問い合わせ概要：「カタログのP10の商品番号1234について詳細を知りたい、とお問い合わせ」
1次対応概要：「当該商品についてご説明しました」
対応完了/未完了：明示されていないので、GPTsが1次対応概要から判定。この場合は「対応完了」と判定されるはず。

</details>

**では、実際にプロンプトを投入してみます！**

#### DBに問い合わせ対応データを新規登録（GPTsが対応完了と判定するケース）

顧客から、「注文したものと色違いのものが届いたゾ！」と苦情が来て、
その場で調べてみたら確かに間違ったものを送っていたことがわかり、直ちに正しいものを送る処置をした。
・・・という事象を、問い合わせ対応担当者が**自然言語のみで**記録する、という場面を想定します。
![実際の問答_新規登録_ID46_使用3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/14628783-6404-5bb8-b0a5-f081e05d02c4.png)

**GPTsは、「対応概要」の内容から、「対応が完了した」と正しく判定しました。
（プロンプトのどこにも、「対応が完了した」という趣旨の文言はありません。）**

本当に、問い合わせ対応データ1件が正しく新規登録されたのでしょうか？
実際に、問い合わせ対応DB（Supabase）内のテーブル「INQUIRY」の中を見て、レコードが存在することを確認してみます。
![新規登録_テーブル「INQUIRY」の中_ID46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/66b5db0c-e332-e184-935d-b8d89cdad425.png)

ご覧の通り、テーブル「INQUIRY」に、**ID=46のレコードがちゃんとできています。** 問い合わせ対応担当が入力した**プロンプトに埋め込まれている個々の情報が、各列の値になっている**ことが確認できます。

**問い合わせ対応DBに、問い合わせ対応担当が入力したプロンプトに含まれる個々の情報が、正しく登録されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "code_customer": "0006",
  "inquiry_summary": "注文番号8217が今日届いたが、注文したものと色が違う。",
  "response_summary": "その場で確認し、商品の誤りと断定、すぐに正しい商品を再発送する処理を行った。",
  "is_completed": true
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 46,
    "code_customer_out": "0006",
    "name_customer_out": "杉田　京子",
    "inquiry_summary_out": "注文番号8217が今日届いたが、注文したものと色が違う。",
    "response_summary_out": "その場で確認し、商品の誤りと断定、すぐに正しい商品を再発送する処理を行った。",
    "is_completed_out": true,
    "created_at_out_str": "2024-02-09 12:42",
    "completed_at_out_str": "2024-02-09 12:42"
  }
]
```

</details>

#### DBに問い合わせ対応データを新規登録（GPTsが対応未完了と判定するケース）

顧客から、「廃番になって売っていないものだが、ぜひ欲しい！在庫が無ければどこで買えるのか教えて！」と懇願されて、
在庫が無いので、売っている店を調べてお知らせする、とひとまず1次回答した。
・・・という事象を、問い合わせ対応担当者が**自然言語のみで**記録する、という場面を想定します。
![実際の問答_新規登録_ID44_使用3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/72ab1470-4475-c5eb-e3ee-47412591b2a7.png)

**GPTsは、「対応概要」の内容から、「対応はまだ未完了」と正しく判定しました。
（プロンプトのどこにも、「対応はまだ未完了」という趣旨の文言はありません。）**

ここでも実際に、問い合わせ対応DB（Supabase）内のテーブル「INQUIRY」の中を見て、レコードが存在することを確認してみます。
![新規登録_テーブル「INQUIRY」の中_ID44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/75b1463e-3597-ac5f-30e1-4f7195344655.png)

ご覧の通り、テーブル「INQUIRY」に、**ID=44のレコードがちゃんとできています。** 問い合わせ対応担当が入力した**プロンプトに埋め込まれている個々の情報が、各列の値になっている**ことが確認できます。

**問い合わせ対応DBに、問い合わせ対応担当が入力したプロンプトに含まれる個々の情報が、正しく登録されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "code_customer": "0003",
  "inquiry_summary": "商品番号8173は廃番になったが、ぜひ手に入れたい。在庫があるかどうか、もし在庫が無ければ購入できる店を教えてほしい。",
  "response_summary": "在庫は無く、購入できる店についてはお調べしてお知らせする。",
  "is_completed": false
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 44,
    "code_customer_out": "0003",
    "name_customer_out": "鈴木　大介",
    "inquiry_summary_out": "商品番号8173は廃番になったが、ぜひ手に入れたい。在庫があるかどうか、もし在庫が無ければ購入できる店を教えてほしい。",
    "response_summary_out": "在庫は無く、購入できる店についてはお調べしてお知らせする。",
    "is_completed_out": false,
    "created_at_out_str": "2024-02-08 12:03",
    "completed_at_out_str": null
  }
]
```

</details>

### 自然言語で、問い合わせ対応データの検索をしてみる

| GPTs名|GPTsロゴ| 　　　　　　　　　　　役割|
|:-----------:|:-----------:|:-----------|
|問い合わせ対応DBの検索|![検索_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/286c7cfe-3193-fbc0-5904-ae2ee9dc6888.png)|以下の検索条件で、問い合わせ対応データを問い合わせ対応DBから収集する。<BR>・問い合わせID<BR>・顧客コード<BR>・顧客名<BR>・対応完了/未完了<BR>・問い合わせ発生日（期間で指定）<BR>・問い合わせ対応完了日（期間で指定）<BR>・キーワード<BR>**これらの検索条件は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|

何らかの理由で問い合わせ対応データの検索をする、という事象を想定してみます。
**GPTs「問い合わせ対応DBを検索」**
を使用します。

#### プロンプトのルール

<details><summary>詳細を見る</summary>

問い合わせ対応担当がプロンプトに含める情報（AND検索条件）は、以下の通りです。

|プロンプトに含める情報<br>（AND検索条件）|必須/任意|　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|問い合わせID|任意|問い合わせを一意に識別するID。|
|顧客コード|任意|問い合わせをした顧客のコード。|
|顧客名|任意|このプロンプト中の”顧客名”が、問い合わせをした顧客の名前に含まれる。|
|対応完了/未完了|任意|対応が完了したか、まだ未完了か。|
|問い合わせ発生日の期間検索の開始日|任意|問い合わせ発生日の期間検索の”以後（from）”の年月日。|
|問い合わせ発生日の期間検索の終了日|任意|問い合わせ発生日の期間検索の”以前（to）”の年月日。|
|対応完了日の期間検索の開始日|任意|問い合わせ対応完了日の期間検索の”以後（from）”の年月日。|
|対応完了日の期間検索の終了日|任意|問い合わせ対応完了日の期間検索の”以前（to）”の年月日。|
|キーワード|任意|キーワード検索のキーワード。問い合わせ概要と対応概要をキーワード検索の対象とする。|

問い合わせ対応担当のプロンプト例）
「顧客コード0004で、2024年2月中に発生したもの。」
↓　GPTsからDBに渡される情報は・・
顧客コード：「0004」
問い合わせ発生日の期間検索の開始日：2024年2月1日
問い合わせ発生日の期間検索の終了日：2024年2月29日

:::note warn
検索結果件数が多くなると、ChatGPT（GPT-4）の出力のトークン数上限に迫るようなAPIレスポンスが返ってくるようだと、GPTsは、自主規制をかけて、検索結果を正しく表示しなくなります。
そういう場合は、別の検索条件をプロンプトに追加して、検索結果件数を減らす必要があります。
:::

</details>

**では、実際にプロンプトを投入してみます！**

#### 問い合わせ対応データを検索-1

長らく対応が完了していない問い合わせを調べようと、**自然言語のみで**問い合わせ対応DBに検索をかける、という場面を想定します（これを書いてるのは2024年2月）。
![実際の問答_検索-1_使用3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/f014619e-6c52-af8c-7150-3b865b1c3f99.png)

プロンプト中の<b>「2024年1月中に発生」という雑な文言から、GPTsは、
2024/1/1 ≦ 問い合わせ発生日 ≦ 2024/1/31
という検索条件を作ってくれました。</b>
イイ感じです。

しかし本当に、正しく検索・収集したのでしょうか？
実際に、独自にクエリを叩いてCSVで出力して、GPTsの検索結果と照合してみました。
![実際の問答_検索-1_クエリ実証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/ab1e1af1-02db-8ee4-d892-87de4f541677.png)

確かに、GPTsの検索結果は正しいです。

**問い合わせ対応データが正しく検索・収集されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "created_at_begin": "2024-01-01",
  "created_at_end": "2024-01-31",
  "is_completed": false
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 8,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "今年のカタログの商品番号9995の詳細を教えて。",
    "response_summary_out": "先日、商品番号9995の詳細をご回答したが、誤りであることが分かったため、改めて、正しい情報をご回答することになった。",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-28 01:14",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 13,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "対応したオペレーターの知識が不足しているという苦情",
    "response_summary_out": "当該オペレーターに聞き取りの上、ご返信、と回答",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-28 05:54",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 14,
    "code_customer_out": "0003",
    "name_customer_out": "鈴木　大介",
    "inquiry_summary_out": "対応したオペレーターの態度が横柄",
    "response_summary_out": "当該オペレーターに聞き取りの上、ご返信、と回答",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-28 05:56",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 15,
    "code_customer_out": "0004",
    "name_customer_out": "高田　一郎",
    "inquiry_summary_out": "対応した店員の知識が不足しているばかりか態度も横柄",
    "response_summary_out": "対応した店員に聞き取りをしてからご返信、と回答",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-28 06:31",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 20,
    "code_customer_out": "0006",
    "name_customer_out": "杉田　京子",
    "inquiry_summary_out": "カタログの商品番号2478の価格についての問い合わせ",
    "response_summary_out": "ただちに調べて返信すると回答しました",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-30 05:05",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 22,
    "code_customer_out": "0003",
    "name_customer_out": "鈴木　大介",
    "inquiry_summary_out": "カタログ記載の商品番号09876の説明に誤りがあるとの苦情",
    "response_summary_out": "調べて返信するとご回答",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-30 05:20",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 24,
    "code_customer_out": "0011",
    "name_customer_out": "亀田　龍馬",
    "inquiry_summary_out": "対応したオペレーターの知識不足と横柄な態度に関する苦情",
    "response_summary_out": "その場で謝罪し、当該オペレーターに聞き取り調査の上ご返信すると回答",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-30 05:56",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 27,
    "code_customer_out": "0010",
    "name_customer_out": "梶原　智美",
    "inquiry_summary_out": "カタログの商品番号8129の価格が誤っているのではないか、との問い合わせ",
    "response_summary_out": "調査の上ご返信するのでお待ちいただくよう、ご回答。",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-31 00:10",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 28,
    "code_customer_out": "0006",
    "name_customer_out": "杉田　京子",
    "inquiry_summary_out": "口座引き落としがなされていない、との問い合わせ",
    "response_summary_out": "お調べして状況がわかり次第ご返信、と回答。",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-31 14:18",
    "completed_at_out_str": null
  }
]
```

</details>

#### 問い合わせ対応データを検索-2

「顧客の名前の一部が「佐藤」さんで、カタログ関連のとある問い合わせ対応データを探しているので、とりあえずサッと網をかける」という目的で、**自然言語のみで**問い合わせ対応DBに検索をかける、という場面を想定します。
![実際の問答_検索-2_使用3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/44a161a8-408c-99a9-6b52-f826cb2b3aaa.png)

今度は、”文字列”で攻めてみました。
キーワードと顧客名をプロンプトに含めました。
問い合わせ概要か対応概要がこのキーワードを含み、
このプロンプト中の顧客名を含むフル顧客名の顧客の
問い合わせ対応データを検索させました。

ここでも同様に、実際に独自にクエリを叩いてCSVで出力して、GPTsの検索結果と照合してみました。
![実際の問答_検索-2_クエリ実証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/2c06f473-f7ca-3bfe-7b81-39c1e8aca4de.png)

確かに、GPTsの検索結果は正しいです。

**問い合わせ対応データが正しく検索・収集されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "keyword": "カタログ",
  "name_customer": "佐藤"
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 6,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "今年のカタログを送ってほしい",
    "response_summary_out": "お送りする、と回答",
    "is_completed_out": true,
    "created_at_out_str": "2024-01-27 16:16",
    "completed_at_out_str": "2024-01-27 16:16"
  },
  {
    "id_inquiry_out": 8,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "今年のカタログの商品番号9995の詳細を教えて。",
    "response_summary_out": "先日、商品番号9995の詳細をご回答したが、誤りであることが分かったため、改めて、正しい情報をご回答することになった。",
    "is_completed_out": false,
    "created_at_out_str": "2024-01-28 01:14",
    "completed_at_out_str": null
  },
  {
    "id_inquiry_out": 11,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "今年のカタログの商品番号8888の詳細を教えて。",
    "response_summary_out": "商品の詳細をご回答",
    "is_completed_out": true,
    "created_at_out_str": "2024-01-28 04:24",
    "completed_at_out_str": "2024-01-28 04:24"
  },
  {
    "id_inquiry_out": 23,
    "code_customer_out": "0002",
    "name_customer_out": "佐藤　花子",
    "inquiry_summary_out": "カタログ記載の商品番号8888のキャンペーン価格についての問い合わせ",
    "response_summary_out": "その場で調べて回答した",
    "is_completed_out": true,
    "created_at_out_str": "2024-01-30 05:34",
    "completed_at_out_str": "2024-01-30 05:34"
  }
]
```

</details>

### 自然言語で、問い合わせ対応データの更新をしてみる

| GPTs名|GPTsロゴ| 　　　　　　　　　　　役割|
|:-----------:|:-----------:|:-----------|
|問い合わせ対応DBを更新|![更新_ロゴ_小.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/b08b3189-1b80-fe93-0464-c8fc4a790ea3.png)|顧客からの問い合わせに対する1次対応後、事後対応を行い進捗が生じれば、その事後対応の概要でDBを更新する。<BR>具体的には、以下の情報で、問い合わせ対応DBの当該問い合わせデータを更新する。<BR>・問い合わせID<BR>・事後対応概要<BR>・対応完了/未完了<BR>**これらの情報は、問い合わせ対応担当が発した自然言語からGPTsが生成する。**|


既に何らかの対応がされている発生済みの問い合わせについて、さらに事後の対応が行われた、という事象を想定してみます。
その事後対応の概要で、当該問い合わせ対応データの更新を行います。
**GPTs「問い合わせ対応DBの更新」**
を使用します。

#### プロンプトのルール

<details><summary>詳細を見る</summary>

問い合わせ対応担当がプロンプトに含める情報は、以下の通りです。
|プロンプトに含める情報|必須/任意|　　　　　　　　　　　意味|
|:-----------:|:-----------:|:------------|
|問い合わせID|必須|問い合わせを一意に識別するID。|
|事後対応概要|任意|その問い合わせに対して行った事後対応の概要。|
|対応完了/未完了|任意|その事後対応で対応完了したか、まだ未完了か。<br>**プロンプト中で明示されない場合は、「事後対応概要」をもとに、GPTsが判定。※1）**|

![「問い合わせ対応データ」概念図_更新時に渡される情報.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/0a76214c-73be-ec0f-2131-6aaddb46f095.png)

問い合わせ対応担当のプロンプト例）
「問い合わせIDが10の件。調査の結果、サイトの記載に誤りがあったことが判明したので、お客様に謝罪の上、サイトの当該ページを修正した。」
↓　GPTsからDBに渡される情報は・・
問い合わせID：「10」
事後対応概要：「調査の結果、サイトの記載に誤りがあったことが判明したので、お客様に謝罪の上、サイトの当該ページを修正した」
対応完了/未完了：明示されていないので、GPTsが事後対応概要から判定。この場合は「対応完了」と判定されるはず。

:::note warn
**※1）更新時は、GPTsによる対応完了/未完了の判定はハズれることが多くなります。**
更新時、この更新用GPTsは、もともとの問い合わせ概要を知らないため、事後対応概要のみで判定せざるを得ないからです。
①事後対応概要に、もともとの問い合わせ概要をさらにサマったものを入れ込む
②プロンプトの中に、対応完了/未完了の情報も含める（当然ですが100%確実）
③同一スレッド内で、事前に更新対象データを検索して表示後、更新をかける
等で回避できます。本稿の例示では、③をしています。
:::

</details>

**では、実際にプロンプトを投入してみます！**

#### 問い合わせ対応データを更新（GPTsが対応完了と判定するケース）

＜以前の1次対応時＞
以前、「知識も無ければ態度も悪いトンデモ店員がいるぞ！」という激烈なクレームに対し、「調査して報告するから待っといてください・・」という1次対応をした。その1次対応の概要を問い合わせ対応DBに新規登録し、問い合わせ対応データができた。
＜今回の事後対応時＞
後日、その店員に聞き取り調査し、クレームした顧客にその調査内容を報告した、という事後対応を行った。
・・・という事象を、**自然言語のみで**問い合わせ対応DBに記録（上書き更新）する、という場面を想定します。

まずは、1次対応した当該問い合わせ対応データを検索して呼び出だすところから始める必要があります。
そして、このデータを、事後対応の概要で更新します。
![実際の問答_更新_ID15_使用2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/eb304d55-7929-2f26-3cf9-a96ba54dc5dd.png)

**GPTsは、「対応概要」の内容から、「対応が完了した」と正しく判定しました。
（プロンプトのどこにも、「対応が完了した」という趣旨の文言はありません。）**

本当に、この問い合わせ対応データは正しく更新されたのでしょうか？
実際に、問い合わせ対応DB（Supabase）内のテーブル「INQUIRY」の中を見て、レコードの関連フィールドが更新されていることを確認してみます。
![更新_テーブル「INQUIRY」の中_ID15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/9af45c27-0aa0-3f7b-41db-b07950de64be.png)

ご覧の通り、テーブル「INQUIRY」のID=15のレコードの「RESPONSE_SUMMARY」「IS_COMPLETED」が、問い合わせ対応担当が入力した**プロンプトに埋め込まれている情報通りになっている**ことが確認できます。

**問い合わせ対応担当が入力したプロンプトに含まれる事後対応の情報で、問い合わせ対応DBが正しく更新されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "id_inquiry": "15",
  "response_summary": "この店員に聞き取りした。まだ店員になって日が浅く、知識不足が否めず、その不安さが態度に出た、と結論。店員教育を見直す、とお客様に報告し、謝罪。",
  "is_completed": true
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 15,
    "code_customer_out": "0004",
    "name_customer_out": "高田　一郎",
    "inquiry_summary_out": "対応した店員の知識が不足しているばかりか態度も横柄",
    "response_summary_out": "この店員に聞き取りした。まだ店員になって日が浅く、知識不足が否めず、その不安さが態度に出た、と結論。店員教育を見直す、とお客様に報告し、謝罪。",
    "is_completed_out": true,
    "created_at_out_str": "2024-01-28 06:31",
    "completed_at_out_str": "2024-02-10 06:44"
  }
]
```

</details>

#### 問い合わせ対応データを更新（GPTsが対応未完了と判定するケース）

＜以前の1次対応時＞
以前、「商品番号2522はどこで買えるの？」という質問に対し、その場で販売店を調べて回答する、という1次対応をした。そこで対応完了となり、その対応の概要を問い合わせ対応DBに新規登録し、問い合わせ対応データができた。
対応完了なので、この件はもう終わった・・はずだった。
＜今回の事後対応時＞
しかし後日、その回答に誤りがあることがわかり、改めて販売店を調べてお知らせする旨を顧客に連絡した、という事後対応した。
・・・という事象を、**自然言語のみで**問い合わせ対応DBに記録（上書き更新）する、という場面を想定します。

まずは、1次対応した当該問い合わせ対応データを検索して呼び出だすところから始める必要があります。
そして、このデータを、事後対応の概要で更新します。
![実際の問答_更新_ID30_使用2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/407a1877-2ab1-bac2-2191-700d98abff47.png)

**GPTsは、「対応概要」の内容から、「対応はまだ未完了」と正しく判定しました。
（プロンプトのどこにも、「対応はまだ未完了」という趣旨の文言はありません。）**

ここでも同様に、実際に問い合わせ対応DB（Supabase）内のテーブル「INQUIRY」の中を見て、レコードの関連フィールドが更新されていることを確認してみます。
![更新_テーブル「INQUIRY」の中_ID30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/879c3c8c-3295-30b1-c48d-7ab20d804e0c.png)

ご覧の通り、テーブル「INQUIRY」のID=30のレコードの「RESPONSE_SUMMARY」「IS_COMPLETED」が、問い合わせ対応担当が入力した**プロンプトに埋め込まれている情報通りになっている**ことが確認できます。

**問い合わせ対応担当が入力したプロンプトに含まれる事後対応の情報で、問い合わせ対応DBが正しく更新されました！**

<details><summary>実際に送られたAPIリクエスト（パラメーター部分）</summary>

```JSON:実際に送られたAPIリクエスト（パラメーター部分）
{
  "id_inquiry": "30",
  "response_summary": "以前回答した販売店舗は誤りであることがわかった。取り急ぎお客様に謝罪の上、正しい販売店舗を調べて後日お知らせする、とした。",
  "is_completed": false
}
```

</details>

<details><summary>実際に受け取ったAPIレスポンス（レスポンスデータ部分）</summary>

```JSON:実際に受け取ったAPIレスポンス（レスポンスデータ部分）
[
  {
    "id_inquiry_out": 30,
    "code_customer_out": "0010",
    "name_customer_out": "梶原　智美",
    "inquiry_summary_out": "商品番号2522が購入可能な店舗について質問あり",
    "response_summary_out": "以前回答した販売店舗は誤りであることがわかった。取り急ぎお客様に謝罪の上、正しい販売店舗を調べて後日お知らせする、とした。",
    "is_completed_out": false,
    "created_at_out_str": "2024-02-02 14:42",
    "completed_at_out_str": null
  }
]
```

</details>


## 【解説】なぜうまくやれるのか？～「Function calling」

### 疑問「なぜうまくやれるのか？」を2つに分解

普段我々が使い慣れている「定型入力フォーム」とは全く真逆で、
**完全に不定形で、何が含まれるのか全く予測できない自然言語**のプロンプトなのに、結果的にはうまく処理されている。
**なぜうまくやれるのか？**

この「なぜうまくやれるのか？」の疑問を分解すると、以下2点になるでしょう。

#### 疑問１

【？】各GPTsで、それぞれの**本来の意図のプロンプトに<font color="red">限って</font>、対応するActionが正しく発動**するのは、なぜ？

例）新規登録用GPTs「問い合わせ対応DBに新規登録」

このGPTsの本来の意図はデータの新規登録である。
- データの新規登録を意図するプロンプトを投入
→　データ新規登録Action「addInquiry」が正しく発動
- 本来の意図であるデータの新規登録とは関係ないプロンプトを投入（「なぜ問い合わせ対応が完了したと判断した？」みたいな）
→　データ新規登録Action「addInquiry」は正しく発動しない（＝発動しないのが正しい）

検索用GPTs、更新用GPTsも同様。

#### 疑問２

【？】自然言語のプロンプトには**様々な要素がシームレスに埋め込まれているのに、Actionのパラメーター達がうまいこと生成される**のは、なぜ？

例）新規登録用GPTs「問い合わせ対応DBに新規登録」

プロンプト：
顧客コード0006のお客様より、注文番号8217が今日届いたが、注文したものと色が違う、と苦情が入った。その場で確認し、商品の誤りと断定、すぐに正しい商品を再発送する処理を行った。

は、自然言語の文章なので、いろいろな要素がシームレスに存在している。
**この中から、データ新規登録Action「addInquiry」のパラメーター「顧客コード」「問い合わせ概要」「1次対応概要」「対応完了/未完了」が巧みに切り出される。**
![新規登録ID46_プロンプトとDB列の対応.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/35c91680-b536-ac06-1ede-0e98ef4e15dd.png)

:::note info
これらを裏から実現しているのは、<b>「Function calling」</b>という、LLMの機能です。
OpenAIのGPT（GPTs含め）だけでなく、GoogleのGeminiにもあります。
:::

### GPTsにおけるFunction calling

ここでは、GPTs「問い合わせ対応DBに新規登録」の
[このやりとり](#dbに問い合わせ対応データを新規登録gptsが対応完了と判定するケース)
を題材にします。

上記のやり取りの中で行われていることを時系列で追っていきます。
随時、「Function calling」がどのような働きをしているのか、説明していきます。

#### 中で何が行われているのか、時系列で追う

中では、時系列で以下のことが行われていると思われます。

０．プロンプト投入

GPTs「問い合わせ対応DBに新規登録」において、
自然言語のプロンプト
「顧客コード0006のお客様より、注文番号8217が今日届いたが、注文したものと色が違う、と苦情が入った。その場で確認し、商品の誤りと断定、すぐに正しい商品を再発送する処理を行った。」
が投入された。

↓

－－－－－－－－－－　ここからがFunction calling　－－－－－－－－－－

１．プロンプトの意図に沿う最適なActionを選択

GPTsは、GPT Builderの「Actions」に定義されているActionのうち、プロンプトの意図に沿う最適なActionを選択する。
または、「最適なAction無し」として、何も選択しない。

具体的には、
プロンプトの意図
と、
「Actions」に設定してあるschemaの各Actionの「summary」
を照合して、決定する。
![新規登録schemaのaddInquiryのsummary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/68bc2094-a429-cf63-f943-88abc6b89e25.png)
ここでは、
GPTsは、GPT Builderの「Instructions」の指示内容も踏まえて、このプロンプトの意図を「問い合わせ対応データを新規登録する」と推測。
このプロンプトの意図「問い合わせ対応データを新規登録する」
と、
このGPTs唯一のAction「addInquiry」のsummary「Record a new inquiry from a customer and the response to it in the database.」
を照合し、一致したので、この**Action「addInquiry」が選択された。
⇒[疑問１](#疑問１)に対する答え**
|　プロンプトの意図|Action|　　　　　　Actionのsummary|照合の結果|
|:-----------|:-----------:|:------------|:-----------:|
|問い合わせ対応データを新規登録する|addInquiry 　　|Record a new inquiry from a customer and the response to it in the database.|一致|

（以後、最適なActionが選択された場合のみ）
↓

２．そのActionのパラメーター（引数）を生成

選択されたActionは関数であり、大抵は引数を持つ。その引数をGPTsが自動生成する。

具体的には、
プロンプトの内容
と、
schema中、1で選択したActionに定義してある全パラメーターの「description」
を照合して、プロンプトの中から抽出するように、生成する。
![新規登録schemaのaddInquiryの全パラメーターのdescription.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/c108267f-5bd6-1591-fa47-0fa170563b5b.png)
ここでは、
プロンプトの内容
と、
選択されたAction「addInquiry」（実体はDBのFunction「addinquiry」）の各パラメーターの「description」
を照合し、**以下のパラメーター値が生成された。
⇒[疑問２](#疑問２)に対する答え**
|パラメーター|　パラメーターのdescription|　照合の結果、生成された値|
|:-----------:|:-----------|:-----------|
|code_customer（顧客コード） |The code of the customer submitting the inquiry.4 digits.|0006|
|inquiry_summary（問い合わせ概要）   |A summary of the inquiry.|注文番号8217が今日届いたが、注文したものと色が違う。|
|response_summary（対応概要）|A summary of the response to the inquiry.|その場で確認し、商品の誤りと断定、すぐに正しい商品を再発送する処理を行った。|
|<font color="grey">is_completed（対応完了/未完了）</font>|<font color="grey">Whether the inquiry response has been completed(true if completed, false if not completed).If a value is not specified, it will be determined from the response_summary.</font>|<font color="grey">true※対象外</font>|

※本プロンプトにおいては、対応完了/未完了を指し示す文言が明示されなかったため、パラメーター「is_completed（対応完了/未完了）」=trueは、response_summary（対応概要）の内容に基づいて、GPTsが自己判定した結果となっています。よって、今の「schemaのdescriptionを参照しての自動生成」の対象外としました。
もし、プロンプト中に「対応が完了した。」などという文言があれば、他のパラメーターと同じで、「schemaのdescriptionを参照しての自動生成」の対象になります。

－－－－－－－－－－　ここまでがFunction calling　－－－－－－－－－－

↓

３．選択したActionの実体を、自動生成したパラメーターを添えて、実行

ここでは、
選択されたAction「addInquiry」の実体であるDBのFunction「addinquiry」を、
自動生成した上記のパラメーターを添えて、実行する。
実行に際しては、GPT BuilderのActionsの認証設定、schemaに書いてあるエンドポイントやsecurityなどを使用する。
⇒
実体であるDBのFunction「addinquiry」実行の結果、問い合わせ対応DBに、GPTs自身が生成したパラメーター値で、問い合わせ対応データが新規登録された。
プロンプトの意図通りの結果である。

【？】もし、プロンプトが「アメリカの首都は？」だったら？
このプロンプトの意図と、Action「addInquiry」のsummaryの間には全然関係がない→「最適なAction無し」と、GPTsは決定します。**⇒[疑問１](#疑問１)に対する答え**
このパターンでは、GPTsは通常応答します。
GPTsの答え「アメリカの首都はワシントンD.Cです。」

#### GPTsにおけるFunction callingのまとめ

上記の時系列での「1」「2」が、「Function calling」にあたります。
GPTsにおけるFunction callingをまとめると、以下となります。
- 目的は、プロンプトの意図に最適なActionを起動すること
- GPTsに事前に以下の情報を提供
・各Actionの役割や機能
・各Actionの個々のパラメーターの意味や機能
・これらの情報は、GPT Builderの「Actions」に設定されているOpenAPI schemaに記載
- **GPTsは、プロンプトの意図や内容をそれらの情報と照合し、以下を行う
・どのActionを起動するべきか、どのActionも起動すべきでないか、選択
・そのActionのパラメーターを生成**

:::note info
GPTsにおけるFunction callingをさらにザックリまとめます。
・GPTsは、各Actionの定義をOpenAPI schemaで事前提供される
・GPTsは、プロンプト投入時、その各Actionの定義をもとに、最適なActionを選択し、パラメーターを生成
:::

GPTsが、内部でどのようにFunction callingの仕組みを利用して、
プロンプトの意図に沿ったActionを選択し、
複数の要素がシームレスに埋め込まれている自然言語の文章から、そのActionのパラメーター値を個々に生成しているか、
イメージできたと思います。

※ここまで述べてきたことは、一般的なFunction callingに基づいた、筆者の推測です。推測ではありますが、他に考えようがないため、正しいと思っています。

### より一般的なFunction callingについて

GPTsに限らない、より一般的なFunction callingについては、以下の記事を参照してください。

https://qiita.com/wing_man/items/788511a69b09ad3db4e3