【GPT】Function calling 最初の一歩

## はじめに ##

<FONT COLOR="RED">－－2023/11/11追記－－
本稿のサンプルコードには、2023/11/6の"OpenAI DevDay"時のAPI仕様変更を適用しています。
ただし、複数の関数を非同期で呼び出す「Parallel function calling」には触れていません。
－－－－
</FONT>

OpenAIの大規模言語モデルGPTのAPI関連の機能「Function calling」。
とても便利で、AIならではの機能だと思います。

Function callingは、手の内に入れてしまえば色々なことができますが、**はじめはとっつきにくく**、公式ドキュメントを読んでも、高度な実践をしている記事を見ても、正直「つかみにくい」ものでもあります。
そこで、簡単な実装例を交えて、「Function callingとはこういうもので、こういうことができる」ということを記したいと思います。

本稿の実装例は、あくまでも、あまたある使い方の内の**一部の基本形**にすぎません。
ただ、それゆえに、十分応用が利く内容だと思います。

#### ＜参考＞GPTsにおけるFunction callingの応用例 ####

GPTsにはFunction callingの機能があらかじめ備わっています。
それを活用した、自然言語でDBの読み書きをするGPTsを作りました。

https://qiita.com/wing_man/items/b12b088155a6cd9787ea

#### 本稿が対象とする読者 ####

* GPTとのAPIでのやり取りが行える人
GPTとのAPIでのやり取りが行える（roleとかcontentとかもわかる）人
（本稿ではここら辺の説明はありません）
* Function calling初心者
まずは、「そもそも何なのか」「何ができるようになるのか」という視点からはじめたい人

※Function callingを既によく知っている人にとっては、目新しいことは何も書いておりません。

## Function callingのしくみと流れ ##

「Function callingとは何か」をザックリ機能面で表現すると、
**GPTに自分だけの関数のリストを教えておくと、GPTは、プロンプト（GPTへの質問や依頼）の内容に応じて、最適な関数を選んでくれて、その引数までも返してくれる**
となります。

#### 全体の流れ ####

①関数の実体（プログラム）と、その関数の定義を記したリストを準備する

**この「関数」は、Function callingを利用する人間が、各々の目的に沿って準備**します。
※以下、関数名が日本語なのは、説明の簡便のためです。
![仕組み説明_関数実体と関数定義2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/881fc65a-0524-5476-fac4-030fd4f3772c.png)

②GPT呼び出し元（GPTを利用する側）は、GPTにプロンプトと一緒に関数定義リストも投入する

<FONT COLOR=RED>③GPTは、プロンプトを満たすのに最適な関数を関数定義リストから選択（その関数の引数もGPTがプロンプトを読んで作成）</FONT> 

ここが、**Function callingの本体部分**となります。
GPTはプロンプトを読み、
・関数定義リストの中から、**プロンプトを満たすのに最適な関数を自動で選択**
・その関数を実行する際の**引数を自動で作成**
します。

つまり、GPTが返すのは、
**「そのプロンプトだったら、与えられた関数定義リストの中ではこの関数を実行するのが最適であり、その時の引数はこれである」**
です。

※最適な関数が無いと判断された場合は、通常の応答がなされます。

④GPT呼び出し元は、GPTが選択した関数を実行（その際、GPTが作成した引数をその関数に渡す）

GPT自身が関数を実行するのではなく、**GPT呼び出し元が関数を実行**します。

全体の流れの図：
![仕組み説明_全体の流れ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/a7758f69-afbe-1034-ab03-8e444fdd96d4.png)

## 使用例：処理の自動化 ##

### 要点 ###

Function callingを利用すれば、
**やりたいことを実行する関数を必要なだけ準備**しておき、
**「何をやりたいか」の意図をプロンプトに含めてGPTに投入すれば、その意図に沿う関数が選ばれて実行される、**
という枠組みを作ることができます。
「やりたいこと」は、例えば、自動化したい業務、であってもいいです。

具体的にどういうことか、以下の実装例で示します。

### 【実装例】複数業務の自動化 ###

#### やること ####

