# Example dummy function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
"""Get the current weather in a given location"""
if "tokyo" in location.lower():
return json.dumps({"location": "Tokyo", "temperature": "10", "unit": "celsius"})
elif "san francisco" in location.lower():
return json.dumps({"location": "San Francisco", "temperature": "72", "unit": "fahrenheit"})
elif "paris" in location.lower():
return json.dumps({"location": "Paris", "temperature": "22", "unit": "celsius"})
else:
return json.dumps({"location": location, "temperature": "unknown"})
# Step 1: send the conversation and available functions to the model
# 会話
messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]
# 利用可能な関数
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
}
]
# モデルへ投げて回答を取得
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages,
tools=tools,
tool_choice="auto", # auto is default, but we'll be explicit
)
# Step 2: check if the model wanted to call a function
if tool_calls:
...
STEP 2 |実際に関数を実行する
公式ドキュメント:
# Step 3: call the function
# Note: the JSON response may not always be valid; be sure to handle errors
available_functions = {
"get_current_weather": get_current_weather,
} # only one function in this example, but you can have multiple
messages.append(response_message) # extend conversation with assistant's reply
# Step 4: send the info for each function call and function response to the model
for tool_call in tool_calls:
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,
}
) # extend conversation with function response
second_response = client.chat.completions.create(
model="gpt-3.5-turbo-1106",
messages=messages,
) # get a new response from the model where it can see the function response
second_responseの中身:
{
'id': 'chatcmpl-yyyyyyyyyy',
'choices': [
Choice(
finish_reason='stop',
index=0,
message=ChatCompletionMessage(
content='Currently, the weather in San Francisco is 72°F, in Tokyo it is 10°C, and in Paris it is 22°C.',
role='assistant',
function_call=None,
tool_calls=None
)
)
],
'created': 1700895214,
'model': 'gpt-3.5-turbo-1106',
'object': 'chat.completion',
'system_fingerprint': 'fp_eeff13170a',
'usage': CompletionUsage(
completion_tokens=29,
prompt_tokens=169,
total_tokens=198
)
}
Function Calling が説明等を読んでもイマイチよくわからなかったので実際に試してみた。これはその時のメモ。
今回は次の公式ドキュメントの手順をやってみた。
https://platform.openai.com/docs/guides/function-calling
また、次の日本語解説記事が参考になった。
https://dev.classmethod.jp/articles/understand-openai-function-calling/
セットアップ
今回、実行環境は Google Colab にした。
まずは OpenAI ライブラリをインストール。
続いて、各種ライブラリのインポートと OpenAI クライアントを作成。
OpenAI API Key は次のページで作成できる:
今回、 Function Calling の動作確認用に次のダミー関数を利用した。
全体像
公式ドキュメントのコードの全体像は次のとおり:
STEP 1 |会話と利用可能な関数をモデルに送る
公式ドキュメントのコード(日本語のコメントは自分で追記した):
response
の中身はこんな感じ。今回はサンフランシスコ、東京、パリの3つの都市の天気を訊いているので、tool_calls
が3リクエスト分になっている。この中から次の2つを取り出す。
次の処理に入る前に、 Function Calling が要求されているかのチェックが必要。
STEP 2 |実際に関数を実行する
公式ドキュメント:
分解して見ていく。
利用可能な関数の辞書を作成している。今回は
get_current_weather()
しか利用できないので、辞書内のアイテムは1つだけになる。available_functions
の中身は次のようになっている。関数がオブジェクトとして辞書に登録されている。STEP 1 で得た
response_message
をmessage
にappend
している。これは STEP 3 で最終的な回答を作成するために使用する。したがって、
messages
の中身は次のようになる。STEP 1 で作成したリクエストをループで順次、実際に関数へリクエストしていく。
実際に関数を実行する部分は次のコード。
関数の実行結果を
messages
へappend
している。これは STEP 3 で最終的な回答を作成するためである。ループ終了時の
messages
の中身は次のようになっている。STEP 3 |最終的な回答を作成する
公式ドキュメントのコード:
second_responseの中身:
second_response.choices[0].message.content
を見てみると、関数の実行結果が回答に組み込まれていることがわかる。何が嬉しいのか?
(ほぼほぼ「 [OpenAI] Function callingで遊んでみたら本質が見えてきたのでまとめてみた | DevelopersIO 」に書かれていることではあるが…)
例えば、今回のように自然言語で各都市の気温を問い合わせる仕組みを作る場合、 ChatGPT だけでは各都市の気温は答えられないので API 等で別途情報を取得してくる必要がある(補足)。そのため、問い合わせ文から API 等へのリクエストに必要な情報を抽出する必要があった。
「 What's the weather like in San Francisco, Tokyo, and Paris? 」という質問の場合、(どのような情報がリクエストに必要かによるが)「 San Francisco 」「 Tokyo 」「 Paris 」の3つの情報を抽出する必要がある。
Function Calling が登場するまでは、プロンプトを工夫することで情報を抽出しやすくしていた。
例えば次のプロンプトを用いたとする。
すると、 ChatGPT は次のように回答してくれる。
今回の気温の例は出力フォーマットがシンプルだったので比較的指定したフォーマットで回答してくれているが、複雑な出力フォーマットだったりすると指定したフォーマットにしたがってくれなかったりする。
Function Calling を使えば、引数の仕様を指定すればそれに合うように情報を抽出してくれる。例えば今回の場合、次のように情報を抽出してくれていた。
補足: 今は Browse with Bing のおかげで ChatGPT 単体での回答が可能になっている
現在では ChatGPT に「 Browse with Bing 」機能が搭載されているため、 LLM 単体でも回答が可能となっている。
例えば、今回の「 What's the weather like in San Francisco, Tokyo, and Paris? 」という質問を ChatGPT (ChatGPT Plus 加入版) に投げてみたところ、次の回答が得られた: