2023/06/13にOpenAIからChatGPTのFunction callingの機能がアナウンスされました。
以前、プラグイン機能が出た時にそれがAPIでもできると良いなと思っていたので、ついに来たか!と思い早速試してみました。
ChatGPT Funciton callingとは?
OpenAPIのフォーマット(JSON)で説明付きで関数を定義しておくと、ChatGPTがチャットの会話の中で呼び出しが可能な時に教えてくれる機能だと解釈しています。
例えば、下記のように関数のインターフェースを定義しておくと、会話の中で呼び出しすべきタイミングに、この関数に対してこの引数を渡せばいいよと教えてくれます。
インターフェース
[
{
"name": "add",
"description": "Take the sum of two numbers",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "The integer value from add"},
"b": {"type": "integer", "description": "The integer value to add"},
},
},
"required": ["a", "b"],
},
]
入力
1足す2の結果は
レスポンス
{
"content": null,
"function_call": {
"arguments": "{\n\"a\": 1,\"b\": 2\n}",
"name": "add"
},
"role": "assistant"
}
何がすごい?
自然言語を入力として引数付きで関数を呼び出せるというところが凄いのかなと考えています。
自然言語を入力にして何か関数を呼ぶといえばSiriやGoogle Homeなどが思い浮かびますが、それを独自で簡単に実装できそうですね。
iOSでPythonを動かせるPythonistaというアプリでもopenaiのライブラリが入ったということなので、Pythonista上で対話できるアプリケーションを作ってみました
音声対話エージェントを作ってみた
コード類はGitHub(pythonista_agent)で公開しています。
全体像
コンポーネント名が異なりますが、シーケンス図だとこんなイメージ
では、詳細に実装内容を説明していきます
文字起こし
openaiのAudioAPIを使って実現できます。
import openai
openai.api_key = ""
def transcribe(filename):
with open(filename, "rb") as f:
res = openai.Audio.transcribe("whisper-1", f)
return res["text"]
音声の録音は、pythonistaのsoundライブラリを利用します。下記のようにすると録音した内容を文字起こしすることができます
from sound import Recorder
import time
filename = 'audio.wav'
recorder = Recorder(filename)
recorder.record()
time.sleep(10) # 10秒録音
recorder.stop()
text = transcribe(filename)
print(text)
関数へのマッピング
Function callingの機能を使って、テキストを関数にマッピングします。下記の例では、get_weather(天気の取得)とadd(加算)の二つの関数を定義しています。自然言語を入力として、関数を呼び出せるときは教えてくれます。
例えば、今日の東京の天気は
という入力に対してはget_weather("東京")
という呼び出し、10足す20は
という入力に対してはadd(10,20)
という呼び出しを提案してくれます。ChatCompletionのレスポンスから引数と関数名を取り出し実行しています。
呼び出せる関数がない場合は、レスポンスの中にfunction_call
というキーが入ってこないので、それを使ってすみません。よくわかりません
と回答させています。
def get_weather(region):
res = requests.get("http://weather.tsukumijima.net/api/forecast/city/130010").json()
return res["forecasts"][0]["detail"]["weather"]
def add(a, b):
return a + b
functions = [
{
"name": "get_weather",
"description": "Get the weather information for a given location",
"parameters": {
"type": "object",
"properties": {
"region": {"type": "string", "description": "The name of city. like Tokyo, Osaka "}
},
},
"required": ["region"],
},
{
"name": "add",
"description": "Take the sum of two numbers",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "The integer value from add"},
"b": {"type": "integer", "description": "The integer value to add"},
},
},
"required": ["a", "b"],
},
]
def function_mapping(text):
messages = [
{
"role": "user",
"content": text,
}
]
res = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=messages,
functions=functions,
function_call="auto",
)
response_message = res["choices"][0]["message"]
if response_message.get("function_call"):
func_map = {
"get_weather": get_weather,
"add": add,
}
func = func_map.get(response_message["function_call"]["name"], None)
if func is None:
return "多分ここにくることはない"
args = json.loads(response_message["function_call"]["arguments"])
result = str(func(**args))
return result
else:
return "すみません。よくわかりません。"
考察
単純な例で音声対話エージェントを作ってみたのですが、functionsの中身は動的に変えることができるのでもっと色々なことができるのではないかな?という気がしています。
- 対話の文脈に応じてfunctionsの中身を変更する
- chatGPT自身にfunctionsの定義や中身を考えさせて自動的に処理できることを増やす
- たくさんあるfunctionsのうちいくつかを抽象化させて、functionsをモジュール化させる
少し実験してみたのですが、
次の三つの関数のうち、統合できるものは一つに統合して関数二つ分にしてください
とchatGPTに聞くと、
def get_tweet(query) -> str:
result = search_tweet(query)
return result
def get_instagram(query) -> str:
result = search_instagram(query)
return result
def save_text(text, filename):
with open(filename, 'w') as f:
f.write(text)
下記の内容が返ってきました。
def search_social_media(query: str, platform: str) -> str:
if platform.lower() == "twitter":
result = search_tweet(query)
elif platform.lower() == "instagram":
result = search_instagram(query)
else:
raise ValueError(f"Unsupported platform: {platform}")
return result
def save_text(text: str, filename: str) -> None:
with open(filename, 'w') as f:
f.write(text)
似たようなことをやっている関数をまとめてくれています。これを使えば、functionsの中身を抽象化して少なくすることができそうです。
逆に、次の三つの関数のうち、統合できるものは統合してください。統合しない方が設計上好ましい場合はその旨を教えてください
という質問をした場合
def take_photo():
# 写真を撮影する
def speech(text):
# 文章を音声で流す
def save_text(text):
# 文章をファイルに保存する
下記の回答が返ってきて、異なる役割の関数は無理に抽象化しないことがわかりました。
こちらの3つの関数はそれぞれ異なるタスクを実行しており、機能的には統合することは可能ですが、設計上の観点から見ると、それぞれ独立していた方が適切と考えられます。
理由としては、各関数は単一の責任原則(Single Responsibility Principle)に従っています。つまり、各関数はそれぞれ一つのタスク(写真を撮る、文章を音声化する、文章を保存する)を実行します。これらを統合すると、新たに作成する関数は複数の責任を持つことになり、理解やメンテナンスが難しくなる可能性があります。
また、それぞれの関数は独立して使用することが可能です。たとえば、写真を撮るだけで、音声化や保存をしないといったケースです。これらを一つの関数に統合してしまうと、特定のタスクだけを実行する際に不便になる可能性があります。
したがって、これらの関数はそれぞれ独立して保つほうが設計上好ましいと考えられます。
これができれば、対話を通してシステムの設計や実装も任せられるような気がしてきますね・・。
この、音声対話エージェントをベースに色々と実験してみたいと思います。
まとめ
今回は、ChatGPTのAPIを用いて音声対話エージェントを作成するプロセスを共有し、その可能性を模索しました。提案したアイデアや具体的な実装方法が皆様のご活用に役立てば幸いです。私たちの技術と知識は常に成長し、それは皆様の新たな視点やアイデアからも刺激を受けています。この記事を読んで何か新しい閃きや提案があればぜひ教えてください。最後までご覧いただきありがとうございました。