見出し画像

Function callingのCookbookを試してみた【1】

こんにちは!株式会社デジタルレシピ CTO室の荻原(@yui_nk222)です。

2023年6月14日に、OpenAIから大きなアップデートが発表され、外部ツールを呼び出せる「Function calling」という機能が発表されました。

早速Cookbookのサンプルコードを試しながら、どんなことができるのか見ていきたいと思います。

1. Function callingの概要

  • gpt-3.5-turbo-0613やgpt-4-0613というモデルでは、関数呼び出しに必要なJSONを生成できるようになった

    • モデルにfunctionsパラメータで関数の内容を渡すと、その内容に基づいて関数の引数を含んだJSONを生成する

    • このJSONを使用して、関数を呼び出すことができる

  • 関数はモデルのコンテキスト制限に含まれ、入力トークンとしてカウントされる


functionsパラメータについて

  • ChatCompletion APIのオプションパラメータ

  • functionsパラメータが指定された場合、デフォルトではモデルが適切なタイミングで関数を使う( = 関数の引数を含んだJSONを生成する)かどうかを判断する

    • 「fanction_call」パラメータを「{"name": "<関数名>"}」に設定することで、特定の関数の使用を強制することもできる


functionsの定義方法

  • Name: 関数の名前

  • Description: 関数の説明。モデルはこの説明に基づいて関数を呼び出すタイミングを決定する

  • Parameters: 関数の引数を設定する(設定できる引数の詳細はこちら

  • Required: 必須の引数を指定する。指定しなかったパラメータはオプションとなる

例:

functions = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "format": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use. Infer this from the users location.",
                },
            },
            "required": ["location", "format"],
        },
    }
]


関数が使用された場合のレスポンス

  • 「"finish_reason": "function_call"」という情報が含まれる

  • 「function_call」オブジェクトに、関数の名前と生成された引数が含まれる

例:

{
    'index': 0,
    'message': {
        'role': 'assistant',
        'content': None,
        'function_call': {
            'name': 'get_current_weather',
            'arguments': '{\n  "location": "Glasgow, Scotland",\n  "format": "celsius"\n}'
        }
    },
    'finish_reason': 'function_call'
}


2. Cookbookの「Basic concepts」を実行してみた

下記のCookbookの、Basic conceptsを実行していきます。実行環境はGoogle Colabを利用しました。


1. 必要なライブラリをインポート

!pip install scipy
!pip install tenacity
!pip install tiktoken
!pip install termcolor 
!pip install openai
!pip install requests
!pip install arxiv
!pip install pandas
!pip install PyPDF2


2. チャットのためのユーティリティを定義

Cookbookでは「openai.api_key」でAPIキーを呼び出していましたが、今回は直接定義して進めます。

import arxiv
import ast
import concurrent
from csv import writer
from IPython.display import display, Markdown, Latex
import json
import openai
import os
import pandas as pd
from PyPDF2 import PdfReader
import requests
from scipy import spatial
from tenacity import retry, wait_random_exponential, stop_after_attempt
import tiktoken
from tqdm import tqdm
from termcolor import colored

GPT_MODEL = "gpt-3.5-turbo-0613"
EMBEDDING_MODEL = "text-embedding-ada-002"
OPENAI_API_KEY = "xxx"


以下の関数「chat_completion_request」は、チャットボットの会話を補完するものです。

  • messages:チャットボットに送信されるメッセージのリスト

  • functions:GPT-3.5モデルに渡す関数のリスト(オプション)

  • model:使用するGPTモデルの指定(オプション)

@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, functions=None, model=GPT_MODEL):
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    json_data = {"model": model, "messages": messages}
    if functions is not None:
        json_data.update({"functions": functions})
    try:
        response = requests.post(
            "https://api.openai.com/v1/chat/completions",
            headers=headers,
            json=json_data,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e


以下の「Conversation」クラスは、チャットの会話履歴を管理し、メッセージの追加や表示を行うためのメソッドを提供するものです。

class Conversation:
    def __init__(self):
        self.conversation_history = []

    def add_message(self, role, content):
        message = {"role": role, "content": content}
        self.conversation_history.append(message)

    def display_conversation(self, detailed=False):
        role_to_color = {
            "system": "red",
            "user": "green",
            "assistant": "blue",
            "function": "magenta",
        }
        for message in self.conversation_history:
            print(
                colored(
                    f"{message['role']}: {message['content']}\n\n",
                    role_to_color[message["role"]],
                )
            )


3. 会話を生成する

まずはチャットボットが利用可能な関数の情報を定義します。ここでは、「get_current_weather」という名前で、「location」と「format」のふたつのパラメータが必須となる関数が定義されています。

functions = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "format": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit to use. Infer this from the users location.",
                },
            },
            "required": ["location", "format"],
        },
    }
]


