7月にAzure OpenAI ServiceでもFunction calling機能が利用できるようになりました。
Function callingは、ChatGPTのAPI呼び出しの際に関数の情報を渡し、AIモデルと連携して関数呼び出しをサポートする機能です。
これだけ聞くと、GPTを他の関数やAPIと組み合わせて利用することを想像し、普段使いする機会は少ないと感じてしまうと思いますが、実は普段でも利用できる機能があることをご存じですか?
本記事では、普段使いできるFunction callingの活用方法を紹介します。
Function calling とは
Function callingはOpenAIやAzure OpenAI ServiceのAPIで利用できる機能で、関数呼び出しのサポートや関数の出力に基づいた回答を生成できます。
Function callingを用いることで、GPTと関数を組み合わせたプログラムを容易に構築することができます。
以下の図はFunction callingを利用したシステムのデータ交換の概略図です。
Function callingのより詳細な説明については、すでに多くのブログで説明がされていますので本記事では省略します。
Function calling機能の特筆すべき点は、呼び出すべき関数を選定できることと、関数に渡すために情報を指定されたフォーマットに整形できることです。
2つ目の「情報を指定されたフォーマットに整形できる」は、実は関数の呼び出し以外のタスクでも活用する事ができます。本記事ではこの機能の活用例を示します。
活用例
今回は例として、ある仮想のレストランへの口コミを分析するタスクを実行します。
準備
以下の2つの口コミをChatGPTで作成してみました。これを今回AIに分析してもらう口コミとします。
口コミ例(1) |
---|
「星空のディナー」での体験は、まさに夢のようでした。店内は暗く、天井には星空が広がっており、まるで宇宙空間でのディナーのような感覚を味わえました。特に、流れ星が時折見られる演出は心を打ちました。 料理も非常にユニークで美味しかったです。特に「オリオン腕の海老とアボカドのサラダ」は、新鮮な海老とクリーミーなアボカドが絶妙にマッチしており、星空の下での食事をより一層楽しませてくれました。また、「銀河鉄道のチーズケーキ」は、甘さ控えめでしっとりとした食感が特徴で、デザートとしても満足度が高かったです。 サービスに関しても、スタッフが料理の背景や星に関する小話をしてくれるのが印象的でした。ただ、ピークタイムには混雑しているようで、料理の提供に少し時間がかかったのが残念でした。 全体的には、非日常を体験できる素晴らしいレストランだと感じました。次回も訪れたいと思います。 |
口コミ例(2) |
---|
「星空のディナー」に大きな期待を持って訪れましたが、正直がっかりしました。まず、店内の星空は綺麗でしたが、テーブルの配置が狭く、隣のお客さんの会話が聞こえてしまうほどでした。 料理に関しても、メニューには「銀河のサーモンタルタル」と「ブラックホールのチョコレートフォンデュ」など、面白い名前がついていましたが、実際の味は平凡でした。特に「銀河のサーモンタルタル」は、サーモンの鮮度がいまいちで、味付けも薄かったです。 サービスに関しても、スタッフが忙しそうで、オーダーを取りに来てくれるのに時間がかかりました。また、料理の説明も簡潔すぎて、メニューの名前の由来などがよくわからなかったです。 このレストランのコンセプトは面白いと思いますが、実際のサービスや料理のクオリティが追いついていない印象でした。 |
(どちらの口コミも料理名の方が気になってしまいます…。)
目標
口コミ内容を分析し、5段階評価・顧客感情・高評価なポイント・低評価なポイント・店がとるべき改善施策を分析していきたいと思います。
もし、私がこの口コミ例(2)の分析をすると、以下のようになります。
- 5段階評価(最低1/最高5):2
- 顧客感情:ネガティブ
- 高評価なポイント:綺麗な店内の星空, 面白いコンセプト
- 低評価なポイント:テーブル配置が狭い, 隣席の会話が聞こえる, 料理のクオリティが低い, 注文受付が遅い
- 店がとるべき改善施策:テーブル配置の改善, 料理の品質改善
つまり、このような感じでAIが回答してくれるようにする、というのが目標となります。
回答が挙げられる項目に対しては、カンマ区切りで並べて回答を受け取れるとその後のデータ分析のときに役立ちそうです。
Function callingを使用しないと…
Function calling機能を利用する前に、使わなかった場合にどうなるか試してみましょう。
Function callingを使わずZero-Shotで回答生成
Function calling機能を使わない場合は、プロンプトで出力内容やフォーマットを指定することになります。
Zero-Shot(出力例をAIに与えない)で回答を生成してみます。
使用したPythonコードは以下になります。
# 準備(共通) import openai openai.api_type = "azure" openai.api_base = RESOURCE_ENDPOINT openai.api_version = "2023-07-01-preview" openai.api_key = API_KEY
def analyze_sentence_zeroshot(user_sentence): # システムメッセージ system_content = """ あなたはレストランのサイトに投稿された口コミの分析するAIアシスタントです。 与えられた口コミの文章をよく読んで、内容を分析し、5段階評価・感情分析・高評価な部分・低評価な部分・店がとるべき改善施策の作成を実行します。 """ #プロンプト user_prompt = f""" 5段階評価・感情分析・高評価なポイント・低評価なポイント・店がとるべき改善施策を以下の形式で出力してください。 - 5段階評価:口コミから判断できる5段階評価(1,2,3,4,5)。最高評価が5で、最低評価を1とする。 - 感情分析:口コミから総合的に判断される顧客のレストランへの感情。negative, positive, neutralのいずれかを選択する。 - 高評価なポイント:口コミに記載されているレストランの高評価な部分を簡潔に表した内容。カンマ区切りで列挙。無い場合は空白。(例)清潔感のある店内, 見やすいメニュー, 親しみやすい接客 - 低評価なポイント:口コミに記載されているレストランの低評価な部分を一文でまとめた内容。カンマ区切りで列挙。無い場合は空白。(例)料理が冷めている, 駅から遠い - 店がとるべき改善施策:レストランの顧客満足度を高めるための該当の口コミから想定できるとるべき改善施策。カンマ区切りで列挙。無い場合は空白。(例)料理の提供時間を早くする ```入力(実際の口コミ) {user_sentence} ``` ```出力 """ # AzureOpenAIへのリクエスト response = openai.ChatCompletion.create( engine="gpt-35-turbo", messages = [ {"role":"system","content":system_content}, {"role":"user","content":user_prompt} ] ) return response user_sentence = "【ここに口コミを入力】" response = analyze_sentence_zeroshot(user_sentence) print(response['choices'][0]['message']['content'])
それぞれ2つの口コミ例に対するGPTからの回答は次のようになりました。
5段階評価:5 感情分析:positive 高評価なポイント:暗く、天井には星空が広がっており、まるで宇宙空間でのディナーのような感覚, 流れ星が時折見られる演出, ユニークで美味しい料理, 新鮮な海老とクリーミーなアボカドが絶妙にマッチしており、星空の下での食事をより一層楽しませてくれる, 甘さ控えめでしっとりとした食感のチーズケーキ, スタッフが料理の背景や星に関する小話をしてくれる 低評価なポイント:ピークタイムには混雑しているようで、料理の提供に少し時間がかかった 店がとるべき改善施策:ピークタイムの料理提供時間を短縮する
- 5段階評価:3 - 感情分析:negative - 高評価なポイント:店内の星空が綺麗 - 低評価なポイント:テーブルの配置が狭く、隣のお客さんの会話が聞こえる / 料理の味が平凡で、特に「銀河のサーモンタルタル」の鮮度と味付けが不満 / スタッフが忙しそうでサービスが遅く、料理の説明が不十分 - 店がとるべき改善施策:テーブルの配置を広くする / 料理の味と鮮度にこだわる / スタッフの負担を軽減し、サービスのスピードを向上させる / メニューの説明を充実させる
概ね求めているような回答を出力してくれています。ただし、以下2点が気になります。
- 2つ目の出力では各行の先頭に
-
が入っている。 - 2つ目の出力では箇条書きを並べる際に、指定した
,
ではなく/
で区切っている。
つまり、回答のフォーマットが安定していないということになります。
これではデータ集計する際に、決められた形式で分析結果を整理するのが困難になってしまいます。
Function callingを使わずOne-Shotで回答生成
Zero-Shot(出力例をAIに与えない)では回答のフォーマットが安定しないことから、次はOne-Shot(1つだけ出力例をAIに与える)で回答を生成してみます。
使用したPythonコードは以下になります。
def analyze_sentence_oneshot(user_sentence): # システムメッセージ system_content = """ あなたはレストランのサイトに投稿された口コミの分析するAIアシスタントです。 与えられた口コミの文章をよく読んで、内容を分析し、5段階評価・感情分析・高評価な部分・低評価な部分・店がとるべき改善施策の作成を実行します。 """ #プロンプト user_prompt = f""" 5段階評価・感情分析・高評価なポイント・低評価なポイント・店がとるべき改善施策を以下の形式で出力してください。 - 5段階評価:口コミから判断できる5段階評価(1,2,3,4,5)。最高評価が5で、最低評価を1とする。 - 感情分析:口コミから総合的に判断される顧客のレストランへの感情。negative, positive, neutralのいずれかを選択する。 - 高評価なポイント:口コミに記載されているレストランの高評価な部分を簡潔に表した内容。カンマ区切りで列挙。無い場合は空白。(例)清潔感のある店内, 見やすいメニュー, 親しみやすい接客 - 低評価なポイント:口コミに記載されているレストランの低評価な部分を一文でまとめた内容。カンマ区切りで列挙。無い場合は空白。(例)料理が冷めている, 駅から遠い - 店がとるべき改善施策:レストランの顧客満足度を高めるための該当の口コミから想定できるとるべき改善施策。カンマ区切りで列挙。無い場合は空白。(例)料理の提供時間を早くする ```入力例 ランチで利用しました。味はまずまずでした。店内の緑豊かな雰囲気は最高だったが、ベジタブルカレーは少し塩辛かった。再訪を考えさせるほど、デザートの抹茶ムースは絶品でした。サービスも丁寧で、スタッフの方々がとてもフレンドリーでした。 ``` ```出力例 5段階評価:4 感情:neutral 高評価なポイント:緑豊かな内装, 絶品な抹茶ムース, フレンドリーなスタッフ 低評価なポイント:塩辛いベジタブルカレー 店がとるべき改善施策:ベジタブルカレーの味を改善する ``` ```入力(実際の口コミ) {user_sentence} ``` ```出力 """ # AzureOpenAIへのリクエスト response = openai.ChatCompletion.create( engine="gpt-35-turbo", messages = [ {"role":"system","content":system_content}, {"role":"user","content":user_prompt} ] ) return response user_sentence = "【ここに口コミを入力】" response = analyze_sentence_zeroshot(user_sentence) print(response['choices'][0]['message']['content'])
口コミ(1), (2)のそれぞれの結果は以下のようになりました。
5段階評価:5 感情:positive 高評価なポイント:店内の星空の演出, オリオン腕の海老とアボカドのサラダ, 銀河鉄道のチーズケーキ, スタッフのサービス 低評価なポイント:ピークタイムの混雑 店がとるべき改善施策:ピークタイムの対応を改善する
5段階評価:2 感情:negative 高評価なポイント:面白いコンセプト 低評価なポイント:狭い配置, 隣のお客さんの会話が聞こえる, 平凡な料理, サーモンの鮮度がいまいち, スタッフ忙しい, オーダーに時間がかかる, 説明が不十分 店がとるべき改善施策:店内の配置を改善する, 料理のクオリティを向上する, スタッフの忙しさを軽減する, 説明の質を向上する
Zero-Shotの出力結果に比べてOne-Shotでは、出力のフォーマットが安定し、指定した形になっていることが確認できます。
このように出力フォーマットを安定させ、求めている形式で得るためにはプロンプトエンジニアリングのテクニックを駆使する必要があります。入出力の例を与えたり、プロンプトの微調整を行うことはなかなか骨の折れる作業で、それでも場合によっては確率的に出力形式が揺らぐことがあります。
そこでFunction callingの機能を利用しようという話になります。
Function callingを利用すると…
Pythonスクリプト
Function calling機能を利用するようにスクリプトを以下のように修正しました。
def analyze_sentence_functioncalling(user_sentence): # システムメッセージ system_content = """ あなたはレストランのサイトに投稿された口コミの分析し、分析結果のデータベース登録をサポートするアシスタントです。 与えられた口コミの文章をよく読んで、内容を分析し、5段階評価・感情分析・高評価なポイント・低評価なポイント・店がとるべき改善施策の作成を実行します。 """ # 関数の情報 functions = [ # 仮想の関数を1つだけ記述する { "name": "register_analysis_results", # 関数の名称 "description": "口コミを分析した結果をデータベースに登録する", # 関数の機能概要 "parameters": { "type": "object", # 関数に与えるパラメータの定義 "properties": { "reputation": { "type": "integer", #文字列ではなく数値パラメータであることを明記 "description": "口コミから判断できる5段階評価(1,2,3,4,5)。最高評価が5で、最低評価を1とする。" }, "emotion": { "type": "string", "enum": ["negative", "positive", "neutral"], #選択肢を明記 "description": "口コミから総合的に判断される顧客のレストランへの感情。" }, "positive_points": { "type": "string", "description": "口コミに記載されているレストランの高評価な部分を簡潔に表した内容。カンマ区切りで列挙。例:清潔感のある店内, 見やすいメニュー, 親しみやすい接客" }, "negative_points": { "type": "string", "description": "口コミに記載されているレストランの低評価な部分を一文でまとめた内容。カンマ区切りで列挙。例:料理が冷めている, 駅から遠い" }, "next_action": { "type": "string", "description": "レストランの顧客満足度を高めるための該当の口コミから想定できるとるべき改善施策。カンマ区切りで列挙。例:料理の提供時間を早くする" } }, # 関数が必要とする必須パラメータ # 以下2つのみを指定することで、他のパラメータは任意であることを示す "required": ["reputation", "emotion"] } } ] # AzureOpenAIへのリクエスト response = openai.ChatCompletion.create( engine="gpt-35-turbo", messages = [ {"role":"system","content":system_content}, {"role":"user","content":user_sentence} #口コミ文章はプロンプトとしてそのままGPTに渡す ], functions=functions, function_call={"name": "register_analysis_results"} #ここに関数名を記載することで、特定の関数の呼び出すことを宣言できる ) return response user_sentence = "【ここに口コミを入力】" response = analyze_sentence_functioncalling(user_sentence) print(response['choices'][0]['message']['function_call']['arguments'])
スクリプトの内容を簡単に説明すると、
- あたかも分析結果を登録する関数が存在するかのように、その関数定義をGPTに与える
- その関数の呼び出したいことを伝えて、口コミ文章をGPTに渡す
- 関数定義で指定したフォーマットで、関数に渡すべきパラメータをGPTから受け取る
という処理を一度に実行しています。
重要な部分については、コメントアウトしてスクリプト内に記載しているので、ご確認ください。 APIに指定するパラメータの詳細については、以下のドキュメントが参考になります。
結果
口コミ(1), (2)のそれぞれに対するスクリプトの結果は以下のようになりました。
{ "reputation": 5, "emotion": "positive", "positive_points": "暗く、天井に星空が広がる店内, 流れ星の演出, ユニークで美味しい料理, サービスのスタッフの小話", "negative_points": "ピークタイムの混雑" }
{ "reputation": 2, "emotion": "negative", "positive_points": "店内の星空の美しさ", "negative_points": "テーブルの配置が狭く、隣のお客さんの会話が聞こえる, 料理の味が平凡で特に銀河のサーモンタルタルの鮮度と味付けが不満", "next_action": "テーブルの配置を見直す, 料理のクオリティを向上させる, スタッフの接客やメニューの説明を改善する" }
JSON形式で見にくいため、先ほどまでと同じように書き起こすと以下のようになります。
5段階評価:5 感情:positive 高評価なポイント:暗く、天井に星空が広がる店内, 流れ星の演出, ユニークで美味しい料理, サービスのスタッフの小話 低評価なポイント:ピークタイムの混雑 店がとるべき改善施策:(出力なし)
5段階評価:2 感情:negative 高評価なポイント:店内の星空の美しさ 低評価なポイント:テーブルの配置が狭く、隣のお客さんの会話が聞こえる, 料理の味が平凡で特に銀河のサーモンタルタルの鮮度と味付けが不満 店がとるべき改善施策:テーブルの配置を見直す, 料理のクオリティを向上させる, スタッフの接客やメニューの説明を改善する
この結果から以下のことが言えます。
- Zero-Shot(入力と出力の例を与えていない)にもかかわらず、指定されたフォーマットで分析結果を出力している。
- 1つ目の口コミの結果には、
next_action
パラメータ(店がとるべき改善施策)が含まれていない。つまり、必須パラメータとして指定していなかった項目に関しては、不要とAIが判断した場合はそのパラメータは返さない。
今回は2つの口コミ例だけを載せましたが、私が実際に利用している感覚として、Function callingは安定して同じフォーマットで出力をします。
(もともとそのための機能なので当然ではあります…。)
自然言語処理にプログラミング的な安定性を求める場合に、Function calling機能はかなり有用ではないでしょうか。
最後に
Function calling機能は関数の呼び出しの用途以外にも、自然言語処理で決まったフォーマットの出力を得たい場合に活用できます。
発想次第では普段の業務にも十分応用できる機能だと思います。