OpenAI APIのfunction callingを使ってみる

## はじめに

ChatGPTを擁するOpenAI社のAPI「**OpenAI API**」を利用したアプリ制作を行う中で、新しい機能である「function calling」を利用する機会がありました。
今回は備忘録として使い方を書いていきます。


## function callingは何が出来るのか

今年の6月頃にOpenAI APIに搭載された新機能で、ざっくりいうと「ユーザーの入力内容に応じてやってほしい処理をAIが自動で判別して実行してくれる」ものです。

事前に「この場合はこんな操作をしてほしい」という設計書のようなもの（関数）を作成します。
AIにユーザーが入力した文章を渡す際に、作成した関数を同時に渡すことで、AIが設計書の内容を理解してその通りの操作を行ってくれます。

簡単なフローチャートを以下に示します。

![flow.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535910/73676b94-d6f9-49d5-1a8a-096bb44b536a.png)

黄色の枠で囲ってある部分は、AIに処理を任せています。
ユーザーが入力した文章に図で示す「○○」があればそれを抽出して返してほしい！という関数を作成している想定です。

最終的なレスポンスはChatGPTのような生成された文章ではなく、関数から得られたJSON形式のデータになります。

関数内の操作を1度でも実行できれば、AIは必ずJSON形式のデータを返してくれるため、**そのデータを使って外部APIと連携した処理を実装することが簡単になる**というのがメリットの1つです。

自然言語処理(ChatGPT)と外部APIを組み合わせたいときに、データの「つなぎ役」を全うしてくれる機能といっても差し支えない（はず）です。

AIに渡すプロンプトを頑張って書けば、function callingを使わなくても同様の操作は可能です。
しかし、プロンプトが非常に長くなることや100%JSON型でデータを返してくれないという欠点があるため、function callingを利用する方がメリットが多いです。



## 開発環境

- Python3.11.4
- VScode
- OpenAI API



## 事前準備

事前準備として、OpenAIアカウントの作成とAPIキーの取得をする必要があります。

取得方法については[こちら](https://qiita.com/kotattsu3/items/b936a65d173a5d39dad0)が分かりやすいのでご参照ください。

また、`pip`等で`openai`ライブラリをインストールしてください。

```cmd
pip install openai
```



## 実際に触ってみた

今回は、AIにおすすめの曲を教えてもらう機能を実装する際にfunction callingを利用しました。

function callingを利用する前に、関数の設定をする必要があります。

AIに渡すシステムプロンプトも作成したいため、`config.py`ファイルにまとめて書きます。

```python:config.py
# システムプロンプト
system_prompt = '''
    あなたはユーザーに音楽を教えるAIです。
    ユーザーから送られた文章から、おすすめの音楽を教えてください。
    ユーザーから送られた文章は英語に変換してください。
'''

# 関数
functions = [
    {
        # 関数の名称
        "name": "search_music",
        # 関数の機能説明
        "description": "音楽を探す際に呼び出される。出力はすべて英語で行う",
        # 関数のパラメータ
        "parameters": {
            "type": "object",
            # 各引数
            "properties": {
                "genre": {
                    "type": "string",
                    # 引数の説明
                    "description": "探したい曲のジャンル"
                },
                "mood": {
                    "type": "string",
                    "description": "感情を表す言葉"
                },
                "artist": {
                    "type": "string",
                    "description": "探したいアーティスト名"
                },
                "title": {
                    "type": "string",
                    "description": "探したい曲のタイトル"
                }
            }
        }
    }
]
```

やたら長いですが、`properties`下が重要です。

関数の引数名とその役割・戻り値を設定します。AIはここに書かれている役割を理解し、入力に対応した関数の引数を呼び出す出力をします。今回は以下のような役割を持つ引数を設定しました。

| 引数名 | 役割                                   |
| ------ | -------------------------------------- |
| genre  | 音楽ジャンルがあったらそれを抜き出す   |
| mood   | 感情を表す言葉があったらそれを抜き出す |
| artist | アーティスト名があったらそれを抜き出す |
| title  | 曲名があったらそれを抜き出す           |



実際にユーザー側の入力を与えてみましょう。

下記コードの`function_test`内の`ChatCompletion`で、functionsパラメータに先ほど設定した関数を渡すことでfunction callingを利用することができます。試しに、「米津玄師みたいなアップテンポな曲が聞きたい」という入力をしてみます。

```python:app.py
import openai
from pprint import pprint
# config.pyファイルの内容をインポート
import config as cf

openai.api_key = "取得したAPIキー"


def function_test(input):
    res = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": cf.system_prompt},
            {"role": "user", "content": input},
        ],
        functions=cf.functions,
        function_call="auto",
    )
    return res['choices'][0]['message']

if __name__ == '__main__':
    ans = function_test("米津玄師みたいなアップテンポな曲が聞きたい")

    print(ans)
```



プログラムを実行すると、レスポンスは以下のようになりました。

```cmd
{
  "content": null,
  "function_call": {
    "arguments": "{\n  \"genre\": \"pop\",\n  \"mood\": \"upbeat\",\n  \"artist\": \"Kenshi Yonezu\"\n}",
    "name": "search_music"
  },
  "role": "assistant"
}
```

`function_call`内の要素を見てみると、「**genre : pop**」「**mood : upbeat**」「**artist : Kenshi Yonezu**」と関数で設定した引数の要素がおおむね正しく抜き出されていることが分かります。
ジャンルや感情の要素はAIが推論して抜き出してくれています。



もう一例見てみましょう。
次は「七尾旅人の『サーカスナイト』みたいな落ち着く曲が聞きたい。ジャズやアコースティックな曲が良い。」という入力をします。

プログラムを実行すると、レスポンスは以下のようになりました。

```cmd
{
  "content": null,
  "function_call": {
    "arguments": "{\n  \"artist\": \"Nanao Tabito\",\n  \"title\": \"Circus Night\",\n  \"mood\": \"calm\",\n  \"genre\": \"jazz\"\n}",
    "name": "search_music"
  },
  "role": "assistant"
}
```

`function_call`内の要素を見てみると、「**artist : Nanao Tabito**」「**Title: Circus Night**」「**mood : calm**」「**genre: jazz**」と関数で設定した引数の要素がおおむね正しく抜き出されていることが分かります。
ジャンルの要素でアコースティックが抜き出されていないこと以外は、アーティスト名も曲名もきちんと抜き出されています。

今回は割愛しますが、これで得られたデータを用いて、外部APIとの連携を行います。
アーティスト名や曲名、ジャンル名をSpotify APIにパラメータとして渡して楽曲データを取得し、ユーザーにおすすめ曲として紹介するレスポンスを返します。




## まとめ

このように、function callingを活用すると、ユーザーの曖昧な入力から必要な情報を勝手に取得してくれます。

取得精度の問題こそありますが、ユーザー側の柔軟な入力を実現し、なおかつ外部APIとの連携も非常に容易に出来る便利な機能です。

OpenAI APIでやれることの幅がとても広くなり、これまで以上に開発にも導入しやすくなったのではないかと思います。


## 参考

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

https://dev.classmethod.jp/articles/understand-openai-function-calling/