GPT-3.5でfunction_callを使ってみた

 ## 開発で必要なこと

1. **OpenAI APIキーの取得**：OpenAIのAPIを利用するためには、OpenAIのウェブサイトでAPIキーを取得する必要があります。取得したAPIキーは環境変数として設定するか、またはセキュリティを考慮し.envファイルに保存します。

2. **開発環境の準備**：
   - Dockerのインストール：Dev Containerの基盤となるDockerをインストールします。
   - VS Codeのインストール：Dev Containerを使用するために必要なVS Codeをインストールします。
   - Remote - Containers拡張機能のインストール：VS Code内で、Remote - Containers拡張機能をインストールします。
   - Dev Containerの設定：プロジェクトのルートに`.devcontainer`ディレクトリを作成し、その中に`Dockerfile`と`devcontainer.json`を作成します。DockerfileにはPythonと必要なPythonライブラリのインストールを指定し、devcontainer.jsonにはVS Codeの設定や開くべきポートなどを記述します。
   - Dev Containerの起動：VS Codeでプロジェクトを開き、コマンドパレットから"Remote-Containers: Reopen in Container"を選択し、Dev Containerを起動します。

3. **.envファイルの作成**：取得したOpenAIのAPIキーを.envファイルに保存します。このスクリプトではdotenvパッケージを使用して環境変数をロードします。

4. **OpenAIのChat modelの選択**：利用するChat model（この例では"gpt-3.5-turbo-0613"）を選択します。モデルの選択は、取得したい結果や性能によって異なります。


## 指定したJSONを情報が取得可能な時に取得する例

```python
import openai
import os
from dotenv import load_dotenv
import json

def load_api_key():

    """
    環境変数からOpen AIのAPIキーを読み込み、Open AIのAPIに設定します。
    """

    load_dotenv()
    openai.api_key = os.getenv("OPEN_AI_API")


def user_info(user, name, age, gender):

    """
    ユーザ情報を入力として受け取り、それをJSON形式に変換します。

    :param user: ユーザの識別子
    :type user: str
    :param name: ユーザの名前
    :type name: str
    :param age: ユーザの年齢
    :type age: int
    :param gender: ユーザの性別
    :type gender: str
    :return: JSON形式に変換されたユーザ情報
    :rtype: str
    """

    info = {
        "user": user,
        "name": name,
        "age": age,
        "gender": gender
    }
    return json.dumps(info)


def create_function_user_info():

    """
    ユーザ情報収集関数の定義を行います。

    :return: ユーザ情報収集関数の定義
    :rtype: dict
    """

    return {
        "name": "user_info",
        "description": "Collect user information and convert it to JSON format",
        "parameters": {
            "type": "object",
            "properties": {
                "user": {
                    "type": "string",
                    "description": "The identifier of the user",
                },
                "name": {
                    "type": "string",
                    "description": "The name of the user",
                },
                "age": {
                    "type": "integer",
                    "description": "The age of the user",
                },
                "gender": {
                    "type": "string",
                    "enum": ["male", "female"],  # enum is added here
                    "description": "The gender of the user",
                }
            },
            "required": ["user", "name", "age", "gender"],
        }
    }


def create_chat_functions(function_list):

    """
    チャット機能のリストを作成します。

    :param function_list: 関数のリスト
    :type function_list: list
    :return: 関数のリスト
    :rtype: list
    """
    
    return function_list


def create_chat(user_msg, functions):

    """
    チャット機能のリストを作成します。

    :param function_list: 関数のリスト
    :type function_list: list
    :return: 関数のリスト
    :rtype: list
    """
    
    return openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": user_msg},
        ],
        functions=functions,
        function_call="auto",
    )


def check_function_call(message):

    """
    メッセージから関数呼び出しをチェックします。

    :param message: メッセージ
    :type message: str
    :return: 関数呼び出しの結果が含まれる場合はユーザ情報の辞書、含まれない場合はエラー
    :rtype: dict | Exception
    """

    if message.get("function_call"):
        function_name = message["function_call"]["name"]
        
        if function_name == "user_info":
            stringified_args = message["function_call"].get("arguments")

            if stringified_args:  # Check if stringified_args isn't None
                function_args = json.loads(stringified_args)  # Convert the stringified JSON to a Python dictionary
            
                # Get the arguments from the function_args dictionary
                user = function_args.get("user")
                name = function_args.get("name")
                age = function_args.get("age")
                gender = function_args.get("gender")

                # If any of the necessary arguments is None we raise an exception
                if None in [user, name, age, gender]:
                    raise Exception("Function arguments are missing")
                return function_args
            else:
              raise Exception("Function arguments are missing")
    else:
        raise Exception("Function call is missing")


def main():

    """
    APIキーの読み込み、チャット機能の作成、チャットコンプリーションの作成、関数呼び出しのチェックという一連の処理を実行します。
    functions機能を理解しやすくするためにこのコードではユーザ情報の辞書を取得するのが目的となっています。
    """

    load_api_key()

    user_msg = "Hello!, My name is John. I'm 28 years old and my id number is 123456789."
    function_list = [create_function_user_info()]  # 初期の関数リスト
    # 他の関数を追加する場合は、ここでfunction_listに追加していく
    functions = create_chat_functions(function_list)
    completion = create_chat(user_msg, functions)

    message = completion.choices[0]["message"]
    
    # userデータの辞書を取得する
    print(check_function_call(message))


if __name__ == "__main__":
    main()

```

### コードの目的

このコードはOpenAIのGPTモデルと対話を行いつつ、ユーザーデータを取得するためにfunction calling機能を使うためのコード例です。

関数呼び出し本来の使い方ではありません。

OpenAIの最新モデル(gpt-3.5-turbo-0613など)は、ユーザーからの入力に基づいて特定の関数を呼び出すべきかどうかを判断する能力を持ってことを理解するサンプルです。


### 主な機能

このプロジェクトのメインの機能は次のとおりです：

- ユーザー情報収集関数の定義
- チャット機能のリストを作成
- チャットの生成
- チャットからのユーザー情報の取得

### 使い方

以下は、このプロジェクトのメイン関数の一部です。以下のようにユーザーからのメッセージを引数に取り、それに対する応答を生成します：

```python
def main():
    load_api_key()

    user_msg = "Hello!, My name is John. I'm 28 years old and my id number is 123456789."
    function_list = [create_function_user_info()]  # 初期の関数リスト
    # 他の関数を追加する場合は、ここでfunction_listに追加していく
    functions = create_chat_functions(function_list)
    completion = create_chat(user_msg, functions)

    message = completion.choices[0]["message"]
    
    print(check_function_call(message))  # userデータの辞書を取得する 
```

上記のコードで注目すべきは `completion = create_chat(user_msg, functions)` 部分です。この部分でGPTモデルがユーザーメッセージの解析を行い、与えられた関数を適時にコールします。 

次に、`check_function_call(message)` 部分で、チャットの返信から関数呼び出しを確認し、必要なユーザーデータを取得します。

各関数は具体的な動作を明確にするため、それぞれ適切にコメントがつけられています。

### 注意点
本コードはGPTモデルが関数呼び出しを正しく判断するための事例であり、実際のバックエンドAPIや外部APIと接続するための機能は組み込まれていません。また、取得される情報を辞書形式で返すように設計されています。これはあくまで例示であり、各自の目的や必要に応じて適切な関数を定義し、引数を設定するなど、必要な改変を加えるべきです。