Open14

[Scrap] Open AI の Chat API の functions calling を触る

ピン留めされたアイテム
へぶんへぶん

TODO

  • とりあえず試す
  • function の強制実行
  • function の複数実行
  • description を省いても動作するパターンがあるか確認
  • function の stream 対応
へぶんへぶん

Function Calling が叩かれるときは content が空

Function が叩かれる際は、以下のようになる。

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'get_current_weather',
  'arguments': '{\n  "location": "Toronto, Canada",\n  "format": "celsius"\n}'}}

content が入らないのが意外だった。

へぶんへぶん

関数のパラメータとしてメッセージも返させる

実行中にメッセージを出したい場合もあると思うので、functions の properties に message_in_executing という propety を追加してみた。

  • message_in_executing を追加
    • どうやらレスポンスでも properties の定義順序を守ってくれるみたいで、stream 時に先に受け取れるように一番目に設定
  • requierdmessage_in_executing を追加
    {
        "name": "get_current_weather",
        "description": "Get the current weather",
        "parameters": {
            "type": "object",
            "properties": {
                "message_in_executing": {
                    "type": "string",
                    "description": "The message for user to show when this function is executing.",       
                },
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "format": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use. Infer this from the users location.",
                }
            },
            "required": ["message_in_executing", "location", "format"],
        },
    },
へぶんへぶん

レスポンス

以下のように返ってくる。

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'get_current_weather',
  'arguments': '{\n  "message_in_executing": "Fetching current weather...",\n  "location": "Glasgow, Scotland",\n  "format": "celsius"\n}'}}
へぶんへぶん

この手法についてのメモ

  • 実行中のメッセージをリッチにできる
    • 汎用的な関数の場合役に立ちそう
  • 場合によっては使えそう
  • トークン数が増える
  • やることがシンプルな場合、使わなくていいパターンもありそう
    • stream 時でも function_call内の name が入ってきた時点で定型メッセージを出せばいい場合もありそう
へぶんへぶん

関数を使うことを強制させる

We can force the model to use a specific function, for example get_n_day_weather_forecast by using the function_call argument. By doing so, we force the model to make assumptions about how to use it.

サンプルコードの中では、以下のように "function_call" を足している。

chat_response = chat_completion_request(
    messages, functions=functions, function_call={"name": "get_n_day_weather_forecast"}
)
)
へぶんへぶん

function_call パラメータ

https://platform.openai.com/docs/api-reference/chat/create

function_call object Optional
The name and arguments of a function that should be called, as generated by the model.

function_call string or objectOptional
Controls how the model responds to function calls. "none" means the model does not call a function, and responds to the end-user. "auto" means the model can pick between an end-user or calling a function. Specifying a particular function via {"name":\ "my_function"} forces the model to call that function. "none" is the default when no functions are present. "auto" is the default if functions are present.

へぶんへぶん

2つ以上の関数を呼び出すにはどうすればいいか考える

functions を束ねる処理を作ってみる

とりあえず ChatGPT に functions の定義を書いてもらう。
https://chat.openai.com/share/bdcc6ff9-6ccc-4420-9d3c-37c87e275652

ChatGPT に書いてもらった functions の定義
functions = [
    {
        "name": "create_email",
        "description": "Create an email",
        "parameters": {
            "type": "object",
            "properties": {
                "message_in_executing": {
                    "type": "string",
                    "description": "The message for user to show when this function is executing."
                },
                "to": {
                    "type": "string",
                    "description": "Email address of the recipient"
                },
                "subject": {
                    "type": "string",
                    "description": "Subject of the email"
                },
                "body": {
                    "type": "string",
                    "description": "Body of the email"
                }
            },
            "required": ["message_in_executing", "to", "subject", "body"]
        }
    },
    {
        "name": "add_calendar_event",
        "description": "Add an event to the calendar",
        "parameters": {
            "type": "object",
            "properties": {
                "message_in_executing": {
                    "type": "string",
                    "description": "The message for user to show when this function is executing."
                },
                "title": {
                    "type": "string",
                    "description": "Title of the event"
                },
                "start_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Start time of the event"
                },
                "end_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "End time of the event"
                },
                "location": {
                    "type": "string",
                    "description": "Location of the event"
                },
                "description": {
                    "type": "string",
                    "description": "Description of the event"
                }
            },
            "required": ["message_in_executing", "title", "start_time", "end_time"]
        }
    },
    {
        "name": "post_tweet",
        "description": "Post a message to Twitter",
        "parameters": {
            "type": "object",
            "properties": {
                "message_in_executing": {
                    "type": "string",
                    "description": "The message for user to show when this function is executing."
                },
                "status": {
                    "type": "string",
                    "description": "The status message to post"
                }
            },
            "required": ["message_in_executing", "status"]
        }
    },
    {
        "name": "execute_operations",
        "description": "Execute multiple functions at once",
        "parameters": {
            "type": "object",
            "properties": {
                "message_in_executing": {
                    "type": "string",
                    "description": "The message for user to show when this function is executing."
                },
                "operations": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "description": "The operations to be executed",
                    }
                }
            },
            "required": ["message_in_executing", "operations"]
        }
    }
]
へぶんへぶん

