OpenAI APIのParallel function Callingを解説します。特定の関数やメソッドを呼び出させる機能です。AIとプログラミングの融合って感じです。



Parallel function callingとは

Function Callingとは、事前に関数のメタデータを定義(どのような時に使うか、引数は何か)して、Chat Completions APIをリクエストすると、OpenAI APIが関数を実行すると判断した場合、レスポンスに「関数名と引数」を含めたJSONオブジェクトが返却される機能です。

ユーザーの入力に応じて、必要な関数を自律的に判断できます。Chat Completions APIのtool_choice="auto"に設定すると、関数の呼び出しが不要とOpenAI APIが判断した場合、通常のGPTの回答が出力されます。

2023年11月に、Parallel function callingとしてバージョンアップし、OpenAI APIが複数の関数の呼び出しを返却することができるようになりました。

Function calling利用の注意点は、Chat Completions API は関数の実行は行わないめ、JSONの内容を受け取って、再度プログラムで実行する必要があります。またOpenAI APIの呼び出し回数が増えるため、レスポンス時間は長くなる傾向にあります。

参考記事:Function calling(OpenAIの公式ドキュメント)

Function callingとReActの違い

Function callingは、関数の処理内容とメタデータを事前に定義し、ユーザーのリクエストを受けて、必要に応じて利用すべき関数を教えてくれます。いわば必要な関数を「思考」して「行動」を推奨しています。

一方ReActは、「思考」「行動」「観察」の3ステップを繰り返して、最終的にタスクを達成しています。行動の結果から得た学びを思考に反映していくことで、新しい状況に対応することが理論的には可能です。ただ、ReActは最初のステップで誤った思考をしてしまうと、正しい道に戻るのはかなり厳しいという課題があります。

行動に外部ツールを設定することが多いので、Function callingと似た機能に見えますが、ReActは言語モデルの思考プロセスを明示的に表現することにあります。推論のステップが可視化されるので、言語モデルの判断根拠がわかりやすくなります。
hikaku

Parallel function callingの精度

ABEJA Tech Blogさんでの評価では、レストラン予約における会話を題材に、 f1-scoreが0.9近くの精度を達成していました。精度向上には、モデルのバージョン向上に加え、関数定義(質問をオプション引数に設定)とプロンプト改善(確認を強く求める指示を設定)が有効とのことです。

精度的にはかなり良い印象ですが、会話である以上、ユーザーの回答に100%正確に関数を呼び出しすることは現実的に難しいです。ユーザーとの会話を想定して評価データを準備し、改善サイクル回す必要があると感じます。

参考記事:Function callingはどれくらい正しくレストラン予約できるのか?

Parallel function callingの実装方法

Parallel function callingを利用した、AIチャットボットの実装方法を紹介します。

対応済みのモデル

Parallel function callingは、以下のモデルで動作可能です。
gpt-4-turbo-preview
gpt-4-0125-preview
gpt-4-1106-preview
gpt-3.5-turbo-0125
gpt-3.5-turbo-1106

①関数を定義する

OpenAI APIで呼び出したい関数を定義します。今回は、最新ニュースを検索するAPIと、仮の自社サービス「ABC」について回答する関数を用意しました。
# インターネットから企業の最新ニュースを検索する
def searchInternet(query):
    search = DuckDuckGoSearchRun(backend="news")
    answer = search.run(query)
    return answer

# 社内ファイル検索用の関数
def searchDocument(query):
answer = "自社サービスABCは〇〇です" return answer

②関数のメタデータを定義する

TOOLS = [ # OpenAI API に選んでもらう関数のリスト
        {
            "type": "function",
            "function": {
                "name": "searchInternet",
                "description": "インターネットから最新ニュースを検索する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "検索クエリ",
                        },
                    },
                    "required": ["location"]
                }
            }
        },
       
        {
            "type": "function",
            "function": {
                "name": "searchDocument",
"description": "自社サービスABCについて回答する", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "検索クエリ", }, }, "required": ["location"] } } }, ]

③Function Callingを実行する

FUNCTIONS_DICT = {
    "searchInternet": searchInternet,
    "searchDocument": searchDocument,
}


def conversation(question):
    # ステップ1: GPTにユーザークエリと利用可能な関数を送信
    messages=[
            {"role": "system", "content": "あなたはAIアシスタントです"},
            {"role": "user", "content": question},
    ]
    
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0125",
        messages=messages,
        tools=TOOLS,
        tool_choice="auto",  # APIに関数の選択を任せる
    )
    response_message = response.choices[0].message # GPTからのレスポンスを取得
    tool_calls = response_message.tool_calls # GPTが選んだ関数のリスト   
    messages.append(response_message)

    # ステップ2: GPTが関数を呼び出すかどうかを確認
    if tool_calls is None:
        print(message["content"])
            
    else: # ステップ3: 関数を呼び出す場合
        for tool_call in tool_calls: # 選ばれた関数を順に呼び出す
            function_name = tool_call.function.name # 関数名を取得
            function_to_call = FUNCTIONS_DICT[function_name] 
            function_args = json.loads(tool_call.function.arguments) 
            function_response = function_to_call(function_args) 
            
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )  

        # 関数の実行結果を受けて再度GPT実行
        second_response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0125",
        messages=messages,
        )

        return second_response["choices"][0]["message"]["content"]

conversation("自社サービスABCの最新ニュースを教えて")

参考記事:Function CallingのParallel function callingを試す

まとめ

OpenAIの公式機能ということもあり、精度はそれなりに高いです。実装も簡易で便利な機能なので、AIチャットボットでの利用例は今後も増えてくると考えられます。

関連記事:LangChainとRAGで実現する先進的なAIチャットボットの開発