見出し画像

ChatGPT APIの新機能 Function calling についてTodoリストを使って実践的に紹介

6月13日から、Function calling と呼ばれる新機能により、ユーザー(開発者)が ChatGPT Plugin のような機能を比較的簡単に実装することができるようになりました。
ただし、関数の実行自体は開発者側で制御する必要があります。

最新のモデル(gpt-3.5-turbo-0613とgpt-4-0613)は、(入力に応じて)関数を呼び出すべきタイミングを検出し、関数に準拠したJSONで応答するようになっています。

この機能には、潜在的なリスクも伴います。ユーザーの代わりに世界に影響を与えるアクション(電子メールの送信、オンラインへの投稿、購入など)を行う前に、ユーザー確認フローを構築することを強くお勧めします。

関数呼び出しにより、モデルから構造化されたデータをより確実に取得することができます。例えば、次のようなことができます:

  • 外部API(ChatGPT Pluginsなど)を呼び出して質問に答えるチャットボットを作成する。

    • 例えば、send_email(to: string, body: string) や get_current_weather(location: string, unit: 'celsius' | 'fahrenheit') などの関数を定義します。

  • 自然言語をAPIコールに変換する

    • 例)"Who are my top customers?" を get_customers(min_revenue: int, created_before: string, limit: int) に変換し、内部APIを呼び出す。

  • テキストから構造化データを抽出する

    • 例えば、extract_data(name: string, birthday: string) や sql_query(query: string)という関数を定義します。

Function calling の基本的な流れは以下の通りです:

  1. ユーザクエリと同時にfunctionsパラメータに関数の説明を渡してモデルを呼び出します。

  2. モデルが関数を呼び出すことを選択します。呼び出す場合には、返答はカスタムスキーマに準拠した文字列化されたJSONオブジェクトになります(注意:モデルは無効なJSONを生成したり、ハルシネーションの可能性があります)。

  3. コード内で文字列をJSONにパースし、提供された引数がある場合はそれを使って関数を呼び出します。

  4. 関数の応答を新しいメッセージとして追加してモデルを再度呼び出し、モデルに結果を要約させてユーザーに返します。

簡単な Todo list の例

以下の例が実際に Todo list でやってみたFunction calling の実装例です。
add_todo と remove_todo という Python関数を実装していますが、これはあくまでダミーであり、本来はAPIなどが対応することになります。

import openai
import json


openai.api_key = "YOUR KEY GOES HERE"


todo_list = ['todo']


def add_todo(todo):
    todo_list.append(todo)
    todo_info = {
        "status": "success",
        "todo": todo,
    }
    return json.dumps(todo_info)


def remove_todo(todo):
    todo_list.remove(todo)
    todo_info = {
        "status": "success",
        "todo": todo,
    }
    return json.dumps(todo_info)


# Step 1, ユーザクエリと同時にfunctionsパラメータに関数の説明を渡してモデルを呼び出し
def run_conversation():
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[{"role": "user", "content": "ティッシュを買うのを追加して"}],
        functions=[
            {
                "name": "add_todo",
                "description": "Add a todo to the list",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "todo": {
                            "type": "string",
                            "description": "a todo to add",
                        },
                    },
                    "required": ["todo"],
                },
            },
            {
                "name": "remove_todo",
                "description": "Remove a todo from the list",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "todo": {
                            "type": "string",
                            "description": "a todo to remove",
                        },
                    },
                    "required": ["todo"],
                },
            },
        ],
        function_call="auto",
    )

    message = response["choices"][0]["message"]
    print(message)

    # Step 2, モデルが関数を呼び出そうとしているか否かを確認
    if message.get("function_call"):
        function_name = message["function_call"]["name"]

        todo = json.loads(message["function_call"]["arguments"])["todo"]

        # Step 3, 実際に関数を呼び出す
        if function_name == "add_todo":
            function_response = add_todo(todo=todo)
        elif function_name == "remove_todo":
            function_response = remove_todo(todo=todo)

        print(todo_list)


        # Step 4, 実行結果を元に最終的なレスポンスを得る
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=[
                {"role": "user", "content": "ティッシュを買うのを追加して"},
                message,
                {
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                },
            ],
        )
        return second_response


print(run_conversation()["choices"][0]["message"]["content"])
# 「ティッシュを買う」が追加されました。

解説

上記のコードで無事に、[ティッシュを買うのを追加して]
というユーザーの入力に対して、
[「ティッシュを買う」が追加されました。]
というChatGPTレスポンスが最終的に返答されました。

「ティッシュを買う」というタスクをしっかりとChatGPTが抽出できていることがわかります。

ステップに分けると上で説明した通り、以下のようになります。

  1. [ティッシュを買うのを追加して] というユーザー入力に追加して、add_todo, remove_todo という関数の説明を functions パラメータに渡して ChatGPT API を呼び出します

  2. message.get("function_call") によって、関数呼び出しが必要かどうかを判定します

  3. message["function_call"]["name"] によって、実行すべき関数名を取得し、(この場合、add_todo が中に入っています)実際に関数を実行します

  4. 関数情報を含んだmessageと、step3 で実行した関数のレスポンス"role": "function"で渡して最終的な結果を ChatGPT API から取得します。

関数情報を含んだmessageには、具体的には、以下の情報が含まれます:

{
  "content": null,
  "function_call": {
    "arguments": "{\n\"todo\": \"ティッシュを買う"\n}",
    "name": "add_todo"
  },
  "role": "assistant"
}

role が assistant であるということがわかります。

Functions パラメータの渡し方について

Functions パラメータは、関数の説明です。
今回使用したパターンでは以下のようなJSONになっています:

{
    "name": "add_todo",
    "description": "Add a todo to the list",
    "parameters": {
         "type": "object",
          "properties": {
              "todo": {
                  "type": "string",
                  "description": "a todo to add",
              },
         },
         "required": ["todo"],
    },
}

name に関数名、description に説明、parameters で、受け付けるpropertiesおよびそのタイプや説明、 required に必須の引数を指定することができます。また、公式のブログを見る限り、propertiesでは、以下のように、Enum を定義できたりする柔軟性があるようです。
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }


他にも、色々と細かい設定がありますので、
より詳しくは公式のドキュメントを参考ください:


最後に

私たち(@ctgptlb)はこれからも、皆さんがAIとの対話を通じて新たな発見や学びを得られるよう、最新の情報と知識を提供してまいります。この革新的なテクノロジーの最前線に立つ機会をお見逃しなく!

追記
普通は出来ませんが、実装を工夫する事で同時に二つの関数を実行する方法を編み出しました。


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!