こんにちは、igaです。
台風が接近していて、外出の予定と重ならないかドキドキしています。
今回は、Azure OpenAI Service(以下、Azure OpenAIと記載します)で7月から利用できるようになった「Function calling」を試してみます。
Function callingとは、実行可能な関数を予め定義しておき、自然言語から実行すべき関数と引数を特定してくれる機能です。
OpenAIのAPIで、6月にリリースされた機能ですが、それがAzure OpenAIでも利用できるようになりました。
この機能のポイントは、「関数を呼び出す」ことではなく、「呼び出す関数(とその引数)を特定してくれる」ことにあります。
(名前が「Function calling」なのに、ややこしいですね)
Azure OpenAIの利用開始方法とAPIの呼び出し方については、前回の記事を参考にしてください。
acro-engineer.hatenablog.com
Function callingのポイント
Function calling(関数呼び出し)と言っていますが、Azure OpenAIが関数を呼び出すわけではありません。
関数を呼び出すのは、ユーザ(プログラム)が行う必要があります。
Azure OpenAIは、「この関数を、この引数で呼び出すのが適切」と判断するまでをやるので、その判断結果に基づいてプログラムで関数を呼び出す必要があります。
今回のプログラムでは、Azure OpenAIに呼び出す関数と引数を判断してもらい、その結果プログラムで関数を呼び出す、というところを試してみます。
Function callingを利用する条件
Azure OpenAIでFunction callingを利用するには、デプロイされたモデルとバージョン、APIバージョンが指定された値になっている必要があります。
デプロイされたモデル | モデルバージョン | APIバージョン |
---|---|---|
gpt-35-turbo gpt-35-turbo-16k gpt-4 gpt-4-32k |
0613 | 2023-07-01-preview |
Function callingを利用する
Azure OpenAIを利用するAPI呼び出しのPythonコードは、前回記事 で使用したコードを流用します。
この時、条件に記述したようにAPIバージョンを「2023-07-01-preview」に変更します。
使用する関数の定義内容については、公式のブログに記述された内容を利用します。
techcommunity.microsoft.com
#Note: The openai-python library support for Azure OpenAI is in preview. import os import pprint import math import json import openai openai.api_type = "azure" openai.api_base = "https://xxxxxx.openai.azure.com/" openai.api_version = "2023-07-01-preview" # Function callingを使用できるAPIバージョンを指定する openai.api_key = os.getenv("OPENAI_API_KEY") # この関数が、呼び出される関数の定義です def calculator(num1, num2, operator): """簡単な計算機 - 対応していない演算子が指定された場合はNoneを返す - 計算できない場合はNoneを返す Args: num1 (int): 数値1 num2 (int): 数値2 operator (str): 演算子 """ if operator == "+": return num1 + num2 elif operator == "-": return num1 - num2 elif operator == "*": return num1 * num2 elif operator == "/": if num2 == 0: return None return num1 / float(num2) elif operator == "**": return num1 ** num2 elif operator == "sqrt": return math.sqrt(num1) else: return None # Azure OpenAIに対する関数の定義です functions= [ { "name": "calculator", "description": "A simple calculator", "parameters": { "type": "object", "properties": { "num1": {"type": "number"}, "num2": {"type": "number"}, "operator": {"type": "string", "enum": ["+", "-", "*", "/", "**", "sqrt"]}, }, "required": ["num1", "num2", "operator"], }, } ] # 最初の問い合わせ内容 messages = [ { "role": "user", "content": "Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?" } ] # 指定されたメッセージを送る response = openai.ChatCompletion.create( engine="ChatTest", messages=messages, functions=functions, function_call='auto') pp = pprint.PrettyPrinter(indent=2) res_message = response['choices'][0]['message'] pp.pprint(res_message) if res_message.get('function_call'): # Functionが利用可能と判断された function_name = res_message['function_call']['name'] arguments = json.loads(res_message['function_call']['arguments']) if function_name == 'calculator': function_result = calculator( num1=int(arguments.get('num1')), num2=int(arguments.get('num2')), operator=arguments.get('operator') ) else: print(f'unknown function {function_name}') exit if not function_result: exit print(f'\ncalculator result: {function_result}\n') # 最初の問い合わせに、Azure OpenAIからの回答を追加する messages.append( { "role": res_message['role'], "name": function_name, "content": res_message['function_call']['arguments'] } ) # 関数の呼び出し結果を追加する messages.append( { "role": "function", "name": function_name, "content": str(function_result) } ) # 関数の実行結果を使用して、問い合わせを行う # Function callingは行わないので、functionsの指定はしない second_response = openai.ChatCompletion.create( engine="ChatTest", messages = messages) second_res_message = second_response['choices'][0]['message'] pp.pprint(second_res_message)
Functionの定義
定義するFunctionには、3つのパラメータを指定する必要があります。
パラメータ | 説明 |
---|---|
name | 定義するFunctionの名前を指定します |
description | モデルが、Functionをいつどのように呼び出せばよいか、決定するために使用します Functionの動作について、意味のある説明を与える必要があります |
parameters | Functionが受け入れる引数について、JSON形式で指定します |
今回定義したFunctionについて
今回定義したFunctionは、以下のようになっています。
functions = [ { "name": "calculator", "description": "A simple calculator", "parameters": { "type": "object", "properties": { "num1": {"type": "number"}, "num2": {"type": "number"}, "operator": {"type": "string", "enum": ["+", "-", "*", "/", "**", "sqrt"]}, }, "required": ["num1", "num2", "operator"], }, } ]
このFunctionは、次のような意味になります。
項目 | 説明 |
---|---|
name | calculator |
description | 簡単な計算機 |
parameters | num1、num2の2つの数値と、operatorの文字列を受け取る operatorは、「+」「-」「*」「/」「**」「sqrt」のいずれかが指定される num1、num2、operatorはいずれも指定が必須 |
Function calling を行う
Function callingを行うには、openai.ChatCompletion.create()の引数で、Functionの定義を指定する必要があります。
response = openai.ChatCompletion.create( engine="ChatTest", messages = messages, functions = functions, function_call = "auto")
Function callingに関連する引数は、「functions」と「function_call」の2つになります。
- functions
- Functionの定義を指定する
- function_call
- auto
- Functionの呼び出しが必要になるか、モデルが決定します
- none
- モデルにFunctionの呼び出しを行わせない
- { "name": 呼び出すFunction名 }
- 決まったFunctionの呼び出しが必要ということを、モデルに指示します
- auto
Function callingの結果
先ほどのサンプルプログラムを実行すると、responseのchoices[0]['message']には以下の値が設定されていました。
{ 'role': 'assistant', 'function_call': { 'name': 'calculator', 'arguments': '{\n' ' "num1": 73846,\n' ' "num2": 12,\n' ' "operator": "*"\n' '}' },
messageにfunction_callが存在しているので、モデルがFunction callingが必要と判断しました。
呼び出すFunctionはどうなっているかは、nameに呼び出すFunction名が、argumentsに引数が指定されています。
引数の内容は、「num1が73846、num2が12、operatorが*」ということになっています。
これは、メッセージに指定した「Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?」という文章から、モデルが「1か月の売り上げ額(73846) × 1年の月数(12) = 年間の売り上げ額」という計算を行う必要がある、と判断して引数の値を決定しています。
Function callingの結果を使って処理を行う
Function callingの結果で、messageにfunction_callが含まれていたので、関数の呼び出しをプログラムで行います。
今回のプログラムでは、以下の部分で関数呼び出しを行っています。
プログラムで呼び出せる関数が指定された場合に、関数「calculator」を呼び出しています。
if res_message.get('function_call'): # Functionが利用可能と判断された function_name = res_message['function_call']['name'] arguments = json.loads(res_message['function_call']['arguments']) if function_name == 'calculator': function_result = calculator( num1=int(arguments.get('num1')), num2=int(arguments.get('num2')), operator=arguments.get('operator') ) else: print(f'unknown function {function_name}') exit
関数呼び出しの結果を使ってAzure OpenAIに問い合わせする
関数呼び出しの結果を使って、もう一度Azure OpenAIに問い合わせを行います。
呼び出す際には、これまでのチャットのやり取りとして、最初の質問、Azure OpenAIからの回答、関数呼び出しの結果、の3つをmessagesに設定してAzure OpenAIの呼び出しを行います。
2回目の呼び出し処理は、以下のようになっています。
# 最初の問い合わせに、Azure OpenAIからの回答を追加する messages.append( { "role": res_message['role'], "name": function_name, "content": res_message['function_call']['arguments'] } ) # 関数の呼び出し結果を追加する messages.append( { "role": "function", "name": function_name, "content": str(function_result) } ) # 関数の実行結果を使用して、問い合わせを行う # Function callingは行わないので、functionsの指定はしない second_response = openai.ChatCompletion.create( engine="ChatTest", messages = messages)
2回目の呼び出し結果(サンプルプログラムの、second_response['choices'][0]['message']の内容)は次のようになりました。
{ 'role': 'assistant', 'content': 'The annual run rate based on last month's sales of $73,846 would be $886,152.' }
contentの内容が、「The annual run rate based on last month's sales of $73,846 would be $886,152.」という文章になり、関数の呼び出し結果を使った文章を生成していることが分かります。
メッセージを日本語で記述する
先ほどのサンプルは、メッセージが英語になっていました。
これを日本語で書いてみると、どのような結果になるでしょうか。
先ほどのサンプルコードの、messagesの定義を日本語に変更します。
# 最初の問い合わせ内容 messages = [ { "role": "user", "content": "先月、太郎さんは5,340,000円の売り上げを上げました。これに基づいた年間売り上げはいくらになりますか?" } ] response = openai.ChatCompletion.create( engine="ChatTest", messages=messages, functions=functions, function_call='auto', )
メッセージを書き換えて、Pythonスクリプトを実行した結果は以下のようになりました。
今回も、指定した文章から必要な計算式を判断して、calculatorに対して渡す引数を生成できています。
{ 'role': 'assistant', 'function_call': { 'name': 'calculator', 'arguments': '{\n \'num1\': 5340000,\n \'num2\': 12,\n \'operator\': \'*\'\n}' } }
この結果を元に、関数を呼び出した結果を使ってAzure OpenAIに問い合わせした結果は以下のようになりました。
こちらも、関数を呼び出した結果を使った文章が生成されました。
{ 'role': 'assistant', 'content': '太郎さんの年間売り上げは64,080,000円になります。' }
関数が特定できないメッセージの場合
メッセージの内容から、関数が特定できない場合(今回のサンプルでは、計算を行わないメッセージの場合)は、どのような結果がAzure OpenAIから返ってくるか確認します。
サンプルプログラムの、messagesの内容を以下のように書き換えます。
Azure OpenAIに問い合わせを行う、openai.ChatCompletion.create() には、そのままfunctionsを指定します。
# 最初の問い合わせ内容 messages = [ { "role": "user", "content": "こんにちは、今日もいい天気ですね。" } ] response = openai.ChatCompletion.create( engine="ChatTest", messages=messages, functions=functions, function_call='auto')
Azure OpenAIの呼び出し結果は、以下の通りです。
Function callingを使わずにチャットを行った時と同じような結果が返ってきました。
Function callingを行う「function_call」は含まれていません。
正常に関数が特定できたかどうかは、レスポンスの内容に「function_call」のパラメータが含まれるかどうかで、判断できそうです。
{ 'role': 'assistant', 'content': 'こんにちは!そうですね、いい天気ですね。何かお手伝いできることはありますか?' }
まとめ
これまで、チャットの機能として、文章を要約したり、プログラムのコードを出力するようなことはできましたが、その応答の内容は基本的に人が判断するものでした。
このFunction callingの機能によって、プログラムからAPIを呼び出し、その結果を受け取って別の処理を実行するようなことを、自動で行えるようになります。
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。Kaggle Grandmasterと一緒に働きたエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの採用 - Wantedlywww.wantedly.com