OpenAIのFunction Callingにおける関数呼び出しと文脈情報の重要性


# はじめに
OpenAIのFunction Callingを使う場合、用意した関数をChatGPTが想定通りに呼び出す方法を確認する。

# 結論
カーナビにユーザーが音声入力で短い問い合わせを行う場合など`適切な文脈情報`の入力が期待できない場合には想定通りに関数を呼び出すことが難しい場合がある。

OpenAIのFunction Callingで関数を呼び出す場合、入力文で`適切な文脈情報`を提供しなければならない。実際の製品やサービスを作成する場合には、ユーザーに不足している`適切な文脈情報`を問い合わせる仕組みや、ユーザーの入力に対して自動的に`適切な文脈情報`をセンサー情報などから取得し言語化して負荷する仕組みが必要となるだろう。

## Function Calling とは?
Function calling（関数呼び出し）は、OpenAIの言語モデルを使って、外部ツールやAPIと連携するための新機能でだ。例えば、チャットボットを作成して、外部ツールを呼び出して質問に回答させることができる。また、自然な言語を使ってAPI呼び出しやデータベースクエリを行ったり、テキストから構造化データを抽出したりすることも可能です。具体的な使い方としては、「メールを送信して、Anyaに来週の金曜日にコーヒーを飲みたいかどうか聞いて」というクエリを、send_email(to: string, body: string)という関数呼び出しに変換することができる。このように、関数呼び出しを利用することで、GPTをより柔軟に外部ツールと統合することができる。

https://platform.openai.com/docs/guides/gpt/function-calling

# Function Callingの呼び出しの調査概要

複数のFunctionを登録した場合に入力に応じた最適な関数が呼び出されているか確認する。

## 登録した関数

| 登録データ | 登録した関数 | 関数の説明 |
|:-:|:-:|:-|
| weather_function  | get_weather_info | 緯度と経度の情報から現在の天気を取得する  |
| carnavi_function  | get_carnavi_info  | インターネット接続機能を持ったカーナビの使用方法についての質問からカーナビの取り扱い説明書を取得する  |
| vehicle_function  | get_vehicle_info  |  車の使用方法についての質問から車の取り扱い説明書を取得する |

```python: 関数の登録
        response = openai.ChatCompletion.create(
            model=model,
            messages=[{"role": "user", "content": text}],
            functions=[weather_function, carnavi_function, vehicle_function],
            function_call="auto",
        )
```

```python: chatGPTが自動的に選択する関数
#緯度と経度の情報から現在の天気を取得する
def get_weather_info(latitude, longitude):
	return inspect.currentframe().f_code.co_name

weather_function = {
    "name": "get_weather_info",
    "description": "緯度と経度の情報から現在の天気を取得する",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {
                "type": "string",
                "description": "緯度の情報",
            },
            "longitude": {
                "type": "string",
                "description": "経度の情報",
            },
        },
        "required": ["latitude", "longitude"],
    },
}

#カーナビの使用方法についての質問からカーナビの使い方を取得する
def get_carnavi_info(query):
	return inspect.currentframe().f_code.co_name

carnavi_function = {
    "name": "get_carnavi_info",
    "description": "カーナビの使用方法についての質問からカーナビの取り扱い説明書を取得する",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "カーナビの使用方法についての質問",
            },
        },
        "required": ["query"],
    },
}

#車の使用方法についての質問から車の使い方を取得する
def get_vehicle_info(query):
	return inspect.currentframe().f_code.co_name

vehicle_function = {
    "name": "get_vehicle_info",
    "description": "車の使用方法についての質問から車の取り扱い説明書を取得する",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "車の使用方法についての質問",
            },
        },
        "required": ["query"],
    },
}
```


## 動作確認に使う入力文
入力文とその入力文に対してChatGPTに呼び出してほしい関数のセットを用意した

```python: 入力文と呼び出して欲しい関数のセット
queries = [
    ["織田信長について10文字で答えよ", "chatgpt"],
	["横浜の今日の天気を教えてください", "get_weather_info"],
	["インターネットを使いたい", "get_carnavi_info"],
	["ガソリンの蓋が左右どちらにあるのか教えて", "get_vehicle_info"],
]
```
```python: 実行結果
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[False] 期待:carnavi_function, 実際:chatgpt, 質問:インターネットを使いたい
[False] 期待:get_vehicle_info, 実際:chatgpt, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```
## ２つの入力文に対して想定した関数が呼び出されなかった理由

