Node-RED で色名を聞くと RGB 値を JSON データで返答する ChatGPT API の仕組みを新機能 Function calling を導入したメモ

Node-RED でつくった色名を聞くと RGB 値を JSON データで返答する ChatGPT API の仕組みを新機能 Function calling を導入したメモです。

ChatGPT API に Function calling という機能が導入された

Function calling and other API updates

こちらのアップデートで、Function calling という機能が導入されたことを知りました。しかも、先進の GPT 4 だけじゃなくて、GPT-3.5 にもきてるのでレスポンスの速さを期待する仕組みでも使いやすい!

【速報 : OpenAI APIがアップデートされました!!】GPT-4, GPT-3.5の0613版がリリース / GPT-3.5のコンテキスト長が4倍に / 新機能Function callingも追加 | DevelopersIO

DevelopersIO さんの記事がとても分かりやすいです。

[OpenAI] Function callingで遊んでみたら本質が見えてきたのでまとめてみた | DevelopersIO

特にこの記事が分かりやすく「Function callingというよりFunction call preparingでした。」というところで、やっとピンときました!まさに、JSON データでねじ伏せるためにプロンプトをめっちゃ苦労していた私にうってつけの機能で、他の仕組みに連携するために、事前に準備した関数に対して、人間の口語(自然言語)ではなく、JSON オブジェクトやスキーマを設定していい感じにやり取りできる機能のようです!

以前の仕組みで試してみる

LED への RGB 値を JSON データで返答する ChatGPT API の仕組みを Node-RED でブラッシュアップしたメモ

こちらの記事の、Node-RED でつくった色名を聞くと RGB 値を JSON データで返答するという、以前の仕組みをを使ってみます。

image

今までは content と書かれている template ノードの中で、結構ガチガチにお願いをしていました。

以下の仕様に従って答えてください。

# 返答するJSON データ
{"result":true,"type":"led","r":255,"g":0,"b":0,"message":"説明"}

# ルール
- 色名がRGB値に認識されたときはresult値はtrueを返します。
- type 値はledで固定です。
- r値は色名がRGB値に認識されたときのR値です。
- g値は色名がRGB値に認識されたときのG値です。
- b値は色名がRGB値に認識されたときのB値です。
- 色名がRGB値に認識されたときの追加の説明はmessage値に入れてください。
- たとえば「赤」というメッセージの場合、RGB値はRが255、G値が0、B値が0なので、JSONデータは {"result":true,"type":"led","r":255,"g":0,"b":0,"message":"説明"} になります。
- 色名がRGB値に認識されなかったときはresult値はfalseを返します。
- 色名がRGB値に認識されなかったときの説明はmessage値に入れてください。
- ここまでのルールにおいての例外は {"result":false,"type":"led","message":"色名が認識されない例外処理です"} と返してください。

# 返答前にチェック!
- 返答データはJSONデータのみです。

仕様は以上です。

今回は「{{payload}}」というメッセージについて返答ください。

まず、API 設定の準備

image

http request ノードに送る API 設定の change ノードの仕組みを調整します。モデルを変更し、Function calling の設定を設置する感じです。

image

このような仕組みでした。msg.payload のところは、

{
   "model":"gpt-3.5-turbo",
   "messages":[
       {
           "role":"user",
           "content":content
       }
   ],
   "temperature":0.7
}

となっていました。こちらを変更したものが以下です。

image

まず、msg.payload は、モデルが gpt-3.5-turbo-0613 に変えたのでこのようになっています。

{
   "model":"gpt-3.5-turbo-0613",
   "messages":[
       {
           "role":"user",
           "content":content
       }
   ],
   "temperature":0.7
}

ここはシンプルです。さらに [OpenAI] Function callingで遊んでみたら本質が見えてきたのでまとめてみた | DevelopersIO を参考にして Function calling 機能を追加しました。

image

まず、function_ call 値は auto で。

functions 値はこのように rgb_json は「色名から RGB 値の情報を得られた場合」、rgb_json_not_found は「色名から RGB 値の情報を得られなかった場合」で分けました。これを、content と書かれている template ノードの方で、うまく色名から RGB 値の情報を得られたかどうかで分岐する指示を出します。

今まではプロンプト側でガッツリ設定したものを、丁寧に JSON スキーマを意識しながら移植します。

[
    {
        "name": "rgb_json",
        "description": "色名から RGB 値の情報を得られた場合",
        "parameters": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "description": "led という値が固定値を入力されます"
                },
                "result": {
                    "type": "boolean",
                    "description": "色名が RGB値 で認識されたので true は認識が入ります。"
                },
                "r": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの R 値"
                },
                "g": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの G 値"
                },
                "b": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの B 値"
                },
                "message": {
                    "type": "string",
                    "description": "色名がRGB値に認識されたときの追加の説明。"
                }
            },
            "required": [
                "type",
                "result",
                "r",
                "g",
                "b",
                "message"
            ]
        }
    },
    {
        "name": "rgb_json_not_found",
        "description": "色名から RGB 値の情報を得られなかった場合",
        "parameters": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "description": "led という値が固定値を入力されます"
                },
                "result": {
                    "type": "boolean",
                    "description": "色名が RGB値 で認識されなかったので false が入ります。"
                },
                "message": {
                    "type": "string",
                    "description": "色名が RGB 値に認識されなかったときの説明。あるいは、色名がRGB値に認識されなかったときの説明。色名の例外は「色名が認識されない例外処理です」と説明します。"
                }
            },
            "required": [
                "type",
                "result",
                "message"
            ]
        }
    }
]

