AI

gpt-4-turbo Function calling AI秘書作ってみよう

2024年5月3日

AI秘書

みなさんこんばんは。良いGWお過ごしですか?

本日はGW中も仕事や趣味で生成AIを触られている方向けにFunction callingに関する記事を書きました。休暇の気分転換に読んでみてください。


Function callingとは

OpenAIモデルにてユーザーからのインプット情報が外部の関数の呼び出しの必要有無を教えてくれる仕組みです。OpenAIモデルに学習されていない情報をユーザーへの回答で使用したいときに自作の関数を使って処理させる場面などで有用です。

OpenAI公式サイト - Function calling -


Function callingは少し難しい印象を持っている方も多いと思うので、ここでは実際のコードベースで理解を深めてもらいたいです。

ハンズオンではAI秘書が適しているのでここから実践してみましょう!

Function callingの流れ

  1. アプリケーション内に関数を作成する(def で実際のビジネスロジック記載)
  2. OpenAIに渡す関数の説明情報を作成(JSONオブジェクト)
  3. OpenAI ChatAPIのfunctionsパラメータに2で作成したオブジェクトを渡す
  4. OpenAIからのレスポンスを解析して、呼び出すべき関数と渡す引数情報を抽出
  5. 4の情報を元に実際のアプリケーション内の関数を呼び出す
  6. 結果を自由に扱う

Function callingを利用できるOpenAIモデル

公式サイト情報です。今回は最新のgpt-4-turboを利用したいと思います。

models: gpt-4-turbogpt-4-turbo-2024-04-09gpt-4-turbo-previewgpt-4-0125-previewgpt-4-1106-previewgpt-4gpt-4-0613gpt-3.5-turbogpt-3.5-turbo-0125gpt-3.5-turbo-1106, and gpt-3.5-turbo-0613

Function calling - OpenAI API

ハンズオン

ここからは実践編となります。実際にPythonでFunction callingの概念を秘書AI開発を通してみていきましょう。

0. 事前準備

以下のファイル構成を準備します。Dockerやvenvなどで適宜Pythonの実行環境は準備してください。

app
 |__ .env
 |__ secretary.py
 |__ main.py

つづいては、必要なライブラリをインストールします。

※今回venvで実行しているため.envパラメータを読み取るためにpython-dotenvをインストールします。

pip install openai python-dotenv

1. アプリケーション内に関数を作成する

以下は秘書AIの機能を定義したアプリケーションファイルです。

ビジネスロジックは省略していますが、実際に開発する際は、各種SaaSサービスのAPIをコールするなどが必要です。

3つしか定義していませんが、秘書AIとなれば皆さんも実装したい機能がたくさんあると思います!

def search_restaurant(category, location):
    print('[+] Searching for a restaurant in', location, 'in the category of', category)
    
    # write business logic here
    ...

def search_hotel(location, checkin, checkout):
    print('[+] Searching for a hotel in', location, 'from', checkin, 'to', checkout)
    
    # write business logic here
    ...
    
def create_schedule(title, date, time, location, description):
    print('[+] Creating a schedule for', title, 'on', date, 'at', time, 'in', location, 'with the description of', description)
    
    # write business logic here
    ...

2. OpenAIに渡す関数の説明情報を作成

ここではStep1で作成した秘書AIの関数の呼び出しが必要かOpenAIに判断してもらうための情報を構築します。

今回秘書AIには3つの関数を定義したので、3つ分のfunctionオブジェクトを準備しています。

各functionオブジェクトには以下のパラメータの設定が必要となります。

  • name: 関数名
  • description: 関数の処理の概要説明
  • parameters: 引数情報を定義
  • required: parametersで指定した引数のうち、必ず必要な情報を設定
