ちょっと話題の記事

GPTのFunction callingを使って自然言語が新たなインターフェースになるかを試してみる

GPTのAPIに新機能「Function calling」が追加され、ユーザーの自然言語入力に応じて特定の機能を呼び出し様々なツールとの連携が可能になりました。この記事ではFunction callingを利用して、自然言語がインターフェースになるかの可能性を探ります。
2023.06.17

はじめに

Function callingという機能がGPTのAPIに新たに追加されました。

Function callingはざっくりいうとユーザーが入力した文字列に応じて特定の関数を呼び出すことができる機能です。

例えば、ユーザーの入力が「Tannerに来週の金曜空いてるか日程調整のメールおくっといて!」だった場合、勝手にGmailを開いてメールを送信する関数を呼び出して処理を実施してくれたり、「5/20のタクシー代600円で経費登録しておいて」と記載すると勝手に経費登録を行う関数が処理を行ってくれるようなイメージです。

上記のように、外部のツールと簡単に連携ができるようになったことが、従来のユースケースとの大きな違いです。 LangChainでこれまで実施していたユーザーの入力に応じてツールを出し分けるような体験がGPTでもできるようになりました。

検証したいこと

このFunction callingの機能追加は個人的にはかなり大きなインパクトがあると考えています。

外部ツールとの連携ができるようになったことで、ユーザーは「◯◯やっといて〜〜」というだけでAIが勝手にツールを使って変わりに実施してくれる未来がすぐそばにきたということです。

このような近未来的な体験が簡単にできるのか、実際に検証してみたいと思います。

 

まずは作ったデモから

 

連携できた。。。今回はデモのため連携の実装が楽なSlackのWebhookとGoogle spreadsheetを利用しましたが、APIが存在するサービスであれば簡単に同様の実装ができそうです。

これまでも同様の実装はLangchainを用いて実装は可能でしたが、今回機能として追加されたFunction callingを利用するとより簡単に安定した実装が可能です。

特に、自然言語から必要な情報(例:経費の場合、指定している 日付 金額 備考)を抜き出してくれるのがスバラしい。

 

Function callingによる影響

主観的な予測になりますが、ユーザーに対するインターフェースとして自然言語による文字列の入力や音声の入力が更に一般的になるのではないかと。必要な情報はGPTが抽出してくれるため、人間になにかを頼むときと同じように、文字を入力したりお話するだけでタスクが完了するインターフェースが生まれてくるのではないかと感じました。

ここからは技術的な話

まず、Function callingはユーザーの入力に対して特定のfunctionを出し分けるものです。

そのため、functionを作成する必要があります。今回は、Google spreadsheetに対する書き込みとSlackに対する書き込みを行うため、それらの処理を行うためのfunctionを作成します。

Spreadsheetに書き込むfunctionは以下です。Spreadsheetにアクセスするためには認証情報が記載されたjsonファイルをGoogle cloudコンソールから獲得する必要があります。そのあたりの手順はChatGPTに聞くと解決するので聞いてみてください。

# Googleスプレッドシートに経費を書き込む関数
def write_expense_to_spreadsheet(date, amount, description):
    scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
    credentials = ServiceAccountCredentials.from_json_keyfile_name("service_account.json", scope)
    client = gspread.authorize(credentials)
    spreadsheet_id = "スプシのIDを書く"
    sheet = client.open_by_key(spreadsheet_id).sheet1
    # 経費情報をスプレッドシートに追加
    sheet.append_row([date, amount, description])

続いて、Slackに投稿するためのfunctionも作成します。今回は実装を楽したかったのでWebhookを利用しましたが、他の投稿方法でも問題ないです。詳しい使い方はChatGPTに聞くと解決するので聞い(略)

WEBHOOK_URL = "URLを書く"
def post_message_to_slack_via_webhook(message):
    # 送信するデータを作成
    payload = {'text': message}

    # Webhook URLにPOSTリクエストを送信
    response = requests.post( 
    WEBHOOK_URL, data=json.dumps(payload),
    headers={'Content-Type': 'application/json'}
    )

    # 応答をチェック
    if response.status_code == 200:
      print("メッセージが正常に送信されました。")
    else:
      print(f"メッセージの送信に失敗しました: {response.content}")

ここまでで利用したい機能を持つfunctionの作成は完了しました。では、続いてGPTで処理を行う部分を実装します。

完成形は以下のようになります。