業務実行の意図が含まれるプロンプトがGPTに投入された後、Function callingにより、プロンプトのそれぞれの意図に応じた処理を自動で走らせます。

#### 仕様 ####

GPTに投入されるプロンプトを発信する人として、企業の仕入担当を想定。

プロンプトの意図と、自動化する処理の関係は、以下の表の通りです。

| プロンプトの意図<BR>（行いたい業務） | 自動化する処理 |
|:-----------:|:------------|
| 発注     |発注先のLineに、その発注の整形された依頼文を送信<BR>関数「order_item」がそれを行う|
| 予定の登録 |共有されたGoogle Calendarに、その予定を登録<BR>関数「write_appointment_on_calendar」がそれを行う|
| その他   |GPTは何の関数も選択せず、よって何も自動化されない<BR>通常の応答<BR>（ごく普通の「GPTへの質問・依頼」→「GPTからの応答」）|

※GPTへのプロンプトの投入手段は、発話→音声認識→GPTや、画面UIのChatGPTライクのテキスト入力→GPTなど、いろいろ考えられます。しかし**本稿の実装では、プロンプトが（投入手段はどうあれ）ともかくGPTに投入された、というところからを範囲**としています。

フロー図：
```mermaid
graph TD;
    Start([GPTにプロンプト投入])-->B-->|発注| GPTが関数order_itemを選択-->関数order_itemの実行-->発注先のLineに注文が送信される;
    B{プロンプトの意図=?\nGPTが自動判別}-->|予定の登録| GPTが関数write_appointment_on_calendarを選択-->関数write_appointment_on_calendarの実行-->GoogleCalendarに予定が登録される;
    B-->|その他| GPTの関数選択無し-->GPTが通常応答;
```
:::note info
GPTが「こういう意図に対してはこっちの関数、そういう意図に対してはそっちの関数、その他の意図に対してはいつもの応答」と自動で分岐してくれるので、この点においては、**面倒な条件分岐は考えなくてもよくなりました。**
:::

#### 関数の定義リストと、関数の実体 ####

##### 関数の定義リスト #####

GPTにプロンプトを投入する際、一緒に投入されます。

```python:関数の定義リスト
functions1 =  \
[
    {
        # 【発注】
        # 関数名
        "name": "order_item",
        # 関数の説明
        "description": "商品を発注する。",
        # 関数の引数の定義
        "parameters":
         {
            "type": "object",
            "properties":
             {
                "name_order_to":
                {
                    "type": "string",
                    "description": "発注先の会社名",
                },
               "name_item":
                {
                    "type": "string",
                    "description": "発注する商品の商品名",
                },
                "quantity_item":
                {
                    "type": "number",
                    "description": "発注する商品の数量",
                },
                "delivery_location":
                {
                    "type": "string",
                    "description": "発注する商品の配達先",
                }
            },
            "required": ["name_order_to", "name_item"],
        }
    },
    {
        # 【予定の登録】
        # 関数名
        "name": "write_appointment_on_calendar",
        # 関数の説明
        "description": "カレンダーに予定を書き込む。",
        # 関数の引数の定義
        "parameters":
         {
            "type": "object",
            "properties":
             {
                "summary":
                {
                    "type": "string",
                    "description": "予定の件名",
                },
                "description":
                {
                    "type": "string",
                    "description": "予定の内容、本文",
                },
                "start_hour":
                {
                    "type": "number",
                    "description": "予定開始時刻の「時」の部分",
                },
                "end_hour":
                {
                    "type": "number",
                    "description": "予定終了時刻の「時」の部分",
                },
                "start_minute":
                {
                    "type": "number",
                    "description": "予定開始時刻の「分」の部分",
                },
                "end_minute":
                {
                    "type": "number",
                    "description": "予定終了時刻の「分」の部分",
                },
                "day":
                {
                    "type": "number",
                    "description": "予定年月日の「日」の部分",
                },
                "month":
                {
                    "type": "number",
                    "description": "予定年月日の「月」の部分",
                },
                "year":
                {
                    "type": "number",
                    "description": "予定年月日の「年」の部分",
                }
            },
            "required": ["summary", "description", "start_hour", "end_hour"],
        }
    }    
]

```
##### 関数の実体 #####