openai_function_objects = [
    {
        "name": "search_restaurant",
        "description": "指定されたジャンルと場所からレストランを検索する",
        "parameters": {
            "type": "object",
            "properties": {
                "category": {
                    "type": "string",
                    "description": "料理のジャンル, e.g. イタリアン, 和食",
                },
                "location": {
                    "type": "string",
                    "description": "都道府県や市、町の名前, e.g. 東京都文京区",
                },
            },
            "required": ["category", "location"],
        },
    },
    {
        "name": "search_hotel",
        "description": "指定された場所、チェックイン・チェックアウト情報からホテルを検索する",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "都道府県や市、町の名前, e.g. 東京都文京区",
                },
                "checkin": {
                    "type": "string",
                    "description": "チェックイン時間",
                },
                "checkout": {
                    "type": "string",
                    "description": "チェックアウト時間",
                },
            },
            "required": ["location", "checkin", "checkout"],
        },
    },
    {
        "name": "create_schedule",
        "description": "指定されたタイトル、日時、場所情報、詳細説明を元に予定を作成する",
        "parameters": {
            "type": "object",
            "properties": {
                "title": {
                    "type": "string",
                    "description": "予定タイトル",
                },
                "date": {
                    "type": "string",
                    "description": "予定日",
                },
                "time": {
                    "type": "string",
                    "description": "予定時間",
                },
                "location": {
                    "type": "string",
                    "description": "場所情報",
                },
                "description": {
                    "type": "string",
                    "description": "予定の詳細説明",
                },
            },
            "required": ["title", "date", "time", "location", "description"],
        },
    },
]

3. OpenAI ChatAPIのパラメータにfunctionオブジェクトを渡す

続いては実際にOpenAI ChatAPIを使ってみます。

以下コード全文になります。(このコードはStep4などでも解説に使用します。)

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

import secretary

load_dotenv()

openai_client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),
)

function_mapper = {
    "search_restaurant": secretary.search_restaurant,
    "search_hotel": secretary.search_hotel,
    "create_schedule": secretary.create_schedule,
}

openai_function_objects = [
    {
        "name": "search_restaurant",
        "description": "指定されたジャンルと場所からレストランを検索する",
        "parameters": {
            "type": "object",
            "properties": {
                "category": {
                    "type": "string",
                    "description": "料理のジャンル, e.g. イタリアン, 和食",
                },
                "location": {
                    "type": "string",
                    "description": "都道府県や市、町の名前, e.g. 東京都文京区",
                },
            },
            "required": ["category", "location"],
        },
    },
    {
        "name": "search_hotel",
        "description": "指定された場所、チェックイン・チェックアウト情報からホテルを検索する",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "都道府県や市、町の名前, e.g. 東京都文京区",
                },
                "checkin": {
                    "type": "string",
                    "description": "チェックイン時間",
                },
                "checkout": {
                    "type": "string",
                    "description": "チェックアウト時間",
                },
            },
            "required": ["location", "checkin", "checkout"],
        },
    },
    {
        "name": "create_schedule",
        "description": "指定されたタイトル、日時、場所情報、詳細説明を元に予定を作成する",
        "parameters": {
            "type": "object",
            "properties": {
                "title": {
                    "type": "string",
                    "description": "予定タイトル",
                },
                "date": {
                    "type": "string",
                    "description": "予定日",
                },
                "time": {
                    "type": "string",
                    "description": "予定時間",
                },
                "location": {
                    "type": "string",
                    "description": "場所情報",
                },
                "description": {
                    "type": "string",
                    "description": "予定の詳細説明",
                },
            },
            "required": ["title", "date", "time", "location", "description"],
        },
    },
]

if __name__ == "__main__":    
    input_text = "明日(2024/5/1)の夜大阪駅周辺に宿泊します。神戸で21時に会食が終わるので、その後チェックインし、翌日の朝11時の関空の国際線の飛行機に乗れるようにチェックアウトします。ホテルを探してください。"
    
    response = openai_client.chat.completions.create(
        model="gpt-4-turbo",
        max_tokens=4096,
        n=1,
        temperature=0.1,
        messages=[
            {"role": "system", "content": "You are a greate secretary in the world."},
            {"role": "user", "content": input_text},
        ],
        functions=openai_function_objects,
    )
    
    #print("response model_dump_json:")
    print(response.model_dump_json(indent=2))
    
    finish_reason = response.choices[0].finish_reason
    if finish_reason == "function_call":
        function_name = response.choices[0].message.function_call.name
        function_args = json.loads(response.choices[0].message.function_call.arguments)
        print("function_name:", function_name)
        print("function_args:", function_args)
        
        if function_name in function_mapper:
            function = function_mapper[function_name]
            function(**function_args)
        else:
            print("function_name is not found. Please check 'function_mapper' variable.")
    else:
        print("function_call is skip .")

