こんちには。
データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。
今回は前回の記事で書ききれなかった「Function callingの並列化」にフォーカスして試してみます。
機能の概要
Function callingが更新され、1つのメッセージで複数の関数を呼び出すように動作するようになりました。(正確には複数呼び出すようにレスポンス側に指示が来ます)
こちらはgpt-4-1106-preview
とgpt-3.5-turbo-1106
で使用可能です。
また、Function calling自体の精度も向上され、正しい関数パラメータを返す可能性が高くなっているようです。
上記の公式ガイドでは並列的なFunction callingの例として、例えば3箇所の天気を取得する機能を例として説明されています。
こちらについて試してみようと思います。
試してみた
前提
- Pythonのopenaiモジュールをインストール済み
- OPENAI_API_KEY取得済み
ツールの定義
まずはツールとして呼び出したい関数を定義します。
import json
def get_current_weather(location, unit="celsius"):
"""Get the current weather in a given location"""
if "tokyo" in location.lower():
return json.dumps({"location": location, "temperature": "10", "unit": "celsius"})
elif "san francisco" in location.lower():
return json.dumps({"location": location, "temperature": "72", "unit": "fahrenheit"})
else:
return json.dumps({"location": location, "temperature": "22", "unit": "celsius"})
次にこの関数をツールとしてもつtoolsを定義します。(データ型はList[ChatCompletionToolParam]となるようにします)
from openai.types.chat import ChatCompletionToolParam
tools = [
ChatCompletionToolParam({
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定した場所の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "県名や都市名(例:東京、サンフランシスコ)",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
})
]
こちらのtoolsの定義ですが、functionsを使う場合とは構造が変わっており注意が必要です。
toolsの場合は"type": "function"
というtypeの指定が増え、ネストが1段階増えているようです。
関数以外の様々な用途を想定したtoolsという形に変わっており、今後にアップデートにも期待が膨らむところです。
ツールを指定してチャット
そして以下のようにtoolsを引数に指定してチャットAPIを呼び出します。
from openai import OpenAI
client = OpenAI(
api_key = "ここにOpenAI APIキーを記載"
)
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
temperature=0.0,
messages=[
{"role": "user", "content": "サンフランシスコ、東京、大阪の天気は?"},
],
tools=tools,
tool_choice="auto", # auto is default, but we'll be explicit
)
呼び出し時もtools
、tool_choice
という新しい引数を使用します。(以前はfunctions
、function_call
でしたね)
結果を確認してみましょう。
print(response.model_dump_json(indent=2))
# {
# "id": "chatcmpl-8IDRfTGaObZEqfL5kpuVx2IeVjIDH",
# "choices": [
# {
# "finish_reason": "tool_calls",
# "index": 0,
# "message": {
# "content": null,
# "role": "assistant",
# "function_call": null,
# "tool_calls": [
# {
# "id": "call_uVBN0txk9txPyqkmhkAZrJnf",
# "function": {
# "arguments": "{\"location\": \"San Francisco\"}",
# "name": "get_current_weather"
# },
# "type": "function"
# },
# {
# "id": "call_5YDoZydCJaoRrE8NEZg9Rjmu",
# "function": {
# "arguments": "{\"location\": \"Tokyo\"}",
# "name": "get_current_weather"
# },
# "type": "function"
# },
# {
# "id": "call_fEGI3Wf2ZPFvW9S95RtmFtR8",
# "function": {
# "arguments": "{\"location\": \"Osaka\"}",
# "name": "get_current_weather"
# },
# "type": "function"
# }
# ]
# }
# }
# ],
# "created": 1699352279,
# "model": "gpt-3.5-turbo-1106",
# "object": "chat.completion",
# "system_fingerprint": "fp_eeff13170a",
# "usage": {
# "completion_tokens": 63,
# "prompt_tokens": 114,
# "total_tokens": 177
# }
# }
"finish_reason": "tool_calls"
となっており、message
にもtool_calls
が含まれています。
tool_calls
にはどのツールを呼び出すべきかのリストが渡されていることが分かります。
これにより1回のレスポンスで複数のツール(今回はfunction)を開発者側で呼び出すことができます。
ツールを開発者側で呼び出す
各ツールの結果を以下のようなフォーマットでコンテキストして格納して渡すと、続きのチャットが可能です。
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
まずは関数呼び出しの結果を取得します。
from openai.types.chat import ChatCompletionToolMessageParam, ChatCompletionUserMessageParam
tool_response_messages = []
if response.choices[0].message.tool_calls is not None:
tool_calls = response.choices[0].message.tool_calls
for tool_call in tool_calls:
if tool_call.type == "function":
function_call = tool_call.function
function_name = function_call.name
available_functions = [t.get("function").get("name") for t in tools if t.get("type") == "function"]
if function_name in available_functions:
function_arguments = function_call.arguments
function_response = eval(function_name)(**eval(function_arguments))
else:
raise Exception
tool_response_messages.append(
ChatCompletionToolMessageParam({
"tool_call_id": tool_call.id,
"role": "tool",
"content": function_response,
})
) # extend conversation with function response
このツールの結果と、初回のリクエストとレスポンスを一つのリストにまとめます。
history_messages = [
ChatCompletionUserMessageParam({"role": "user", "content": "サンフランシスコ、東京、大阪の天気は?"}),
response.choices[0].message,
*tool_response_messages
]
これを次のリクエストに渡すことで、続きを得ることができます。
続きのチャットを得る
以下で続きを得るためのリクエストを実行します。
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
temperature=0.0,
messages=[
*history_messages
],
)
print(response["choices"][0]["message"]["content"])
# サンフランシスコの天気は摂氏22度で晴れ、東京の天気は摂氏10度で曇り、大阪の天気は摂氏22度で晴れです。
正しく使用できました。
まとめ
いかがでしたでしょうか。
ほぼ従来のFunction callingと使い方は同じですが、構造や引数名が変わっている点には注意が必要そうです。
本記事がご参考になれば幸いです。