```python:関数の実体
# 【発注】発注先のLineに整形された発注依頼文を送信する実体関数
def order_item(name_order_to, name_item, quantity_item=None, delivery_location=None):
    # 引数は、GPTがFunction callingにて作成したものがそのまま渡されている
    # ただし、プロンプトにおいて、数量や配達先が省略されたものは、Noneとなっているはず

    # プロンプトにおいて省略されてNoneとなった引数をここで補完
    if quantity_item is None: quantity_item = 1
    if delivery_location is None: delivery_location = "本社倉庫"

    dt_now = datetime.datetime.now()

    message = name_order_to + "　御中\n"
    message = message + "以下の商品を発注いたします。\n"
    message = message + "商品名：" + name_item + "\n"
    message = message + "数量：" + str(quantity_item) + "\n"
    message = message + "指定場所：" + delivery_location + "\n"
    message = message + "発注日時：" + str(dt_now)

    # Lineにメッセージを送信する汎用関数
    # 本稿ではこの中身にまでは入り込みません（中では実際にはLine Developersのチャネルを使用）
    line_send_message(message)

    return message

# 【予定の登録】共有されたGoogle Calendarに予定を登録する実体関数
def write_appointment_on_calendar(summary, description, start_hour, end_hour, start_minute=None, end_minute=None, day=None, month=None, year=None):
    # 引数は、GPTがFunction callingにて作成したものがそのまま渡されている
    # ただし、プロンプトにおいて、予定の年月日時分のうち省略されたものは、Noneとなっているはず

    ymd_today = datetime.date.today()
    
    # プロンプトにおいて省略されてNoneとなった引数をここで補完
    if day is None: day = ymd_today.day
    if month is None: month = ymd_today.month
    if year is None: year = ymd_today.year
    if start_minute is None: start_minute = 0
    if end_minute is None: end_minute = 0

    start_dt_iso = datetime.datetime(year, month, day, start_hour, start_minute).isoformat()
    end_dt_iso = datetime.datetime(year, month, day, end_hour, end_minute).isoformat()
    
    # Google Calendarに予定を書き込む汎用関数
    # 本稿ではこの中身にまでは入り込みません（動作させるにはGCPで権限設定を頑張る必要あり）
    event_id = gcal_post_event(summary=summary, description=description, start_dt_iso=start_dt_iso, end_dt_iso=end_dt_iso)

    return event_id
```

#### 全体～プロンプト投入から自動処理実行まで ####

流れは、上記のフロー図のうち「発注」「予定の登録」の分岐を参照のこと。

