はじめに
2023/6/13のOpenAIのアップデートでFunction calling
という機能が追加されました。
触ってみたところ今まで気合いと若干の運頼みでどうにかしていたところを綺麗にできる機能なのが分かったので試してみました。
個人的にはFunction calling
という名前がちょっと紛らわしかったです。Function selecting
みたいな感じじゃないかと思いました。
やってくれることは、雑にいうとユーザーの入力メッセージに対して使えそうな関数を選んでレスポンスしてくれるというものです。これだけだと有り難みが分かりにくいので説明して行きます。
ChatGPT APIのレスポンスを何らかのアクションに繋げるには何が必要だったのか
この機能ができるまでChatGPT APIのレスポンスから何かしらアクションを行わせたい時、以下の実装が必要でした。
1. レスポンス内容の指定
2. レスポンスのパース
3. パース結果を関数に渡す
まず、ChatGPT APIに何か質問したとして返ってくるのはテキストメッセージです。(ChatGPTなので当たり前ですが)
なので例えばJSON形式など、その後の工程で使いやすい形で返すよう、ChatGPT APIに指示を出していました。
一例ですが、こんな感じのプロンプトを書いたりしてました。
role = "
Strictly adhere to the following restrictions.
- You are a pharmacist.
- You are professional, but also kind, friendly, and respectful.
- You reply in the user's language.
- You can reply only in the JSON format including the \"message\" and \"medicines\" properties.
- If medicine is recomended, include each in this property in array format. Array elements must be names only, not sentences.
- if medicine is not recomended, set \"medicines\" value to null.
- Again, you must in any case respond in JSON format which has the message and medicines parameters.
"
トークン節約のため英語にしてますが、内容としては薬剤師という設定で、
- 絶対にJSONで答えろ
- そのJSONには
message
とmedicines
というプロパティを含めろ - オススメの薬があったらmedicinesに配列で含めろ
- オススメの薬がないならmedicinesは
null
にしろ - 繰り返す、絶対JSONで返せ!
みたいなことが書いてあります。細かいことは置いておいて、要はレスポンス内容を指定しようとしているということです。
ChatGPT APIからのレスポンスをその後に別の処理で使いたければ、パースしやすいようにしてどんな値を返すかまで指示しないといけなかったということですね。そうしないとただのテキストで返ってくるので正規表現で頑張る手もあるかもですが中々大変かなと思います。
ちなみにgpt-3.5-turbo
ではここまで書いてもJSONで返ってこないことが結構な確率でありました。
なのでこの後にパース失敗時やパースできても想定してるプロパティが入ってない時などに対応できるような処理を書き、ユーザーにはそれっぽく答えをするようにしてました。運が絡むものでした。
Fanction callingで何ができるようになったのか
プログラムの例があったほうが早いので書いてみます。
例なので単純なものにしています。また、より汎用的にするためこの例ではOpenAIのモジュールを使っていません。
全体像は以下ですが、下に部分ごとに解説を書きます。
import requests
import json
def search_medicine(medicine):
url = "https://www.amazon.co.jp/s?k=" + medicine
return url
def ask_chat_gpt():
headers = {
"Authorization": "Bearer your-token"
}
messages = [
{
"role": "system",
"content": "あなたは薬剤師です。ユーザーの症状に対してオススメの薬の名前を具体的に教えてください。"
},
{
"role": "user",
"content": "微熱があるみたい"
}
]
functions = [
{
"name": "search_medicine",
"description": "症状に合わせた推奨される医薬品の候補を返す",
"parameters": {
"type": "object",
"properties": {
"medicine": {
"type": "string",
"description": "薬の名前"
}
},
"required": ["medicine"]
}
}
]
function_call = "auto"
body = {
"model": "gpt-3.5-turbo-0613",
"messages": messages,
"functions": functions,
"function_call": function_call
}
response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=body)
return response.json()
if __name__ == '__main__':
response = ask_chat_gpt()
print(response)
ChatGPT APIへのリクエストはこんな形のBodyになります。
body = {
"model": "gpt-3.5-turbo-0613", # モデル
"messages": messages, # 入力するメッセージ
"functions": functions, # ChatGPT APIに伝える関数群
"function_call": "auto" # autoにしておくと、functionsで伝えた関数から使えそうなものを選ぶ
}
次に、大事なのがこの部分です。search_medicine
1つだけしか書いてないですが、ここにAPI側に伝える関数を書いて行きます。関数名、その説明、引数ですね。
functions = [
{
"name": "search_medicine",
"description": "症状に合わせた推奨される医薬品の候補を返す",
"parameters": {
"type": "object",
"properties": {
"medicine": {
"type": "string",
"description": "薬の名前"
}
},
"required": ["medicine"]
}
}
]
実行してみると以下のような内容になります。
{
"id": "chatcmpl-7SsfdsfTDGE6TSd02XWL",
"object": "chat.completion",
"created": 1687022283,
"model": "gpt-3.5-turbo-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"function_call": {
"name": "search_medicine",
"arguments": {
"medicine": "解熱剤"
}
}
},
"finish_reason": "function_call"
}
],
"usage": {
"prompt_tokens": 139,
"completion_tokens": 21,
"total_tokens": 160
}
}
message
の中にfunction_call
とあり、search_medicineが選択されてます。また、arguments
に引数でmedicine
が解熱剤
と入ってます。入力メッセージと関数の説明から、この関数を選択したわけですね。
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"function_call": {
"name": "search_medicine",
"arguments": {
"medicine": "解熱剤"
}
}
},
"finish_reason": "function_call"
}
あとは、この結果から好きに処理を書けば良いです。
function = message["function_call"]["name"]
if function == "search_medicine":
medicine = json.loads(message["function_call"]["arguments"])["medicine"]
medicine_url = search_medicine(medicine)
print(medicine_url)
..その他好きな処理..
実際には必ずしも症状を含むコメントではない入力がされることもあるので、
if message.get("function_call"):
function = message["function_call"]["name"]
if function == "search_medicine":
medicine = json.loads(message["function_call"]["arguments"])["medicine"]
medicine_url = search_medicine(medicine)
print(medicine_url)
else:
print(message["content"])
↑こんな感じにすればそれにも対応できます。ChatGPT APIに伝えている関数に、該当するものがなかった場合の処理です。
単にこんにちは
とやってみると、
messages = [
{
"role": "system",
"content": "あなたは薬剤師です。プロフェッショナルな薬剤師として振る舞ってください。"
},
{
"role": "user",
# "content": "微熱があるみたい"
"content": "こんにちは"
}
]
普通にコメントが返ってきます。
こんにちは!どのようなお困りごとがありますか?お手伝いできることがあれば教えてください。
まとめ
Function calling
を使えば、ユーザーのメッセージに対してアクションに繋げるプログラムを簡単に作れます。
今までもできなくはなかったですが、かなりやりやすくなったなと思います。頑張ってプロンプトでレスポンスの形式を指定しなくて良くなりましたし、複数の関数を伝えておけばユーザー入力に対して最適な次の処理を選んでくれます。
だいぶ応用しやすくなったと感じます。
参考
- Function calling