OpenAI APIのFunction callingを速攻検証

# はじめに

昨日公表されたOpenAI APIのFunction callingをGoogle Colaboratoryで試してみました
 **この機能ずっと実現されないかな〜** と前から思ってました。

簡単に言いますと人間の言葉から**与えられた関数の仕様に適合するパラメータを抽出して作成してもらえる**という機能ですね。
言葉によるシステム操作インターフェースが比較的容易に作れるようになります。
たとえばSlackなどの**チャットツール向けのボットアプリとはとても相性の良い機能**と言えると思います。

# 検証

早速やってみましょう！
まずは[昨日のOpenAI発表ページ](https://openai.com/blog/function-calling-and-other-api-updates)にあったサンプルをそのままPythonに焼き直してColabで実行します。
OpenAIのAPIキーは各自でご用意ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123439/ef31e67e-4861-fcbf-3990-38c0ef27061d.png)

`functions`というパラメータを渡すことにより
AIにコールしたい関数の仕様を教えてあげることになります
人間の言葉から関数の仕様に適合した関数のパラメータを作成してくれます

```python
import openai
import json

openai.api_key = "APIキー"

messages = []
message = {"role": "user", "content": "What is the weather like in Boston?"}
messages.append(message)

functions = []
function = {
      "name": "get_current_weather",
      "description": "Get the current weather in a given location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "The city and state, e.g. San Francisco, CA"
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"]
          }
        },
        "required": ["location"]
      }
    }

functions.append(function)

result = openai.ChatCompletion.create(model="gpt-3.5-turbo-0613", messages=messages, functions=functions)
ai_response = result.choices[0].message.function_call
decoded_arguments = json.loads(ai_response["arguments"])
print(ai_response)
print(decoded_arguments)

```
返却された内容
```
{
  "name": "get_current_weather",
  "arguments": "{\n  \"location\": \"Boston, MA\"\n}"
}
{'location': 'Boston, MA'}
```
このように人間の言葉から抽出したデータをJSON形式で返してくれるので
プログラム内での取り扱いが容易になります

次に日本語への対応を検証してみました

```python
import openai
import json

openai.api_key = "APIキー"

messages = []
message = {"role": "user", "content": "ニューヨークの天気と気温を摂氏で教えてください"}
messages.append(message)

functions = []
function = {
      "name": "get_current_weather",
      "description": "Get the current weather in a given location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "都市や地域の名前（日本語）"
          },
          "unit": {
            "type": "string",
            "enum": ["摂氏", "華氏"]
          }
        },
        "required": ["location"]
      }
    }

functions.append(function)

result = openai.ChatCompletion.create(model="gpt-3.5-turbo-0613", messages=messages, functions=functions)
ai_response = result.choices[0].message.function_call
decoded_arguments = json.loads(ai_response["arguments"])
print(ai_response)
print(decoded_arguments)
```

返却された内容
```
{
  "name": "get_current_weather",
  "arguments": "{\n\"location\": \"\u30cb\u30e5\u30fc\u30e8\u30fc\u30af\",\n\"unit\": \"\u6442\u6c0f\"\n}"
}
{'location': 'ニューヨーク', 'unit': '摂氏'}
```
最初そのまま日本語の質問に変えたら英語で返されたので
`properties`の`description`を日本語に書き換えたら日本語で返るようになりました。
この辺は`functions`内の各種プロパティを調整しながら試していく感じになるかなと思いますね。

ここでは試していないですが複数の関数をパラメータに設定できそうなので
関数自体の呼び分けも制御できそうに見えますね。

# おわりに

重要なこととして関数を直接コールすることはさすがにできないのですが
コールする関数の判断と引数データを抽出してもらえるので動的な処理制御が可能になります。

これからいろんなアイデアが出てきて面白いアプリが沢山誕生する期待がありますね。

# おまけ（20230617追記）

> ここでは試していないですが複数の関数をパラメータに設定できそうなので
関数自体の呼び分けも制御できそうに見えますね。

こちらがどうしてもキニナル！なので試してみました。

```python
import openai
import json

openai.api_key = "APIキー"

messages = []
message = {"role": "user", "content": "質問内容"}
messages.append(message)

functions = []
function = {
      "name": "get_current_weather",
      "description": "Get the current weather in a given location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "都市や地域の名前（日本語）"
          },
          "unit": {
            "type": "string",
            "enum": ["摂氏", "華氏"]
          }
        },
        "required": ["location"]
      }
    }

functions.append(function)

function2 = {
      "name": "get_postal_code",
      "description": "Get the postal code in a given location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "都市や地域の名前（日本語）"
          }
        },
        "required": ["location"]
      }
    }
functions.append(function2)

result = openai.ChatCompletion.create(model="gpt-3.5-turbo-0613", messages=messages, functions=functions)
ai_response = result.choices[0].message.function_call
decoded_arguments = json.loads(ai_response["arguments"])
print(message)
print(ai_response)
print(decoded_arguments)

```

`function2`を`functions`に新しく追加しました。
質問内容によって適切な関数が選択され正しいパラメータが取得できるのか検証しました。

質問１
```
{'role': 'user', 'content': 'ニューヨークの天気と気温を摂氏で教えてください'}
{
  "name": "get_current_weather",
  "arguments": "{\n  \"location\": \"\u30cb\u30e5\u30fc\u30e8\u30fc\u30af\",\n  \"unit\": \"\u6442\u6c0f\"\n}"
}
{'location': 'ニューヨーク', 'unit': '摂氏'}
```

質問２
```
{'role': 'user', 'content': '千代田区の郵便番号を教えてください'}
{
  "name": "get_postal_code",
  "arguments": "{\n\"location\": \"\u5343\u4ee3\u7530\u533a\"\n}"
}
{'location': '千代田区'}
```

見事に`get_current_weather`と`get_postal_code`の選択が出来てますね！
よって以降はこのような感じでコードを発展させることが可能ですね。

```python
def get_current_weather(location, unit):
  print(location + unit)

def get_postal_code(location):
  print(location)

name = ai_response["name"]
arguments = json.loads(ai_response["arguments"])

if name == "get_current_weather":
  get_current_weather(arguments["location"], arguments["unit"])
elif name == "get_postal_code":
  get_postal_code(arguments["location"])
```


あとは気になることとして`functions`のラインアップを増やしていった場合の応答時間の影響やプロンプトが複雑化した場合の正確性については実装しながら検証していくことになると思います。