```python:全体～プロンプト投入から自動処理実行まで
# 前準備　API_KEY文字列を渡してclientオブジェクト生成
from openai import OpenAI
client = OpenAI(api_key="xxxxx") # 本当はOS環境変数を使用してAPI_KEY文字列を渡した方が良い

# プロンプトを受け付けてGPTに投入する。
# その際、Function callingを使用して、自動化した処理を走らせる。
def function_calling_demo(prompt):
    
    # GPTにプロンプトと関数定義リストを一緒に投入し、Function callingの使用を指示
    response = client.chat.completions.create(
        model="gpt-4-turbo-preview",  # モデル名を指定　これはGPT-4 Turbo previewの最新版のAlias
        temperature=0, # ここでは、GPTの答えに多様性を求めない
        messages=
    	[
    		{ "role": "user", "content": prompt}  # プロンプトを投入
    	],
        functions=functions1, # プロンプトと一緒に関数定義リストを投入
        function_call="auto", # Function callingを使用するが、その際、関数の選択はGPTに任せる
    )

    # GPTからの応答を抽出
    message = response.choices[0].message
    function_call = message.function_call

    if function_call is not None:
        # 関数定義リスト中の関数を実行すべきとGPTが判断

        # GPTが実行すべき関数であるとして選択した関数の名前
        function_name = function_call.name
        # その関数に渡すべき引数も、GPTが作成してくれる
        arguments = json.loads(function_call.arguments)
        
        # function_name（GPTが選択した関数の名前）に基づき、該当する実体関数を実行
        # GPTが選択した関数によって分岐させている

    	if function_name == "order_item":
            # 【発注】
            # 商品を発注する関数「order_item」をGPTが選択
            # GPTの選択に従い、実体関数「order_item」を実行
            
            # 実体関数に渡す引数もGPTが作成済み
            # 発注する（Lineに整形された発注依頼文を送信）
            function_response = order_item(
        		name_order_to=arguments.get("name_order_to"), 
        		name_item=arguments.get("name_item"), 
        		quantity_item=arguments.get("quantity_item"), 
        		delivery_location=arguments.get("delivery_location")
            )

            answer = function_response

        elif function_name == "write_appointment_on_calendar":
            # 【予定の登録】
            # カレンダーに予定を書き込む関数「write_appointment_on_calendar」をGPTが選択
            # GPTの選択に従い、実体関数「write_appointment_on_calendar」を実行
            
            # 実体関数に渡す引数もGPTが作成済み
            # カレンダーに予定を登録（Google Calendarに予定をポスト）
            function_response = write_appointment_on_calendar(
                summary=arguments.get("summary"), description=arguments.get("description"),
                start_hour=arguments.get("start_hour"), end_hour=arguments.get("end_hour"),
                start_minute=arguments.get("start_minute"), end_minute=arguments.get("end_minute"),
                day = arguments.get("day"), month=arguments.get("month"), year=arguments.get("year")
            )

            answer = function_response

    else:
    	# 【その他】
        # 関数定義リスト中のどの関数も実行すべきでない、とGPTが判断（それら関数とは無関係のプロンプト）
        # ごく普通の「GPTへの質問・依頼」→「GPTからの応答」
        answer = message.content

    return answer
```

#### 実際の自動処理の実行－1：プロンプトの意図が、発注の場合 ####

発注の意図を持つプロンプトをGPTに投入し、実際に自動処理を走らせてみました。
```:【発注】の意図のプロンプト
ワハハ株式会社にほんやくコンニャク50丁注文。青森支社第2営業部に直接。
```

##### 実際の流れ #####

この、ビミョーに雑で曖昧なプロンプトから、
GPTは、Function callingの下で、
・関数定義リストから、**プロンプトの意図【発注】に沿う最適な関数として、関数「order_item」を選択**
・関数「order_item」実行のための引数を作成
これらを、GPT呼び出し元に返す
↓
GPT呼び出し元は、**実体関数「order_item」を、GPTが作成した引数で実行**
↓
発注先のLineに発注依頼文が自動送信される

<details><summary>（折りたたみ）実際の流れをさらに詳細に（GPT担当部分のみ）</summary>

<strong>GPTの「きもち」になって</strong>、GPT担当部分のみ、詳細にstep by stepで記述します。

プロンプトが来たぞ。Function calling使えって指示がある。関数定義リスト受け取ったぞ。

**まずは、プロンプトを実現するのに最適な関数が関数定義リストにあるか、探そう。**
**関数定義リストの各関数のdescriptionとプロンプトを照らし合わせるぞ。**
プロンプト見てみよう。
「ワハハ株式会社にほんやくコンニャク50丁注文。青森支社第2営業部に直接。」
どこかの会社に、何か注文してるぞ。
関数「order_item」これか。descriptionが「商品を発注する。」ってなってるから。
この関数「order_item」が、プロンプトを実現するのに最適な関数、ということで決定。

**次は、この関数「order_item」を実行する時の引数を作るぞ。**
**関数定義リストの関数「order_item」の各引数のdescriptionとプロンプトを照らし合わせるぞ。**
「ワハハ株式会社にほんやくコンニャク50丁注文。」って言ってるから、
「ワハハ株式会社」が、引数「name_order_to」なんだろう。descriptionが「発注先の会社名」だから。
「ほんやくコンニャク」が、引数「name_item」なんだろう。descriptionが「発注する商品の商品名」だから。
「50」丁が、引数「quantity_item」なんだろう。descriptionが「発注する商品の数量」だから。
プロンプト最後の「青森支社第2営業部に直接。」これ曖昧で悩むな。でもきっと、「青森支社第2営業部に直接届けろ。」ていう意味なんだろう。
「青森支社第2営業部」が、引数「delivery_location」なんだろう。descriptionが「発注する商品の配達先」だから。
関数「order_item」を実行する時の引数もこれで決定。