会話履歴に「what is the weather like today」というユーザーのメッセージを追加し、「chat_completion_request」関数に会話履歴と関数リスト(functions)を渡して、チャットボットの応答を取得します。

conversation = Conversation()
conversation.add_message("user", "what is the weather like today")

chat_response = chat_completion_request(
    conversation.conversation_history, functions=functions
)
assistant_message = chat_response.json()["choices"][0]["message"]
conversation.add_message(assistant_message["role"], assistant_message["content"])
assistant_message


ここまでのコードを実行してみました。ユーザーの「what is the weather like today」というメッセージに対して、アシスタントが「Where are you located?」と聞く、という会話になったということがわかります。

{'role': 'assistant', 'content': 'Where are you located?'}


「chat_response.json()」の中身が気になったので、出力してみました。

{
    'id': 'chatcmpl-7RAMd0l8bSB7abSmmhzJjQOXf7Zdf', 
    'object': 'chat.completion', 
    'created': 1686709051, 
    'model': 'gpt-3.5-turbo-0613', 
    'choices': [
        {'index': 0, 
         'message': {'role': 'assistant', 'content': 'Where are you located?'}, 
         'finish_reason': 'stop'}
    ], 
    'usage': {'prompt_tokens': 90, 'completion_tokens': 6, 'total_tokens': 96}
}


次に、ユーザーが「I'm in Glasgow, Scotland」と返すように会話履歴を追加し、チャットボットの応答を取得するコードを追加して実行します。

# Once the user provides the required information, the model can generate the function arguments
conversation.add_message("user", "I'm in Glasgow, Scotland")
chat_response = chat_completion_request(
    conversation.conversation_history, functions=functions
)
chat_response.json()["choices"][0]


結果は下記のようになりました。「get_current_weather」関数を実行するための引数「location」と「format」が結果に含まれています。

{
    'index': 0,
    'message': {
        'role': 'assistant',
        'content': None,
        'function_call': {
            'name': 'get_current_weather',
            'arguments': '{\n  "location": "Glasgow, Scotland",\n  "format": "celsius"\n}'
        }
    },
    'finish_reason': 'function_call'
}


例えば、下記のような「get_current_weather」という関数を用意しておけば、先ほどのチャットの結果を利用して天気を取得することができます。「get_current_weather」関数の中でもGPT-3.5モデルのAPIを呼び出せば、会話を続けることもできますね。

def get_current_weather(location, format):
    # 簡易的に「晴れ」と温度を返す
    if format == "celsius":
        return f"現在の天気は 晴れ で、温度は 25 ℃ です。"
    elif format == "fahrenheit":
        return f"現在の天気は 晴れ で、温度は 77 ℉ です。"
    else:
        return "温度の単位が無効です。"
# 結果から関数名を取得
function_name = chat_response.json()["choices"][0]["message"]["function_call"]["name"]

# 結果から引数を取得
arguments = chat_response.json()["choices"][0]["message"]["function_call"]["arguments"]
location_value = json.loads(arguments)["location"]
format_value = json.loads(arguments)["format"]

# 関数を呼び出す
result = globals()[function_name](location_value, format_value)
# result = 現在の天気は 晴れ で、温度は 25 ℃ です。

(結果から関数名と引数を取得して関数を呼び出すところ、もっといい書き方がありそうです…)

長くなってしまったので、Cookbookの残りは次のnoteにします。


弊社では、CTO直下で最新AI情報の調査・開発から発信まで行うエンジニアを募集しています!少しでも興味のある方はぜひ下記のWantedlyをご覧ください!


この記事が気に入ったらサポートをしてみませんか?