テックブログ

エルカミーの技術ブログです

Parallel Function Callingについて

はじめに

2023年11月6日のOpenAIの更新で、gpt-4-1106-previewgpt-3.5-turbo-1106の2つのモデルで1つのメッセージで複数の関数を呼び出すことが可能になりました。今回はParallel function callingについて、以前のモデルと比較したデモを踏まえて紹介します。

Parallel function callingについて

Parallel function callingは、複数の関数呼び出しを同時に実行し、これらの呼び出しとその結果を並列に処理できるモデルの機能です。

メッセージに含まれる特定の情報を取得して、その情報に応じて自動的に関数を決定して返答します。

例えば、3つの異なる場所の天気を取得する関数を同時に呼び出したいとします。この場合、モデルは1つのメッセージで複数の関数を呼び出します。そして、各ツール呼び出しのIDに一致するレスポンス内のtool_call_idを参照することで、各関数呼び出しの結果を返すことができます。

サンプルコード

今回利用したサンプルコードは以下の通りです。

モデルはgpt-4-1106-preview を使用しました。

呼び出す関数はget_current_weatherget_scheduleで、それぞれ特定の場所の天気を返すものと特定の人物のある日にちの予定を返すものです。

import openai
import json

openai.api_key = "OPENAIのAPIキーを入れる"


def get_current_weather(location, unit="華氏"):
    if "tokyo" in location.lower():
        return json.dumps({"location": "東京", "temperature": "10", "unit": "摂氏"})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "サンフランシスコ", "temperature": "72", "unit": "華氏"})
    elif "paris" in location.lower():
        return json.dumps({"location": "パリ", "temperature": "22", "unit": "摂氏"})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})


def get_schedule(date, person):
    if "Aさん" in person.lower():
        return json.dumps(
            {"date": "10月10日", "person": "Aさん",
                "schedule": "10時:A社とMTG、12時:友人Bとランチ"}
        )
    return json.dumps(
        {"date": date, "person": person, "schedule": "10時:A社とMTG、12時:友人Bとランチ"}
    )


def run_conversation(model, query):
    messages = [
        {"role": "user", "content": query}]
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "指定した場所の現在の天気を取得する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "都市と州(例:San Francisco, CA)",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "華氏"]},
                    },
                    "required": ["location"],
                },
            },
        },  {
            "type": "function",
            "function": {
                "name": "get_schedule",
                "description": "指定された日付と人物のスケジュールを取得",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "date": {"type": "string", "description": "日付"},
                        "person": {"type": "string", "description": "人の名前"},
                    },
                    "required": ["date", "person"],
                },
            },
        }
    ]
    response = openai.chat.completions.create(
        model=model,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls
    if tool_calls:
        available_functions = {
            "get_current_weather": get_current_weather,
            "get_schedule": get_schedule,
        }
        messages.append(response_message)
        for tool_call in tool_calls:
            if tool_call.function.name == "get_current_weather":
                function_name = tool_call.function.name
                function_to_call = available_functions[function_name]
                function_args = json.loads(tool_call.function.arguments)
                function_response = function_to_call(
                    location=function_args.get("location"),
                    unit=function_args.get("unit"),
                )
                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
            elif tool_call.function.name == "get_schedule":
                function_name = tool_call.function.name
                function_to_call = available_functions[function_name]
                function_args = json.loads(tool_call.function.arguments)
                function_response = function_to_call(
                    date=function_args.get("date"),
                    person=function_args.get("person"),
                )
                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
        # extend conversation with function response
        second_response = openai.chat.completions.create(
            model=model,
            messages=messages,
        )
        return second_response


if __name__ == "__main__":
    query = "東京の天気と10月10日のAさんのスケジュールを教えて"
    model = "gpt-4-1106-preview"  # gpt-3.5-turbo-1106
    response = run_conversation(model, query)
    print(response.choices[0].message.content)
デモ

まず、以前までのモデルのFunction callingのデモを示します。

📌
以前までのFunction calling

クエリ

東京の天気と1010日のAさんのスケジュールを教えてください。

結果

東京の天気は、現在10度です。

しかし、私はAさんの個人情報にアクセスすることはできませんので、
1010日のAさんのスケジュールについてはお答えすることができません。
Aさんご本人または適切な権限を持つ者に直接確認していただく必要があります。

以前のFunction callingでは、後半の質問についての回答を取得することができませんでした。

次に、更新されたモデルのFunction callingのデモを示します。

📌
最新のFunction calling

クエリ

東京の天気と1010日のAさんのスケジュールを教えてください。

結果

東京の現在の天気は、気温が10℃です。

20231010日のAさんのスケジュールは以下の通りです。
- 10時:A社とMTG(ミーティング)
- 12時:友人Bとランチ

以上のように正しい返答が返ってきました。

Parallel Function callingの説明で、「メッセージに含まれる特定の情報を取得して、その情報に応じて自動的に関数を決定して返答します」と説明しました。そこで、必要な情報を欠如させたクエリを与えた時の実験もしてみました。

💡
実験

クエリ

東京の天気とAさんのスケジュールを教えてください。

結果

東京の現在の天気は10℃です。

202352日のAさんのスケジュールは以下の通りです。
- 10時:A社とのミーティング
- 12時:友人Bとランチ

以上がAさんのスケジュールの情報です。

この実験ではハルシネーションが起こってしまいました。

クエリには必ず必要な情報を含める必要がありそうです。ちなみに、クエリに含めないといけない情報は関数の引数で決めることができ、今回でいうとget_current_weatherはlocation(場所)、get_scheduleはdate(日付)とperson(人名)の情報が必要になっています。

最後に

今回は、Parallel Function callingの紹介をしました。今回のアップデートでは、クエリから必要な情報を取得して、正確な関数で回答を生成する精度が向上しているようなので、1つのメッセージで複数の関数が呼び出せる機能の追加と合わせて活用の場面が増えてくると考えます。

参考