２つの入力文が`False`になった理由をChatGPTに確認させた。

:::note
適切な文脈情報を提供することでChatGPTは質問の目的や関連する情報をより正確に理解できる。
つまりユーザーの短い質問文に追加して、現在の状況や対象としている機器などを具体的に入力する必要がある
:::

```markdown:ChatGPTに問い合わせ
記の入力文をChatGPTに入力した場合、「カーナビの使用方法についての質問」だとChatGPTが理解しない。入力の問題点と解決策を提示せよ。
入力文：インターネットを使いたい
```

```markdown:ChatGPTの回答
入力文の問題点は、「カーナビの使用方法についての質問」という明確なトピックと関連性がないことです。ChatGPTは、前提となる文脈やトピックに基づいて応答するため、明確な要求がない場合は一般的な情報を提供する傾向があります。
解決策としては、次のような修正が考えられます：
1. カーナビに関する具体的な質問を追加する: 例えば、「カーナビで目的地を設定する方法を教えてください」といった具体的な質問を追加することで、ChatGPTに対して明確なトピックを伝えることができます。
2. 質問の文脈を追加する: カーナビの使用方法について質問する前提となる文脈を追加することで、ChatGPTにトピックの関連性を理解させることができます。例えば、「最近、カーナビを購入しましたが、まだ使い方がよくわかりません。カーナビの操作方法について教えていただけますか？」といった形で文脈を提供します。
これらの修正を行うことで、ChatGPTはカーナビの使用方法についての質問を適切に理解し、適切な応答を返すことができるでしょう。
```



```markdown:ChatGPTに問い合わせ
下記の入力文をChatGPTに入力した場合、「車の使用方法についての質問」だとChatGPTが理解しない。入力の問題点と解決策を提示せよ。
入力文：ガソリンの蓋が左右どちらにあるのか教えて
```

```markdown:ChatGPTの回答
入力の問題点: 入力文「ガソリンの蓋が左右どちらにあるのか教えて」という文だけでは、ChatGPTに十分な文脈が与えられておらず、質問の背景や関連情報が不明です。そのため、質問が「車の使用方法についての質問」であることを正確に理解することは難しいです。
解決策: 文脈を明示する追加の情報を提供することで、ChatGPTが質問の意図を正確に理解できるようになります。以下は修正例です:
修正後の入力文: 「私は初めて車を運転しようとしています。ガソリンの蓋が左右どちらにあるのか教えていただけますか？」
修正後の入力文では、「初めて車を運転しようとしている」という文脈が与えられています。これにより、ChatGPTは質問が「車の使用方法についての質問」であることを把握しやすくなります。
適切な文脈情報を提供することで、ChatGPTは質問の目的や関連する情報をより正確に理解できます。このような修正を行うことで、より適切な回答やアドバイスを生成することが期待できます。
```

### 修正例１（質問:インターネットを使いたい）
この問いが`False`になった理由は、ユーザーがカーナビでインターネットを使いたいとChatGPTが想定しないことが原因だろう。インターネット対応のカーナビはまだ一般的ではない。今回はカーナビでインターネットを使いたいと明示する。

```python: 「カーナビで」 と明示する
queries = [
	["カーナビでインターネットを使いたい", "get_carnavi_info"],
]
```

```
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:カーナビでインターネットを使いたい
```

:::note
カーナビでと対象を明示することでカーナビに関する問い合わせだとChatGPTは認識した
:::


### 修正例２（質問:ガソリンの蓋が左右どちらにあるのか教えて）

`適切な文脈情報`を入力文を変更せずに問題解決する方法を考える。ユーザーの状況をChatGPTに推測させ、その情報を追加してChatGPTに関数を選択させてみる。

