💡

OpenAI APIのJSON ModeとFunction Callingの精度比較

2023/12/05に公開
2

こんにちは! @koonagiです。
2023年も変わらずインフラエンジニアとデータ基盤エンジニアを兼務しておりまして、RDSのアップデートしたり、Snowflake使ったデータ基盤作ったり色々楽しくやっておりますー。一年早かったなー。

今日はゆるっとこれまで気になってたけど、調査できなかったOpenAI APIの仕様について調査したのでその結果を書いていきます!

やったこと

システムにOpenAI APIを組み込むときに、OpenAIのJSON ModeやFunction Callingどちらのほうが安定するのか知りたかったので検証してみました。

システムに組み込む際は安定して形式をしていできるように、これまではFunction Callingを使うことが多かったと思いますが、先月JSON Modeがリリースされたので精度的にどちらが良いのか気になったので今回の検証の背景ですー!

方法/条件

  • ブログ記事から特定の要素を抜いてJSON形式で返すタスクをJSON ModeとFunction Callingで実施。
  • 各100回APIコールして、JSON形式で返ってくるか、要求したスキーマで返ってくるかについて確認。
  • モデル: gpt-3.5-turbo-1106

ブログ: https://stafes.co.jp/pressrelease/29563.html
上記のブログから、会社名/サービス名/人物名/県名 の4つを抽出するようにしてみました。

返却例

{'company': 'スターフェスティバル', 'service_name': 'ごちクル', 'person': '岸田祐介', 'prefecture': '沖縄'}

結果

種別 総計 JSON形式エラー スキーマエラー 平均応答時間
JSON Mode 94/100
(成功率 94%)
6件 0件 6秒
(成功だけだと3.1秒)
Function Calling 96/100
(成功率 96%)
0件 4件 3秒

精度に関して

  • JSON Modeでは時折改行のみが返されることがあり、エラー時の応答に時間がかかることがあった。
  • Function CallingはJSON形式での返答は安定していたが、特定のスキーマが欠ける場合があった。
  • JSON Modeでの抽出内容はFunction Callingに比べてブレが少なく、例えば会社名の抽出精度はJSON Modeが99%でFunction Callingが92%だった。

JSON Modeのほうに関してはプロンプトを変えると精度があるとかもありそう。

[202404追記]
コメントでいただいていますが、一部コードの誤りがあり、その影響で特定のスキーマが抜けている可能性がありますのでご注意ください。
https://zenn.dev/link/comments/56c0d9cc4eb11b

JSON Mode

■ 成功パターン

■ 失敗パターン

スプレットシートだと見えにくいんですが、以下のように大量の改行コードが返ってくることがありました。

NG,"\n
\n
\n
",[JSONDecodeError('Expecting value: line 4 column 37 (char 308)')],23.7

Function Calling

■ 成功パターン

■ 失敗パターン
たまに一部の項目が抜けてきますね。

プロンプト

先程も書きましたが、結構あっさりプロンプト書いているので、もう少し作り込むと精度変わりそうです。

JSON Mode

    # 期待するJSONレスポンス
    {"company":"株式会社山崎","service_name":"山崎サービス","person":"山崎太郎","prefecture":"東京都"}

Function Calling

    functions=[
            {
                "name": "get_schedule",
                "description": "ニュース記事の中から会社名、サービス名、登場人物名、地名を抽出します。",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "company": {
                            "type": "string",
                            "description": "会社名"
                        },
                        "service_name": {
                            "type": "string",
                            "description": "サービス名"
                        },
                        "person": {
                            "type": "string",
                            "description": "登場人物名"
                        },
                        "prefecture": {
                            "type": "string",
                            "description": "県名"
                        },
                    },
                    "required": ["company","service_name","person","place"]
                }
            }
    ],

まとめ

