見出し画像

ちゃんと掛け算ができるChatGPTを作ってみる【function call APIの使い方】

はじめに

ChatGPTで誰でも簡単にプラグインを作れそうな仕組みが発表されたので、早速使ってみました。

まず、ChatGPTには「拡張機能」と「プラグイン」という似た概念がありますが、両者は全くの別物です。「拡張機能」はChatGPTの脳はそのままでUIを拡張するもの、「プラグイン」はChatGPTの脳を外部のサーバーに置き換えてしまうためのものです。

ChatGPTでプラグインを使う最大のメリットは、「ChatGPT自身が学習していない範囲の事象を正確に応答する」ということだと考えています。

すなわち、ChatGPTのプラグイン開発を行えば、例えば社内ドキュメントや、現場のセンシングデータなど、ChatGPTの学習に使われていない「閉じた情報」にアクセスして、業務活用することも考えられます。
(余談ですがChatGPTはAPI利用の場合、送信したデータは学習には使われませんが、OpenAIに送ることには変わりないので、個人情報などはマスキングする必要があると思います。)

また、あまり知られてないですが、ChatGPTは桁数が上がると正確な計算ができなくなります。過去にYoutubeでも紹介していましたが、簡単な計算問題でもプロンプトを使わない場合、下のように平気で間違えます。

そのため、今回は新しく発表されたfunction callingという機能を使って、計算プラグインを自作することにより、wolfram alphaのように正確な計算をChatGPTに実行させ、その感触を確かめてみようと思います。


API(関数)を準備する

まずは必要なパッケージのインポートとAPIキーの読み込み。

import openai
import json

openai.api_key = "YOUR_OPENAI_API_KEY"

続いて四則演算の関数を用意。

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiple(a, b):
    return a * b

def divide(a, b):
    return a / b

ChatGPTが正しく関数を利用するために説明を用意します。

    function_description = [
        {
            "name": "calculate",
            "description": "2つの変数を計算する",
            "parameters": {
                "type": "object",
                "properties": {
                    "order": {
                        "type": "string",
                        "description": "「足す、引く、掛ける、割る」いずれかの演算方法",
                    },
                    "a": {
                        "type": "string",
                        "description": "1つめの数値",
                    },
                    "b": {
                        "type": "string",
                        "description": "2つめの数値",
                    }
                },
                "required": ["order", "a", "b"],
            },
        },
    ]

これで準備完了です。

実行すべきAPIを判定させる

早速実行してみます。

message = "1243かける342は?"

def get_order(message, function_description):

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[{"role": "user", "content": message}],
        functions=function_description,
        function_call="auto",
    )

    order_response = response["choices"][0]["message"]
    return order_response

order_response = get_order(message, function_description)

function_name = order_response["function_call"]["name"]
order = json.loads(order_response["function_call"]["arguments"])

print("判定内容:")
print("function", function_name)
print("order", order.get("order"))
print("a", order.get("a"))
print("b", order.get("b"))

これで以下の結果が返ってきます。

判定内容:
function calculate
order 掛ける
a 1243
b 342

ちゃんと変数と演算方法を認識してくれています。

実行結果を使って、応答文を生成する

まずはAPIを実行する仕組みを作ります。

def run_calculator(function_name, order):

    if function_name == "calculate":

        if order.get("order") == "足す":
            function_response = add(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "引く":
            function_response = subtract(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "掛ける":
            function_response = multiple(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "割る":
            function_response = divide(float(order.get("a")), float(order.get("b")))
        else:
            return None
    else:
        return None

    return function_response

これでも実行自体は事足りますが、せっかくChatGPTを使っているので、応答文も自然な文にしてみます。

ということで、応答文を作成させます。

def get_response(message, function_name, function_response):

    result_response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[
            {"role": "user", "content": message},
            order_response,
            {
                "role": "function",
                "name": function_name,
                "content": str(function_response),
            },
        ],
    )

    result = result_response["choices"][0]["message"]["content"]
    return result

if order_response.get("function_call"):
    function_response = run_calculator(function_name, order)
    if function_response is not None:
        result = get_response(message, function_name, function_response)
    else:
        result = "不適切な判定結果"
else:
    result = "関数が検出されませんでした"

print("応答結果:")
print(result)

これを実行すると以下のように表示されます。

応答結果:
1243かける342は425106です。

当然ChatGPTの脳ではなく、上記のmultiple関数の実行結果なので必ず正確なものが返ってきます。

所感

驚くべきことは関数の説明を自然言語で行うことでした。本来はベクトル類似度などを計算する仕組みが必要ですが、すべてマスキングされていてAIエンジニアでなくとも簡単にAPIを作れるのが凄いと思います。

ただ、上記のfunction_descriptionでは、APIの判定結果としてorderに「掛ける」ではなく、「multiple」と英語で返ってしまうことがあったので、関数説明には英語を使ったり、プロンプトを工夫する必要があると思いました。

(enumで解決できました。下記全文に反映済み)

コードサンプル全文

import openai
import json

openai.api_key = "YOUR_OPENAI_API_KEY"

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiple(a, b):
    return a * b

def divide(a, b):
    return a / b

def get_order(message, function_description):

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[{"role": "user", "content": message}],
        functions=function_description,
        function_call="auto",
    )

    order_response = response["choices"][0]["message"]
    return order_response

def run_calculator(function_name, order):

    if function_name == "calculate":

        if order.get("order") == "足す":
            function_response = add(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "引く":
            function_response = subtract(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "掛ける":
            function_response = multiple(float(order.get("a")), float(order.get("b")))
        elif order.get("order") == "割る":
            function_response = divide(float(order.get("a")), float(order.get("b")))
        else:
            return None
    else:
        return None
    return function_response

def get_response(message, function_name, function_response):

    result_response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[
            {"role": "user", "content": message},
            order_response,
            {
                "role": "function",
                "name": function_name,
                "content": str(function_response),
            },
        ],
    )
    result = result_response["choices"][0]["message"]["content"]
    return result

if __name__ == '__main__':

    function_description = [
        {
            "name": "calculate",
            "description": "2つの変数を計算する",
            "parameters": {
                "type": "object",
                "properties": {
                    "order": {
                        "type": "string",
                        "description": "「足す、引く、掛ける、割る」いずれかの演算方法",
                        "enum": ["足す", "引く", "掛ける", "割る"],
                    },
                    "a": {
                        "type": "string",
                        "description": "1つめの数値",
                    },
                    "b": {
                        "type": "string",
                        "description": "2つめの数値",
                    }
                },
                "required": ["order", "a", "b"],
            },
        },
    ]

    message = "1243かける342は?"

    order_response = get_order(message, function_description)
    function_name = order_response["function_call"]["name"]
    order = json.loads(order_response["function_call"]["arguments"])

    print("判定内容:")
    print("function", function_name)
    print("order", order.get("order"))
    print("a", order.get("a"))
    print("b", order.get("b"))

    if order_response.get("function_call"):
        function_response = run_calculator(function_name, order)
        if function_response is not None:
            result = get_response(message, function_name, function_response)
        else:
            result = "不適切な判定結果"
    else:
        result = "関数が検出されませんでした"

    print("応答結果:")
    print(result)

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