```python: ChatGPTに状況を状況を推測させるプロンプト
guess_prompt = '''
あなたは車の運転中です。
車にはカーナビがセットアップされています。
車のカーナビにはインターネット接続機能があります。
車のカーナビにはWebブラウザがインストールされています。
次の入力文からどのような状況にあるか50文字以内で答えよ
入力文:{}
'''
```

`ChatGPTに状況を状況を推測させるプロンプト` + `入力文` をChatGPTに与えて、ユーザーがいまどのような状況なのかを推測させてみた。
| 入力文 | ChatGPTに状況を推測させた結果 |
|:-|:-|
| 織田信長について10文字で答えよ  |  車の運転中で、カーナビのインターネット接続機能を使って織田信長について調べている。 |
| 横浜の今日の天気を教えてください  |  運転中でカーナビでインターネット接続し、Webブラウザから横浜の今日の天気を検索中 |
| インターネットを使いたい  | 車の運転中で、カーナビにインターネット接続機能とWebブラウザがあり、インターネットを使用したい状況です。 |
| ガソリンの蓋が左右どちらにあるのか教えて  |  カーナビのWebブラウザで検索し、ガソリンの蓋の位置を調べている状況。 |

今回の場合は推測した想定内容には適切でないもの多くChatGPTの関数選択結果はイマイチであった。この手法はもう少し深く調査してみたい。

```terminal: １度目
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:インターネットを使いたい
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```
```terminal: ２度目
[False] 期待:chatgpt, 実際:get_carnavi_info, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[False] 期待:get_carnavi_info, 実際:chatgpt, 質問:インターネットを使いたい
[False] 期待:get_vehicle_info, 実際:chatgpt, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```
```terminal: 3度目
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[False] 期待:get_carnavi_info, 実際:chatgpt, 質問:インターネットを使いたい
[False] 期待:get_vehicle_info, 実際:chatgpt, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```

### 修正例3 （質問:ガソリンの蓋が左右どちらにあるのか教えて）
入力文をChatGPTに与える時のプロンプトを工夫することで対処を試みる。
```python: プロンプトで状況説明をChatGPTに与える
template ='''
あなたは車の運転中です。
あなたはカーナビでインターネットが利用できます
あなたはカーナビでWebブラウザが利用できます
次の条件を踏まえて入力文について答えよ。

条件:
- 50文字以内で回答せよ

入力文:
{}
'''
```

そこそこ良さそうな結果に見える。この修正案を適用して確認を進める。

```terminal: 1回目
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[False] 期待:get_carnavi_info, 実際:chatgpt, 質問:インターネットを使いたい
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```
```terminal: 2回目
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:インターネットを使いたい
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```
```terminal: 3回目
[True] 期待:chatgpt, 実際:chatgpt, 質問:織田信長について10文字で答えよ
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:横浜の今日の天気を教えてください
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:インターネットを使いたい
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:ガソリンの蓋が左右どちらにあるのか教えて
```

## 修正結果の確認

ChatGPTに入力文を用意させて関数呼び出しを確認した。

```python:ChatGPTに作成させたテスト用入力文
queries = [
	["燃料タンクの容量は？", "get_vehicle_info"],
	["オイル交換時期は？", "get_vehicle_info"],
	["タイヤの適切な空気圧は？", "get_vehicle_info"],
	["バッテリーの寿命は？", "get_vehicle_info"],
	["最新地図データはどこで入手できますか？", "get_carnavi_info"],
	["目的地設定方法は？", "get_carnavi_info"],
	["渋滞情報の表示方法は？", "get_carnavi_info"],
	["Bluetooth接続方法は？", "get_carnavi_info"],
	["明日の天気は？", "get_weather_info"],
	["現在の気温は？", "get_weather_info"],
	["週末の天気予報は？", "get_weather_info"],
	["都市Aの明後日の降水確率は？", "get_weather_info"],
	["最近の科学ニュースは？", "chatgpt"],
	["好きな映画は何ですか？", "chatgpt"],
	["人工知能の仕組みは？", "chatgpt"],
	["ヘルシーなレシピを教えてください", "chatgpt"]
]
```

３回試行し、それぞれの入力文に対応した関数が想定通りに呼び出された回数をTrueにまとめた。
`現在の気温は?`や`人工知能の仕組みは`、`人工知能の仕組みは`といった、ChatGPTが適切にやってくれそうな入力文に対しても関数呼び出しに失敗していることが確認できて面白い。