とりあえず試してみる

上記の functions をそのまま渡しつつ、execute_operationsfunction_call に渡して実行してみる。

# in this cell we force the model to use get_n_day_weather_forecast
messages = []
messages.append({"role": "system", "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
## 私は次の日曜日にライブを見に行くつもりです。その旨をツイートしつつ、予定を追加してください。
messages.append({"role": "user", "content": "I plan to go watch a live performance next Sunday. Please note this down as a tweet and add it to my schedule."})
chat_response = chat_completion_request(
    messages, functions=functions, function_call={"name": "execute_operations"}
)
chat_response.json()["choices"][0]["message"]

レスポンス

以下が function_call として返ってきた。

{
  "message_in_executing": "Creating tweet and adding to schedule...",
  "operations": [
    {
      "operation": "post_tweet",
      "message_in_executing": "Posting tweet...",
      "status": "I'm excited to watch a live performance next Sunday! #livemusic #performance"
    },
    {
      "operation": "add_calendar_event",
      "message_in_executing": "Adding event to calendar...",
      "title": "Live Performance",
      "start_time": "2022-12-12T19:00:00",
      "end_time": "2022-12-12T22:00:00",
      "location": "",
      "description": "Watching a live performance"
    }
  ]
}

メモ

  • パラメータとして operation を追加してくれてる
  • その他の定義はその他の関数の定義に準じてくれている。
  • operationは揺れがある場合があるので、多少堅くした方が良さそう。
へぶんへぶん

まとめ

これで以下の仕様で実装できることがわかった。

  • 関数の実行を強制できる
  • メッセージの発行と関数の実行を同時にできる(message_in_executing)
  • 複数の関数を同時に実行できる
へぶんへぶん

タスク実行とは無関係のメッセージを送ると?

上記の複数関数実行で、「こんにちは」とかタスク実行とは無縁のメッセージを送ってみる。

# in this cell we force the model to use get_n_day_weather_forecast
messages = []
messages.append({"role": "system", "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role": "user", "content": "Hello! How are you doing?"})
chat_response = chat_completion_request(
    messages, functions=functions, function_call={"name": "execute_operations"}
)
chat_response.json()["choices"][0]["message"]

レスポンス

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'execute_operations',
  'arguments': '{\n  "message_in_executing": "I am executing multiple operations at once"\n}'}}

メモ

  • よくわからん答えが返ってきた。
    • 関数がない場合に message_in_executing の存在が意味不明になる
  • operations のキーがない
    • 適当な operation が追加されていないことは重畳
へぶんへぶん

prompt に例外ケースの説明を追加する

下記を execute_operationsdescription に追加する。
"If there are no functions to be executed, assign an empty array to operations and an appropriate response to message_in_executing."

execute_operations の定義

{
"name": "execute_operations",
"description": "Execute multiple functions at once. If there are no functions to be executed, assign an empty array to operations and an appropriate response to message_in_executing.",
"parameters": {
"type": "object",
"properties": {
"message_in_executing": {
"type": "string",
"description": "The message for user to show when this function is executing."
},
"operations": {
"type": "array",
"items": {
"type": "object",
"description": "The operations to be executed",
}
}
},
"required": ["message_in_executing", "operations"]
}
}

レスポンス

空配列は返ってこなかったが、適切なメッセージが返却されるようになった。

{'role': 'assistant',
 'content': None,
 'function_call': {'name': 'execute_operations',
  'arguments': '{\n  "message_in_executing": "I\'m doing well, thank you!"\n}'}}

メモ

  • 空配列が返って来ないが、返って来ない前提で対応した方が良さそう。