よし、これで、
プロンプトを実現するのに最適な関数と、
その関数を実行する時の引数
が決まった。
呼び出し元に返そう。

</details>

##### 結果 #####

**意図通りに、発注先のLineに、以下のように整形された発注依頼文が自動送信されました。**
あのビミョーに雑で曖昧なプロンプトから、**GPTが関数の引数をバッチリ正しく作成していた**ことがわかります。
![使用法-1_結果_発注.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/a8d65269-73aa-c882-dd48-cc330d162b8c.png)

#### 実際の自動処理の実行－2：プロンプトの意図が、予定の登録の場合 ####

カレンダーへの予定の登録の意図を持つプロンプトをGPTに投入し、実際に自動処理を走らせてみました。
見やすくなるように、ここでは本来のプロンプトには無い改行を加えて表示しています。

```:【予定の登録】の意図のプロンプト（プロンプト投入時は2023年7月）
カレンダーに予定登録。件名は、臨時・仕入担当者会議。急激な物価高により、仕入の仕方を
根本的に見直すことになった。各自、資料に目を通しておくように！24日の午後4時から6時まで。
```

##### 実際の流れ #####

この、またもビミョーに雑で曖昧なプロンプトから、
GPTは、Function callingの下で、
・関数定義リストから、**プロンプトの意図【予定の登録】に沿う最適な関数として、関数「write_appointment_on_calendar」を選択**
・関数「write_appointment_on_calendar」実行のための引数を作成
これらを、GPT呼び出し元に返す
↓
GPT呼び出し元は、**実体関数「write_appointment_on_calendar」を、GPTが作成した引数で実行**
↓
Google Calendarに予定が自動で登録される

##### 結果 #####

**意図通りに、Google Calendarに、以下の予定が自動で登録されました。**
ここでも、あのビミョーに雑で曖昧なプロンプトから、**GPTが関数の引数をバッチリ正しく作成していた**ことがわかります。
![使用法-1_結果_予定登録_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/7d8f53c1-69bf-808b-9c5e-6bcc843b6030.png)


## ＜補足＞プロンプトに対する応答が必要な場合 ##

### 要点 ###

今までの使用例では、GPT呼び出し元が「（Line送信みたいな）何らかの機械的処理をする」関数を実行して、完結していました。
この場合、プロンプトに対して何も応答はありません。

しかし、質問→GPT→回答のように、プロンプトに対する応答を以て完結する、というのも勿論あります。
このようなもののうち、
**関数の実行結果がそのままではプロンプトに対する答として使用できない**
ものがあります。

例）自社の商品についての質問のプロンプトに答える
自社の商品情報DBから所望の商品情報をJSON文字列で返す関数を実行し、プロンプト中の当該商品の商品情報を得たが、
**その商品情報は、（「商品名」や「価格」などの）複数の属性で構成されたJSON文字列でしかなく、そのままではプロンプトに対する答にならない。**

このような場合、
**元々のプロンプトと一緒に関数の実行結果をGPTに投入し、元々のプロンプトに対する答を引き出す**
ということを行います。
（プロンプトに対する答になるように、**関数の実行結果をGPTに”精製（refine）”させる**イメージ）

つまり、**GPTへの問い合わせは2回**となります。
1回目：（今まで説明してきたこと）実行すべき関数と引数をGPTに問い合わせ
**2回目：プロンプトに対する答をGPTに問い合わせ（関数の実行結果をGPTに投入して）**

![補足_GPTの「応答」を要する場合_流れ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613026/22c23c2b-e2ab-4b4a-486f-f188757d5cce.png)

以下、簡単な実装例で示します。