| 入力文 | True | False |
|:-:|:-:|:-:|
|燃料タンクの容量は？|  3 |  0 |
| オイル交換時期は？  | 3  | 0  |
| タイヤの適切な空気圧は？  | 3  | 0  |
| バッテリーの寿命は？  | 3  | 0  |
|最新地図データはどこで入手できますか？| 3  | 0  |
|目的地設定方法は？|  3 | 0  |
|渋滞情報の表示方法は？| 3  | 0  |
|Bluetooth接続方法は？| 3  | 0  |
|明日の天気は？|  3 |0   |
|現在の気温は？| 0  | 3  |
|週末の天気予報は？| 3  | 0  |
|都市Aの明後日の降水確率は？|3   | 0  |
|最近の科学ニュースは？| 1  | 2  |
|好きな映画は何ですか？| 3  | 0  |
|人工知能の仕組みは？| 0  | 3  |
|ヘルシーなレシピを教えてください| 0  | 3  |

```terminal: 1回目
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:燃料タンクの容量は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:オイル交換時期は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:タイヤの適切な空気圧は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:バッテリーの寿命は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:最新地図データはどこで入手できますか？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:目的地設定方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:渋滞情報の表示方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:Bluetooth接続方法は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:明日の天気は？
[False] 期待:get_weather_info, 実際:chatgpt, 質問:現在の気温は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:週末の天気予報は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:都市Aの明後日の降水確率は？
[True] 期待:chatgpt, 実際:chatgpt, 質問:最近の科学ニュースは？
[True] 期待:chatgpt, 実際:chatgpt, 質問:好きな映画は何ですか？
[False] 期待:chatgpt, 実際:get_vehicle_info, 質問:人工知能の仕組みは？
[False] 期待:chatgpt, 実際:get_vehicle_info, 質問:ヘルシーなレシピを教えてください
```

```terminal: 2回目
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:燃料タンクの容量は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:オイル交換時期は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:タイヤの適切な空気圧は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:バッテリーの寿命は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:最新地図データはどこで入手できますか？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:目的地設定方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:渋滞情報の表示方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:Bluetooth接続方法は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:明日の天気は？
[False] 期待:get_weather_info, 実際:chatgpt, 質問:現在の気温は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:週末の天気予報は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:都市Aの明後日の降水確率は？
[False] 期待:chatgpt, 実際:get_carnavi_info, 質問:最近の科学ニュースは？
[True] 期待:chatgpt, 実際:chatgpt, 質問:好きな映画は何ですか？
[False] 期待:chatgpt, 実際:get_vehicle_info, 質問:人工知能の仕組みは？
[False] 期待:chatgpt, 実際:get_carnavi_info, 質問:ヘルシーなレシピを教えてください
```

```terminal: 3回目
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:燃料タンクの容量は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:オイル交換時期は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:タイヤの適切な空気圧は？
[True] 期待:get_vehicle_info, 実際:get_vehicle_info, 質問:バッテリーの寿命は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:最新地図データはどこで入手できますか？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:目的地設定方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:渋滞情報の表示方法は？
[True] 期待:get_carnavi_info, 実際:get_carnavi_info, 質問:Bluetooth接続方法は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:明日の天気は？
[False] 期待:get_weather_info, 実際:chatgpt, 質問:現在の気温は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:週末の天気予報は？
[True] 期待:get_weather_info, 実際:get_weather_info, 質問:都市Aの明後日の降水確率は？
[False] 期待:chatgpt, 実際:get_carnavi_info, 質問:最近の科学ニュースは？
[True] 期待:chatgpt, 実際:chatgpt, 質問:好きな映画は何ですか？
[False] 期待:chatgpt, 実際:get_vehicle_info, 質問:人工知能の仕組みは？
[False] 期待:chatgpt, 実際:get_carnavi_info, 質問:ヘルシーなレシピを教えてください
```

# 今回調査に利用したソースコード
https://github.com/aRaikoFunakami/openai_function_calling/blob/main/non_streaming_function_calling.py