functions=openai_function_objects,の部分が重要なポイントです。

ここでStep2で定義したfunctionオブジェクトを渡します。

4. OpenAIからのレスポンスを解析して、呼び出すべき関数と渡す引数情報を抽出

さきほどのmain.pyの以下部分で呼ぶべき関数と渡す引数情報を抽出しています。

外部関数の呼び出しが必要な時はfinish_reasonfunction_callが入るので、これをトリガに詳細情報を抽出することになります。

    finish_reason = response.choices[0].finish_reason
    if finish_reason == "function_call":
        function_name = response.choices[0].message.function_call.name
        function_args = json.loads(response.choices[0].message.function_call.arguments)
        print("function_name:", function_name)
        print("function_args:", function_args)

実際の結果が以下です。

インプットとして「明日(2024/5/1)の夜大阪駅周辺に宿泊します。神戸で21時に会食が終わるので、その後チェックインし、翌日の朝11時の関空の国際線の飛行機に乗れるようにチェックアウトします。ホテルを探してください。」を与えました。

良い感じに移動時間なども考慮してチェックイン、チェックアウトの時間を返してくれるのがいいですね!

function_name: search_hotel
function_args: {'location': '大阪駅', 'checkin': '2024-05-01T22:00', 'checkout': '2024-05-02T08:00'}

5. 4の情報を元に実際のアプリケーション内の関数を呼び出す

この時点でOpenAIはあくまで外部関数の呼び出しの必要性を教えてくれた状態です。

ここから実際にその関数を呼び出しましょう。呼び出しを行いやすくするためにmain.pyの最初に関数名と関数の辞書変数を定義しておきます。

function_mapper = {
    "search_restaurant": secretary.search_restaurant,
    "search_hotel": secretary.search_hotel,
    "create_schedule": secretary.create_schedule,
}

そして、OpenAIのレスポンス解析後に上記のfunction_mapperを元に関数を呼び出します。念のため、レスポンスに記載の関数名がfunction_mapperに存在するかのチェックは入れる方が望ましいです。

# 省略
...    
    finish_reason = response.choices[0].finish_reason
    if finish_reason == "function_call":
        function_name = response.choices[0].message.function_call.name
        function_args = json.loads(response.choices[0].message.function_call.arguments)
        print("function_name:", function_name)
        print("function_args:", function_args)
        
        if function_name in function_mapper:
            function = function_mapper[function_name]
            function(**function_args)
        else:
            print("function_name is not found. Please check 'function_mapper' variable.")
    else:
        print("function_call is skip .")

上記を実行すると以下の結果が出ます。無事関数の呼び出しが成功したことがわかります!!

function_name: search_hotel
function_args: {'location': '大阪駅', 'checkin': '2024-05-01T22:00', 'checkout': '2024-05-02T08:00'}
[+] Searching for a hotel in 大阪駅 from 2024-05-01T22:00 to 2024-05-02T08:00

これで秘書AIハンズオンの開発が完了です。これを拡張することであなただけの秘書AIを作ることも夢ではありません!

6. 結果を自由に扱う

実際の関数の実行結果は再度OpenAIに解析させるも良し、そのまま活用するも良し。応用は無限大です。

まとめ

いかがでしょうか?Function callingを使うことで生成AIを用いたプロダクト開発がかなりスムーズになる気がしませんか?

是非皆さんも自分だけの秘書AIを開発してみてください!

当社は日々、生成AIを用いた最先端プロダクト開発を行っておりますので、お気軽にご相談もお待ちしております。

ATD InnoSolutions - お問い合わせ -

お問い合わせ

  • この記事を書いた人
  • 最新記事

石川 侑扶(Yusuke Ishikawa)

IoT, AI, デジタルツイン等の最先端な開発経験とセキュリティコンサルティング経験を元に世の中を変えます!

-AI
-