### 【実装例】商品情報を検索し、自然言語で応答 ###

ここでは、
**商品についての何らかの問い合わせのプロンプトが来ると、
その商品の情報を検索し、
その検索結果である商品情報（JSON文字列）を、GPTがこなれた自然言語にして答える**
という簡単な実装をします。

商品名をキーにして、該当する商品の複数の属性（名前とか価格とか商品説明とか）を商品情報としてJSON文字列で返す関数を考えます。
**JSON文字列での実行結果では、自然言語でのプロンプトへの応答には、そのままでは使えません。**
例えば、プロンプトが「商品「hoge」の価格は？」だとしましょう。
このようなプロンプトの時に、
**・関数の実行結果である商品「hoge」の商品情報が持つ複数の属性から価格の属性のみを抽出し
・「商品「hoge」の価格は・・・円です。」のような自然言語で
応答する**、ということを、**手間暇かけずに**やりたいわけです。

#### 関数の定義リストと、関数の実体 ####

##### 関数の定義リスト #####

GPTに最初にプロンプトを投入する際（GPTへの問い合わせ1回目）、一緒に投入されます。

```python:関数の定義リスト
functions2 =  \
[
    {
        # 関数名
        "name": "get_item_info",
        # 関数の説明
        "description": "商品の情報を取得",
        # 関数の引数の定義
        "parameters":
         {
            "type": "object",
            "properties":
            {
                "name_item":
                {
                    "type": "string",
                    "description": "商品名",
                }
            },
            "required": ["name_item"]
         }
    }
]
```

##### 関数の実体 #####

ここでは、ベタに商品情報をハードコーディングし、商品情報DBをその場で作ります。
そして、商品名をキーに、商品情報DBからその商品情報を検索して返します。
```python:関数の実体
# 商品情報を取得する実体関数
def get_item_info(name_item):

    # 商品名をもとに、商品情報DBを検索し、その商品の商品情報を返す。
    # ここでは、商品情報DBはDictionaryで構築する。
    # キーとなる商品名で、Dictionary内のitemを検索する。
    # 商品情報はJSON文字列で返す。

    # 商品情報DBをこの場で作成（3商品しかない）
    item_0 = \
     {"名前":"どこでもドア", "価格":"2980円", 
      "説明":"ドアを開ければ、その先は、あなたが行きたいところ。" }
    item_1 = \
     {"名前":"ほんやくコンニャク", "価格":"300円", 
      "説明":"見た目も味もただのコンニャクなのに、これを食べると、言語が通じない相手でも、それを意識せず会話できます。" }
    item_2 = \
     {"名前":"タケコプター", "価格":"1280円", 
      "説明":"見た目は竹とんぼ。頭に乗っけただけでヘリコプターのように回転し、どこへでも飛んで行けます。" }
    items[item_0["名前"]] = item_0
    items[item_1["名前"]] = item_1
    items[item_2["名前"]] = item_2

    # 商品名をキーに商品情報を検索
    item_info = items.get(name_item)
    if item_info is not None:
        # JSON文字列に
        ret = json.dumps(item_info, ensure_ascii=False)
    else:
        # そういう商品名の商品は無い
        ret = None
    
    return ret

```

#### 全体～プロンプト投入からGPTの応答まで ####

[ GPTへの問い合わせ1回目 ]では、
今まで説明してきた通りで、
GPTにプロンプト「prompt」を投入→
GPTがそのプロンプトから商品情報を取得する関数「get_item_info」の実行を指示し引数を生成→
呼び出し元はその実体関数「get_item_info」をその引数で実行→
実際にJSON文字列の商品情報「function_response」を取得
します。