def run_conversation():
    # STEP1: モデルにユーザー入力と関数の情報を送る
    user_input=input("user_input:")
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[
            {"role": "system", "content": "You are best assistant ever!"},
            {"role": "user", "content": user_input}],
        functions=[
            {
                "name": "write_expense_to_spreadsheet",
                "description": "経費情報をスプレッドシートに書き込む",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "date": {"type": "string", "format": "date"},
                        "amount": {"type": "string"},
                        "description": {"type": "string"},
                    },
                    "required": ["date", "amount", "description"],
                },
            },
            {
                "name": "post_message_to_slack_via_webhook",
                "description": "Slackにメッセージを投稿する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "message": {"type": "string"},
                    },
                    "required": ["message"],
                },
            }
        ],
        # Noneの場合は使わない、"auto"の場合は自動で選択,指定もできるっぽい
        function_call="auto",
    )
    message = response["choices"][0]["message"]

    # STEP2: モデルが関数を呼び出したいかどうかを確認
    if message.get("function_call"):
        function_name = message["function_call"]["name"]
        arguments = json.loads(message["function_call"]["arguments"])
        print(arguments)
        if function_name == "write_expense_to_spreadsheet":
            function_response = write_expense_to_spreadsheet(
                date=arguments.get("date"),
                amount=arguments.get("amount"),
                description=arguments.get("description"),
            )
        elif function_name == "post_message_to_slack_via_webhook":
            function_response = post_message_to_slack_via_webhook(
                message=arguments.get("message"),
            )
        else:
            raise NotImplementedError()
        
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
             # get user input
             
            messages=[
                {"role": "user", "content": user_input},
                message,
                {
                    "role": "function",
                    "name": function_name,
                    "content": str(function_response),
                },
            ],
        )
        return second_response
    else:
        return response

print("response:", run_conversation()["choices"][0]["message"]["content"], "nn")

以下は簡単な解説です。 まず、今回はユーザーの入力によってfunctionの出し分けを行うため、ユーザーからの入力を受け取ります。

user_input=input("user_input:")

続いてユーザーの入力をGPTのAPIに渡します。 openai.ChatCompletion.createを利用するためには、ModelとMessageを指定する必要があります。

model="gpt-3.5-turbo-0613",
messages=[
    {"role": "system", "content": "You are best assistant ever!"},
    {"role": "user", "content": user_input}],

ModelとMessageの宣言はこれまでのGPTのAPIの利用方法と特に変わりません。今回はsystem roleのメッセージとして"You are best assistant ever!"と設定していますが、多分あってもなくても出力は変わらないかもしれません。

なんかあったほうがより正確に処理を出し分けてくれそうなので記載しています。

以下が今回のFunction callingによって新たに追加されたfunction部分です。

functions=[
            {
                "name": "write_expense_to_spreadsheet",
                "description": "経費情報をスプレッドシートに書き込む",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "date": {"type": "string", "format": "date"},
                        "amount": {"type": "string"},
                        "description": {"type": "string"},
                    },
                    "required": ["date", "amount", "description"],
                },
            },
            {
                "name": "post_message_to_slack_via_webhook",
                "description": "Slackにメッセージを投稿する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "message": {"type": "string"},
                    },
                    "required": ["message"],
                },
            }
        ],
        # Noneの場合は使わない、"auto"の場合は自動で選択,指定もできるっぽい
        function_call="auto",

nameには呼び出したい関数の名前、descriptionにはその関数がなにを行うものかの概要を記載します。ユーザーの入力に対してこのdescriptionの内容と比較してどのFunctionを実行するかをGPT側で判断している認識です。

parametersの部分にはユーザーが入力した文章から取り出してほしい情報(例:経費処理の場合は 日付,金額,備考)を指定します。

このように指定することでユーザーが入力した文章からイイ感じに必要な情報を抜き出してくれます。 Step2以降の内容については公式ドキュメントとほぼ同様の内容になりますが、functionの名前によって実行する処理を出し分けています。 second_response の内容はfunctionが呼び出されなかった場合に実行されます。上記の例の場合、経費処理に関する入力とSlackへの投稿以外に実行されます。

今後、検証したいこと

①. ユーザーからの入力をGPTで加工してからSlackへ投稿する

今回のデモでは、Slackに送信するメッセージはユーザーが入力した文章をそのまま投稿しましたが、この場合直接Slackに書いたほうがぶっちゃけはやいです。

ユーザーがメモ書きのように投稿した内容を正確な文章に整形したうえで投稿することができれば面倒な文章を書く必要がなくなるため、GPTに文章を作成してもらうアプローチで検証したいと思います。

②. ユーザーが必要な情報を入力しなかった場合のハンドリング

今回はユーザーが必要な情報を含んだ文章を入力する前提で検証しましたが、実際の利用においては情報が記載されない場合もあると思うのでそのあたりのハンドリングをどうするのが良いかを検証したいです。

③. 音声入力との連携とUIの作成

Whisper(OpenAIが提供する音声認識API)と連携すれば、適当に話すだけでタスクを完了してくれそうな気がします。音声をインターフェースにする体験も試してみたいです。UIは実装していないため、音声にも対応した画面があるといいですね。

④. Function callingの得意,不得意の整理

Function callingを利用すると外部APIと連携したうえで自然言語をインターフェースとして使えることに強みがあります。

ユースケースとして考えられるのはCRMなどへの入力を楽にしたり、カレンダーと連携して日程調整などが思い浮かびます。ただ、CRMへの入力の場合はそもそも記載しないといけない項目が多いこなどから、あまり自然言語インターフェースのメリットがない印象があります。

上記のように得意なところ、不得意なところを加味したうえでの体験設計を検証してみたいです。

まとめ

Function callingによって音声や文章など自然言語をインターフェースとして活用することができる可能性が一気に高まったと感じました。

検証したいことはまだまだたくさんありますが、この記事は以上としたいと思います。

ご協力いただいた方へのお礼

実装に協力いただきFunction callingの解説を行ってくれた山本さん、おおきに!いつもありがとうございます!!