そんなに多くないサンプル数ではありますが、Function Callingは全体的な成功率と応答時間において優れているという結果でした。ただ、Function Callingは抽出結果にJSON Modeと比べるとブレがあるので、2つの機能の特徴を捉えながらシステム開発するのがいいのかなと言う感じです。(こっちのほうがいい!!と言えなくてすいません

この検証ではGPT-3.5を使用していますが、GPT-4の使用やプロンプトの変更によって結果や応答速度が大きく変わる可能性がありますので、システム開発の際はそちらについても検証してみてください...!

サンプルコード

共通部分

import openai
import json 
import time
import pandas as pd
openai.api_key = ""

def check_json_schema(data):
    try: 
        data = json.loads(data)
    except ValueError as e:
        return ['NG',data,[e]]
    # 期待されるスキーマの定義
    expected_schema = {
        'company': str,
        'service_name': str,
        'person': str,
        'prefecture': str
    }

    # エラーを記録するリスト
    errors = []

    # スキーマの各キーに対してチェックを行う
    for key, expected_type in expected_schema.items():
        if key not in data:
            errors.append(f"Key '{key}' is missing.")

    if len(errors) == 0:
        return ['OK',data,""]
    else:
        return ['NG',data,errors]

message = '''
日本最大級のフードデリバリーサービス「ごちクル」
沖縄県で開催されたバスケットボールの国際大会にて
選手・関係者向けに累計22,274食の食事をお届け
近隣エリアの飲食店等製造パートナー・配送パートナーと連携し、現地のあらゆる食事ニーズを包括的に管理・サポート
日本最大級のフードデリバリーサービス「ごちクル」を運営するスターフェスティバル株式会社(本社:東京都港区、代表取締役CEO:岸田祐介)は、2023年8月14日から9月3日まで沖縄アリーナにて開催された、バスケットボールの国際大会にて、累計22,274食の食事をお届けしたことをご報告いたします。



スターフェスティバルは、日本全国の飲食店・物流ネットワークを活用したフードデリバリーサービスの展開を行なっております。このネットワークを活かし、2019年のラグビー国際大会、東京2020大会ではメディアや運営・ボランティアスタッフのお弁当の手配を担当したほか、2022年以降には「川崎フロンターレ」「ガンバ大阪」などをはじめとするスポーツチーム5団体とパートナー契約を結ぶなどスポーツ振興に注力をしております。

今大会においては、沖縄県および周辺地域の製造・配送パートナーと連携しながら、現地での食事に関する管理業務を包括的にお引き受けいたしました。大会期間中は、選手から、大会運営関係者、メディア、VIPにいたるまでの幅広いお客さまに向け、各ニーズに合わせたメニュー構成でお弁当・オードブル・ケータリングなどあらゆる形態の食事をご提供いたしました。サービス開始以来、スポーツ大会においては過去最大規模の提供食数となります。

これまで10年以上に渡って築き上げた商品ラインナップやメニュー編成のノウハウ、ロジスティクスの体制のもと、グローバルなゲストにも安心して食事を楽しんでいただけるヴィーガン・フレンドリーハラルへの対応や、大会ルールに則り食材を選定した選手向けメニューのご用意も含め、現地でのご要望に柔軟に対応しながらバリエーション豊かな食事提供を実現してまいりいました。

「ごちそうで 人々を より 幸せに」を理念に掲げるスターフェスティバルは、今後もスポーツチーム・団体とのパートナーシップのさらなる強化を通じ、アスリートにエールを送り、スポーツ・地域社会の振興に貢献してまいります。
'''

JSON Modeのサンプルコード

results = []
for i in range(100):
    system_prompt='''
    # 期待するJSONレスポンス
    {"company":"株式会社山崎","service_name":"山崎サービス","person":"山崎太郎","prefecture":"東京都"}
    '''

    start = time.time()  
    response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-1106",
    messages=[
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": message},
    ],
        response_format={"type": "json_object"}

    )

    data = response["choices"][0]["message"]["content"]

    # 時間計測
    end = time.time() 
    time_diff = end - start
    formatted_time = f"{time_diff:.1f}"

    # データチェック
    result = check_json_schema(data)
    result.append(formatted_time)
    results.append(result)

    print(result)

pandas_data = pd.DataFrame(results,columns=['result','data','error','time'])
print(pandas_data)

pandas_data.to_csv('jsonmode_data.csv',index=False)

Function Callingのサンプルコード

results = []
for i in range(100):
    start = time.time()  
    response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-1106",
    messages=[
    {"role": "user", "content": message},
    ],
    functions=[
            {
                "name": "get_schedule",
                "description": "ニュース記事の中から会社名、サービス名、登場人物名、地名を抽出します。",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "company": {
                            "type": "string",
                            "description": "会社名"
                        },
                        "service_name": {
                            "type": "string",
                            "description": "サービス名"
                        },
                        "person": {
                            "type": "string",
                            "description": "登場人物名"
                        },
                        "prefecture": {
                            "type": "string",
                            "description": "県名"
                        },
                    },
                    "required": ["company","service_name","person","place"]
                }
            }
    ],
    function_call="auto",
    )
    data = response["choices"][0]["message"]["function_call"]["arguments"]

    # 時間計測
    end = time.time() 
    time_diff = end - start
    formatted_time = f"{time_diff:.1f}"

    # データチェック
    result = check_json_schema(data)
    result.append(formatted_time)
    results.append(result)


pandas_data = pd.DataFrame(results,columns=['result','data','error','time'])
print(pandas_data)

pandas_data.to_csv('func_data.csv',index=False)
スタフェステックブログ

Discussion

ピン留めされたアイテム
tattuutattuu

【Function Callingで、一部の項目が抜けてしまう件について】
エラー内容も含めて考えると、以下の2点が原因でたまに抜けてしまうのかな?、と思ったので記載致します。
見当違いな事を言っていたらすいません!

▼1つ目
変数functionsのdescriptionでは地名になっているが、parameters['properties']['prefecture']['description']では県名になっており、一致していない。

▼2つ目
変数functionsの、parameters['properties']の中の地名部分はprefectureになっているが、parameters['required']の部分では、placeになっており、一致していない。

koonagi (kohei yamazaki)koonagi (kohei yamazaki)

tattuuさん

コメントいただいて、ありがとうございます!
パラメータの指定で整合できてない部分が結構ありますね😢
ご指摘のところを合わせたら結果変わりそう..。確認してみます!