[ GPTへの問い合わせ2回目 ]で、
・元々のプロンプト「prompt」
・取得したJSON文字列の商品情報「function_response」
を一緒にGPTに投入し、2回目のGPT問い合わせを行います。
この問い合わせに対する応答「answer」は、元々のプロンプト「prompt」に対する自然言語での応答となっています。
```python:全体～プロンプト投入からGPTの応答まで
# 前準備　API_KEY文字列を渡してclientオブジェクト生成
from openai import OpenAI
client = OpenAI(api_key="xxxxx") # 本当はOS環境変数を使用してAPI_KEY文字列を渡した方が良い

# プロンプトを受け付けてGPTに投入する。
# その際、Function callingを使用して、実体関数を実行して商品情報（JSON文字列）を取得する。
# 取得した商品情報（JSON文字列）をGPTに投げて、自然言語での応答を引き出す。
def function_calling_demo2(prompt):
    
    # [ GPTへの問い合わせ1回目 ]
    # GPTにプロンプトと関数定義リストを一緒に投入し、Function callingの使用を指示
    response = client.chat.completions.create(
        model="gpt-4-turbo-preview",  # モデル名を指定　これはGPT-4 Turbo previewの最新版のAlias
        temperature=0, # ここでは、GPTの答えに多様性を求めない
        messages=
    	[
    		{ "role": "user", "content": prompt}  # プロンプトを投入
    	],
        functions=functions2, # プロンプトと一緒に関数定義リストを投入
        function_call="auto", # Function callingを使用するが、その際、関数の選択はGPTに任せる
    )

    # GPTからの応答を抽出
    message = response.choices[0].message
    function_call = message.function_call

    if function_call is not None:
        # 関数定義リスト中の関数を実行すべきとGPTが判断

        # GPTが実行すべき関数であるとして選択した関数の名前
        function_name = function_call.name
        # その関数に渡すべき引数も、GPTが作成してくれる
        arguments = json.loads(function_call.arguments)
        
        # function_name（GPTが選択した関数の名前）に基づき、該当する実体関数を実行
	
        if function_name == "get_item_info":
            # 商品情報を取得する関数「get_item_info」使用をGPTが指示
            
            # GPTの指示に従い、実体関数「get_item_info」を実行            
            # 関数の実行結果「function_response」に、商品情報のJSON文字列がある
            function_response = get_item_info(arguments.get("name_item"))
            
            if function_response is None:
                # その商品名の商品は無い
                return "該当する商品を見つけることができませんでした。"

            # [ GPTへの問い合わせ2回目 ]
            # 商品情報のJSON文字列は、そのままではプロンプトへの答として使用できない
            # プロンプトへの答をGPTに作成させる
            # （関数の実行結果をGPTが精製しプロンプトへの答に）            
            response2 = client.chat.completions.create(
                model="gpt-4-turbo-preview",
                temperature=0, # ここでは、GPTの答えに多様性を求めない
                messages=
                [
                    {"role": "user", "content": prompt}, # 元々のプロンプト（質問）をここで再度投げている
                    {
                        "role": "function", # roleに"function"を指定
                        "name": function_name, # 1回目の問い合わせでGPTに指示され、呼び出し元で実行した関数名
                        "content": function_response, # 商品情報のJSON文字列「function_response」を渡す
                    },
                ],
            )

            # GPTの応答（プロンプトへの答として使用できるもの）
            answer = response2.choices[0].message.content 

    else:
    	# 【その他】
        # 関数定義リスト中のどの関数も実行すべきでない、とGPTが判断（それら関数とは無関係のプロンプト）
        # ごく普通の「GPTへの質問・依頼」→「GPTからの応答」
        answer = message.content

    return answer
```

#### 実際のGPTの応答　数例ほど ####


```:プロンプト-1
商品「タケコプター」の価格は？
```
```:プロンプト-1　に対するGPTの応答
商品「タケコプター」の価格は1280円です。
```
[ GPTへの問い合わせ1回目 ]の結果、
商品情報を取得する実体関数「get_item_info」が返してきたのは、
JSON文字列での商品情報
{"名前": "タケコプター", "価格": "1280円", "説明": "見た目は竹とんぼ。頭に乗っけただけでヘリコプターのように回転し、どこへでも飛んで行けます。"}
です。
[ GPTへの問い合わせ2回目 ]で、
**GPTは、このJSON文字列の商品情報内の「価格」を、簡にして要を得た短文にしてくれました。**
**⇒GPTが、プロンプトへの答になるように関数の実行結果を精製**