いろいろな学びとしては、

  • 「led という値が固定値を入力されます」という指示でちゃんと固定値入れてくれる(揺れない)
  • 「色名が RGB値 で認識されなかったので false が入ります。」といった指示がちゃんと理解されている。
  • 値の型 type も string , boolean , number が機能している。
  • number も小数点の揺れがあり得ると思ったが、RGB 値の場合はちゃんと整数値になっててえらい
  • プロンプトでしっかり指示ができていれば description に指示を移植すればちゃんと動く

といったところです。

プロンプトの調整

image

content と書かれている template ノードの方の修正です。

Function calling によって JSON データでくることが保証されるので、JSON データで受け取り念押し指示が少なくなり、Function calling 内でルールをきっちり移植できたものは削除します。

image

こうなりました!

今回は「{{payload}}」という色名がRGB値で返答ください。
色名がRGB値で認識されたら rgb_json を使います。
色名がRGB値で認識されない場合は rgb_json_not_found を使います。

はい、めちゃくちゃシンプルです。さきほど、Function calling 内で指定した関数の元に、色名がRGB値で認識されたら rgb_json を使い、色名がRGB値で認識されない場合は rgb_json_not_found を使うことを指示して、あとは色名が入ってきたら答える「今回は~~~という色名がRGB値で返答ください。」の部分は一緒です。

2023/07/12 追記 : よりうまく分岐した記述

まだ定まってないんですが、さきほどのものより、うまく例外に言ってくれる記述です。以前はうまくいってた気がするけど、なんでだろうなー。やっぱ箇条書きの指示は強そう。

- 色名がRGB値で認識されたら rgb_json を使います。
- 色名がRGB値で認識されない例外処理は rgb_json_not_found を使います。

今回はこのルールで「{{payload}}」でお願いします

データの受け取り部分を変更

image

http request ノード以降で実際に受け取ったデータから、Function calling の値を抽出するために payload.choices[0].message.function_call.arguments という値で取得するようにしてます。

動かしてみる

image

茶色を質問します。

image

このように、いままでと同じようなデータを回答してくれます。

image

きいろを質問します。

image

message 値の説明が、軽く揺れてるのがいいかんじです。

image

以前は難関だった、色名が認識されないパターンもプロンプトで前述のとおり分岐させていると、しっかり振り分けられて返答してくれます。

余談 : プロンプトで分岐指示なしで Function calling だけ分岐をつくっても選ばれにくい

たとえばプロンプトを「今回は~~~という色名がRGB値で返答ください。」だけで、「色名がRGB値で認識されたら rgb_json を使います。色名がRGB値で認識されない場合は rgb_json_not_found を使います。」といった詳細の分岐指示がない場合で Function calling だけ今回のように分岐を設定しても、auto でうまく選んでくれなかったのが学びです。

最初遭遇したときは、頭を抱えましたが、結果として詳細の分岐指示をプロンプト側ですればバッチリでした。

余談 : Function calling で色名を認識 OK NG を分岐させないと、無理やり全部認識 OK させてくる

試行錯誤当初の頭を抱えたやつ。正直諦めそうになりましたw

移植しはじめは以下のようにしました。

[
    {
        "name": "rgb_json",
        "description": "色名から RGB 値の情報を得る",
        "parameters": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "description": "led という値が固定値を入力されます"
                },
                "result": {
                    "type": "boolean",
                    "description": "色名が RGB値 で認識されたかどうか。true は認識されたとき。false は認識されなかったとき。"
                },
                "r": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの R 値"
                },
                "g": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの G 値"
                },
                "b": {
                    "type": "number",
                    "description": "色名から RGB 値の情報を得たときの B 値"
                },
                "message": {
                    "type": "string",
                    "description": "色名がRGB値に認識されたときの追加の説明、あるいは、色名がRGB値に認識されなかったときの説明。色名の例外は「色名が認識されない例外処理です」と説明します。"
                }
            },
            "required": [
                "type",
                "result",
                "message"
            ]
        }
    }
]

もともとのプロンプトでの指示にある、色名を認識 OK NG の要素を、ひとつの Function calling でやろうとしてました。

image

これだと認識されたときは、うまくいきます。しかし、

image

「ほげほげ」のような色名じゃないものも、無理やり全部認識 OK させてきます。ほげほげ色なんてないです!

やってみての感想

いままで、こういった JSON データお願い系のプロンプトは、

  • 今回受け取りたい指示設計(JSON 以外)に 40
  • JSON データをきっちり受け取るためのスキーマ的な定義指示に 40
  • いろいろ指示したから JSON データと覚えててくれよという念押し指示に 20

となっていた印象で、Web 版のカジュアルに質問するのと同等な「今回受け取りたい指示設計(JSON 以外)に 40」以外にかなりパワーを使っていたんですが、Function calling によって、

  • 今回受け取りたい指示設計(JSON 以外)に 30
  • JSON データをきっちり受け取るためのスキーマ的な定義指示に 20
  • いろいろ指示したから JSON データと覚えててくれよという念押し指示は 0 (ちゃんと JSON でくるので)

となりまして、スキーマ的な定義指示は 40 → 20 で半減し、以前は苦労してたものがサクサクと定義たのしーとなり、念押し指示はちゃんと JSON でくるので驚愕の 0 となり、さらに指示設計自体も、若干スキーマ的な定義指示を意識せざる得なかったところが軽減したので 40 → 30 という感触になったのがとても良かったなーと思ってます。