はじめに
こんにちは、ABEJAでデータサイエンスグループに所属している今井です。ABEJAアドベントカレンダー2023の20日目の記事です。
OpenAIのAPIには、チャットアプリケーションを作る際に役立つfunction callingという機能がありますが、かねてから「どれくらいfunction callingは精度良く呼び出されるのだろうか?」と思っていました。
今回の記事では、上記の疑問に答えるべくfunction callingの呼び出し精度をレストラン予約のシナリオを題材に定量的に評価してみたので、その内容についてご紹介したいと思います。
以下の順序で内容を説明していきます。
Function callingについて
Function callingの使用イメージ
(以下、function callingに詳しい方は飛ばして問題無い内容です。)
Function callingは、予め呼び出して欲しい関数の候補を与えておくと、OpenAIのモデルが文脈に応じて呼び出すべき関数と引数を教えてくれる機能です。以下の図を用いて、ユーザから「今日の東京の天気は?」という入力(図中①)があった場合のfunction callingの使われ方のイメージを説明します。
このときチャットアプリケーションは、任意の地名(location)に対する現在の天気情報を得るための手段としてget_current_weather(location)という関数が使えるものとします。チャットアプリケーションは、この関数とユーザからのメッセージと共に、次にユーザに回答するべきメッセージをOpenAIモデルにChat Completion APIを通じてリクエストします(図中②)。
OpenAIモデルは「こんにちは!東京、晴れていると良いですね。」といった通常のメッセージを応答することもありますが、この例では今日の天気予報に答えるためにはget_current_weather関数を呼び出す必要があると判断したとします。このときモデルは、ユーザのメッセージに含まれていた”東京”という地名から、「get_current_weather (location: Tokyo)」のように呼び出すべき関数とその引数を埋めたJSONオブジェクトをチャットアプリケーションに対して返却します(図中③)。この呼び出すべき関数を教えてくれる機能が、function callingと呼ばれるものです。
チャットアプリケーションは、モデルに教えてもらった関数の情報を使って第三者が提供する外部APIを呼び出し(図中④)、東京の天気情報を取得することで(図中⑤)、最終的にユーザからの質問に回答します(図中⑥)。
Function calling評価のモチベーション
LLM大流行のご時世、ABEJAの業務においてもfunction callingを用いたチャットアプリケーション開発を検討する機会は多くあります。実際にOpenAIのAPIを使ってみると、モデルは期待するタイミングでうまくfunction callingを呼び出してくれることもあれば、予期しないタイミングで呼び出してしまうこともありました。そのような経験を踏まえて、以下のような疑問に答えたいと思い、今回function callingの評価を行ってみました。
- どれくらい正しいタイミングでfunction callingは呼び出されるか?
- モデルのバージョンによる呼び出し精度の違いはどれくらいか?
- プロンプトや関数定義の工夫による呼び出し精度の違いはどれくらいか?
評価用データセット
評価には、Googleが公開している以下のThe Schema-Guided Dialogue Dataset *1 という人間とバーチャルアシスタントの対話ログに関するデータセットを用います。 github.com
データセットの概要
本データセットは、以下のような特徴を持っています。
- 様々なタスクシナリオ:レストラン予約、音楽の選曲、ホテルでのアシスタンス、…
- アノテーション:やりとりされる全ての発話に対する、含まれている情報の種類や、その意図のアノテーション付け
- ライセンス:Creative Commonsライセンスで商用利用も可
以下はレストラン予約の際の対話ログの一例です。
No. | 発話者 (speaker) | 発話内容 (utterance) | 発話意図 (actions) |
---|---|---|---|
1 | USER | Hi, could you get me a restaurant booking on the 8th please? | INFORM, INFORM_INTENT |
2 | SYSTEM | Any preference on the restaurant, location and time? | REQUEST |
3 | USER | Could you get me a reservation at P.f. Chang's in Corte Madera at afternoon 12? | INFORM |
4 | SYSTEM | Please confirm your reservation at P.f. Chang's in Corte Madera at 12 pm for 2 on March 8th. | CONFIRM |
5 | USER | Sure, that is great. | AFFIRM |
6 | SYSTEM | Sorry, your reservation could not be made. Could I help you with something else? | NOTIFY_FAILURE, REQ_MORE |
7 | USER | Could you try booking a table at Benissimo instead? | INFORM, INFORM_INTENT |
8 | SYSTEM | Sure, please confirm your reservation at Benissimo Restaurant & Bar in Corte Madera at 12 pm for 2 on March 8th. | CONFIRM |
9 | USER | Sure, may I know if they have vegetarian options and how expensive is their food? | REQUEST, AFFIRM |
10 | SYSTEM | Your reservation has been made. Unfortunately, they do not serve vegetarian options, although they are moderate priced. | INFORM, NOTIFY_SUCCESS |
11 | USER | I see, thanks alot! | THANK_YOU |
12 | SYSTEM | No worries, could I further assist you? | REQ_MORE |
13 | USER | No, that is all. Thank you! | NEGATE, THANK_YOU |
14 | SYSTEM | Have a great day ahead! | GOODBYE |
レストラン予約における予約実行タイミング
上記のレストラン予約の対話ログを例にとった場合、いつアシスタントはレストランに問い合わせを行い、予約の実行をしているかを見てみます。まず、No.3でユーザが
USER: Could you get me a reservation at P.f. Chang's in Corte Madera at afternoon 12?
と予約情報を伝えた後、アシスタントはNo.4で以下のようにユーザに対して確認を行います。
SYSTEM: Please confirm your reservation at P.f. Chang's in Corte Madera at 12 pm for 2 on March 8th.
その後、No.5で
USER: Sure, that is great.
とユーザが予約内容に同意した後、アシスタントは予約を行い、その結果をNo.6で
SYSTEM: Sorry, your reservation could not be made. Could I help you with something else?
と予約が失敗したことをユーザに伝えています。
他の例も確認したところ、本データセットに含まれるレストラン予約ログでは常にアシスタントがまずユーザに予約内容を確認(CONFIRM)し、ユーザがその確認に同意(AFFIRM)してから予約を行う流れになっていました。そのため、actionにAFFIRMが出現した直後に予約実行を行うのがアシスタントとしての正しい振る舞いと言えそうです。
Function callingの評価方法
評価方法
上記で説明したThe Schema-Guided Dialogue Datasetに含まれる32個のレストラン予約の対話ログを評価対象のデータセットとして用いることにします。
評価の基準は「対話ログにユーザによるAFFIRM actionが出現した直後のタイミングで、OpenAIモデルがレストラン予約を行うためのreserve_restaurant関数(下記参照)をfunction callingによって呼び出せるかどうか」とします。 なお今回は、呼び出しタイミングのみを評価対象とすることとして、関数パラメータの適切さは評価対象外とします。
functions = [{ "name": "reserve_restaurant", "description": "Make a table reservation at a restaurant. Don't make assumptions about restaurant_name, location, and time to plug into this function.", "parameters": { "type": "object", "properties": { "restaurant_name": { "type": "string", "description": "Name of the restaurant", }, "location": { "type": "string", "description": "City where the restaurant is located", }, "time": { "type": "string", "format": "time", "description": "Tentative time of restaurant reservation", }, "date": { "type": "string", "format": "date", "description": "Tentative date of restaurant reservation", }, "number_of_seats": { "type": "number", "description": "Number of seats to reserve at the restaurant", }, }, "required": ["restaurant_name", "location", "time"], } }]
具体的な評価指標としては、以下を用います。
- recall : 正解タイミングに対して、どれくらいreserve_restaurant関数を呼び出せているか?
- precision : reserve_restaurant関数呼び出しは、どれくらい正解タイミングを含んでいるか?
- f1-score : recallとprecisionの調和平均
またアシスタントの振る舞いを決めるシステムプロンプトは、以下を用います。
system_prompt = ''' You are an assistant tasked with making a restaurant reservation. Please adhere strictly to the rules outlined below. # Rules - Always seek confirmation from the user. Do not proceed with the reservation until you have received explicit confirmation of the reservation details. - Unless the user specifies otherwise, assume that the default number of seats required is 2. '''
評価における注意点
今回の評価では、OpenAIのChat Completion APIをユーザからの入力があった後毎回呼び出します。 その際function callingによって関数が呼び出されることもあれば、ユーザへの応答メッセージが返ってくることもあります。 例えば、「USER: Hi, could you get me a restaurant booking on the 8th please? 」というユーザからのメッセージに対しては、以下の応答がAPIから返ってきました。
Sure, I can help you with that. Could you please provide me with the following details:
Name of the restaurant
City where the restaurant is located
Tentative time of the reservation
Number of seats required (default is 2)
Once I have these details, I will confirm the reservation with you before proceeding.
しかし今回のデータセットに沿った評価では、APIからの返答をユーザに返したとしても、それに応じてくれるユーザが存在しません。 そのためAPIからの返答は無視して、予め対話ログに含まれているアシスタントのメッセージをユーザに対する応答として、会話を進めながら評価を行います。
SYSTEM: Any preference on the restaurant, location and time?
評価実験
実験シナリオ
評価実験は以下の4つの条件で行います。
実験No. | モデル | 関数定義およびプロンプト |
---|---|---|
実験1 | gpt-3.5-turbo-0613 | ベースライン |
実験2 | gpt-4-0613 | ベースライン |
実験3 | gpt-3.5-turbo-0613 | 改善版 |
実験4 | gpt-4-1106-preview | 改善版 |
実験シナリオは、以下に示す①関数/プロンプトの改善と、②モデルバージョンの変更という2つの観点から構成します。
①関数/プロンプトの改善: 同じモデルを使った場合、関数定義やプロンプトの改善で精度はどれだけ向上するか?
- 実験1と実験3では、双方ともgpt-3.5-turbo-0613を用いる。
- 実験1では、こちらで示したベースラインの関数定義およびプロンプトを使う。
- 実験3では、ベースラインでうまくいかなかったケースに対処するため、関数定義とプロンプトに改善を施す(詳細は直後に記載)。
②モデルバージョンの変更:同じ関数定義とプロンプトを使った場合、モデルバージョンを上げると精度はどれだけ向上するか?
- 実験1と実験2では、それぞれgpt-3.5-turbo-0613とgpt-4-0613の比較を行う。
- 実験3と実験4では、それぞれgpt-3.5-turbo-0613と2023年12月時点の最新版であるgpt-4-1106-previewの比較を行う。
関数定義とプロンプトの改善
関数定義の改善
下記のようにユーザが確認(AFFIRM)と同時に質問をしているケースにおいて、関数呼び出しが行われていないケースがありました。
USER: Sure, may I know if they have vegetarian options and how expensive is their food?
このような質問がある場合でも関数を呼び出しやすくするために、reserve_restaurant関数に対してオプショナルで質問も引数として取ることできるよう以下のように関数定義を変更しました。
functions_v2 = [{ "name": "reserve_restaurant", "description": "Make a table reservation at a restaurant. Don't make assumptions about restaurant_name, location, and time to plug into this function. Optionally ask questions about the restaurant and its menu.", "parameters": { "type": "object", "properties": { "restaurant_name": { "type": "string", "description": "Name of the restaurant", }, -------- ベースラインと同一のため途中省略 -------- "questions": { "type": "string", "description": "Questions to ask about the restaurant and its menu", }, }, "required": ["restaurant_name", "location", "time"], } }]
プロンプトの改善
下記のようにユーザが予約を行うことを一文で直接的に要求しているような場合、確認をせずにすぐに関数呼び出しを行ってしまうケースがありました。
USER: Could you try booking a table at Benissimo instead?
このようなケースに対処するため、確認を強く求める以下の指示をプロンプトに加えました。
Even when the user requests a restaurant reservation, do not make a reservation immediately. Instead, always ask for confirmation first.
実験結果
実験の結果、実験1 → 実験4と関数定義/プロンプトの改善やモデルのバージョンを上げていくにつれて、各スコアが向上していくという傾向が見られました。
特に関数定義/プロンプト改善に最新のGPT-4であるgpt-4-1106-previewを用いた実験4では、ベースラインである実験1に比べてf1-scoreが0.542 → 0.892と0.35ポイント上昇し、大きな改善が見られました。
また実験シナリオを比較すると、以下のことが分かります。
- 関数定義/プロンプト改善は有効
- 同じモデルバージョン(gpt-3.5-turbo-0613)を用いている実験1と実験3では、関数定義とプロンプトに改善を施した実験3が、改善が入っていない実験1に比べて0.18ポイント f1-scoreが高い。
- ベースラインのプロンプトとGPT-3.5を用いている実験3を、改善プロンプトとGPT-4を用いている実験2を比較すると、f1-scoreは実験3の方が若干(0.023ポイント)高くなった。これは、プロンプトの変更だけでもモデルによる性能差を克服できるだけの精度改善を得られることを示している。
- モデルバージョンの向上は有効
- 同じプロンプトを用いている実験1と実験2では、モデルバージョンが高い実験2の方が0.18ポイント f1-scoreが高い。
- 同様に実験3と実験4では、モデルバージョンがより高い実験4の方が0.17ポイント f1-scoreが高い。
また各実験における合計の関数呼び出し回数と、その正解/不正解の内訳(precisionの内訳)を図示してみると以下のようになりました。GPT-3を用いている実験1と実験3では、GPT-4を用いている実験2と実験4に比べて関数呼び出し自体の回数が多く、よりアグレッシブに関数呼び出しを行っていることが分かりました。
おわりに
レストラン予約における会話を題材に、function callingの呼び出しタイミングの精度評価を定量的に行いました。 プロンプトやモデルバージョンを上げると関数呼び出しが改善する事は感覚としては感じていましたが、今回評価を行ってみて、改めてその精度の変化を視覚化し定量的に把握することができました。 特にプロンプトや関数定義を改善することでも精度はかなり向上することが確認でき、OpenAIモデルに目的に沿った振る舞いをしてもらうためには、やはりプロンプトの工夫は大事だと改めて認識しました。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方のエントリーもお待ちしております)
*1:Rastogi et al., "Towards scalable multi-domain conversational agents: The schema-guided dialogue dataset", Proceedings of the AAAI Conference on Artificial Intelligence, 2020