```:プロンプト-2
商品「ほんやくコンニャク」とは、どういうもの？
```
```:プロンプト-2　に対するGPTの応答（改行は筆者）
商品「ほんやくコンニャク」は、見た目や味は普通のコンニャクですが、食べると言語が通じない相手とも会話ができるという特徴を持っています。
この商品を食べることで、言葉の壁を感じずにコミュニケーションが取れるようになります。
価格は300円です。
```
[ GPTへの問い合わせ1回目 ]の結果、
商品情報を取得する実体関数「get_item_info」が返してきたのは、
JSON文字列での商品情報
{"名前": "ほんやくコンニャク", "価格": "300円", "説明": "見た目も味もただのコンニャクなのに、これを食べると、言語が通じない相手でも、それを意識せず会話できます。"}
です。
[ GPTへの問い合わせ2回目 ]で、
**GPTは、このJSON文字列の商品情報全体、特に「説明」を、イイ感じの説明文に仕立て上げてくれました。**
**⇒GPTが、プロンプトへの答になるように関数の実行結果を精製**

```:プロンプト-3
商品「どこでもドア」とは、どういうもの？
```
```:プロンプト-3　に対するGPTの応答（改行は筆者）
商品「どこでもドア」は、ドアを開けると、その先にあなたが行きたい場所へ瞬時に移動できるという特殊なドアです。
このドアを使えば、遠くの場所や難しい場所にも簡単にアクセスすることができます。
価格は2980円です。
```
[ GPTへの問い合わせ1回目 ]の結果、
商品情報を取得する実体関数「get_item_info」が返してきたのは、
JSON文字列での商品情報
{"名前": "どこでもドア", "価格": "2980円", "説明": "ドアを開ければ、その先は、あなたが行きたいところ。"}
です。
[ GPTへの問い合わせ2回目 ]で、
**ここでも、GPTは、このJSON文字列の商品情報全体、特に「説明」を、イイ感じの説明文に仕立て上げてくれました。**
**⇒GPTが、プロンプトへの答になるように関数の実行結果を精製**

```:プロンプト-4
商品「どこでもドア」のユースケースは？
```
```:プロンプト-4　に対するGPTの応答（改行は筆者）
商品「どこでもドア」のユースケースは、以下のようなものが考えられます。
1. 旅行業界: 旅行会社や航空会社は、「どこでもドア」を利用して、顧客にリアルな旅行体験を提供することができます。顧客は自宅から「どこでもドア」を通じて目的地に瞬時に移動し、現地の観光や文化を楽しむことができます。
2. 物流業界: 物流会社は、「どこでもドア」を利用して商品の配送を効率化することができます。商品を「どこでもドア」に入れると、目的地の「どこでもドア」から出てくる仕組みです。これにより、長距離輸送や国際物流の時間とコストを大幅に削減することができます。
3. レジャー業界: テーマパークやアミューズメント施設では、「どこでもドア」を利用して、顧客に新しいエンターテイメント体験を提供することができます。顧客は「どこでもドア」を通じて異なるエリアやアトラクションに瞬時に移動し、様々な楽しみを体験することができます。
4. 医療業界: 医療機関や救急医療サービスでは、「どこでもドア」を利用して緊急患者の移送を迅速に行うことができます。救急車が到着する前に「どこでもドア」を使用して患者を病院に瞬時に移動させることができます。
これらは一部の例であり、商品「どこでもドア」はさまざまな業界で利用される可能性があります。
```
[ GPTへの問い合わせ1回目 ]の結果、
実体関数「get_item_info」が返してきたのは、プロンプト-3同様に、
JSON文字列での商品情報
{"名前": "どこでもドア", "価格": "2980円", "説明": "ドアを開ければ、その先は、あなたが行きたいところ。"}
です。
[ GPTへの問い合わせ2回目 ]で、
**GPTは、このJSON文字列の商品情報内の「説明」を大いに膨らませて、ユースケースを何通りか編み出してくれました。**
**⇒GPTが、プロンプトへの答になるように関数の実行結果を精製、を通り越して、もはや”拡張”**