[Scrap] Open AI の Chat API の functions calling を触る
TODO
- とりあえず試す
- function の強制実行
- function の複数実行
- description を省いても動作するパターンがあるか確認
- function の stream 対応
以下の Cookbook を動かす
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 時に先に受け取れるように一番目に設定
-
requierd
にmessage_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 が入ってきた時点で定型メッセージを出せばいい場合もありそう
- stream 時でも
関数を使うことを強制させる
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 パラメータ
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 の定義を書いてもらう。
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_operations
を function_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_operations
の description
に追加する。
"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}'}}
メモ
- 空配列が返って来ないが、返って来ない前提で対応した方が良さそう。