Azure OpenAI にもFunction Callingの機能がきたんですって!

2023.07.21

Azure OpenAIにも本家OpenAIで人気だったFunction Callingの機能が使えるようになりました。

こちらのブログでも紹介されています。 Function calling is now available in Azure OpenAI Service

API Referenceは日本語の更新はまだされておらず、英語で表示すると、Chat completionsの項目に2023-07-01 preview Swagger specの記載もあります。(2023年7月21日 日本時間15:00現在) Azure OpenAI Service REST API reference

2023-07-01-preview Swagger spec

追記) このFunction callingは、0613バージョンのgpt-35-turboとgpt-4で利用可能です。 0301バージョンでは利用できないらしいので、ご注意ください。

まずはお手本通りにやってみる。

まずはブログに載っているリクエストの中でも見た目短いものを試してみます。

特に、何の設定もしていません。←これ重要

messages= [
    { "role": "user", "content": "Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?"}
]

functions= [  
    {
        "name": "calculator",
        "description": "A simple calculator",
        "parameters": {
            "type": "object",
            "properties": {
                "num1": {"type": "number"},
                "num2": {"type": "number"},
                "operator": {"type": "string", "enum": ["+", "-", "*", "/", "**", "sqrt"]},
            },
            "required": ["num1", "num2", "operator"],
        },
    }
]

このコードを見てみると、何か計算をやりそうな感じがしますね。

というわけで、さっそくやってみます。

$ RESOURCE_NAME=xxxxxxxxxx
$ DEPLOYMENT_NAME=xxxxxxxx
$ API_KEY=xxxxxxxxxxxxxxxx

curl -XPOST "https://${RESOURCE_NAME}.openai.azure.com/openai/deployments/${DEPLOYMENT_NAME}/chat/completions?api-version=2023-07-01-preview" \
-H "Content-Type: application/json" \
-H "api-key: ${API_KEY}" \
-d @functions.json | jq .

functions.jsonの内容はこちら。

{
    "messages": [
        {
            "role": "user",
            "content": "Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?"
        }
    ],
    "functions": [
        {
            "name": "calculator",
            "description": "A simple calculator",
            "parameters": {
                "type": "object",
                "properties": {
                    "num1": {
                        "type": "number"
                    },
                    "num2": {
                        "type": "number"
                    },
                    "operator": {
                        "type": "string",
                        "enum": [
                            "+",
                            "-",
                            "*",
                            "/",
                            "**",
                            "sqrt"
                        ]
                    }
                },
                "required": [
                    "num1",
                    "num2",
                    "operator"
                ]
            }
        }
    ]
}

このように メッセージとして、

"Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?" 訳)先月、Fabrikamは売上を$73,846を達成しました。これに基づいて、年間の売上はいくらになりますか? (↑ 余談、github copilotが出してくれました。)

を送るとどのように返ってくるのでしょ。

{
    "id": "chatcmpl-7eei24MMbdVuwcOlfA5ApnXbuGoKW",
    "object": "chat.completion",
    "created": 1689923962,
    "model": "gpt-35-turbo",
    "prompt_annotations": [
      {
        "prompt_index": 0,
        "content_filter_results": {
          "hate": {
            "filtered": false,
            "severity": "safe"
          },
          "self_harm": {
            "filtered": false,
            "severity": "safe"
          },
          "sexual": {
            "filtered": false,
            "severity": "safe"
          },
          "violence": {
            "filtered": false,
            "severity": "safe"
          }
        }
      }
    ],
    "choices": [
      {
        "index": 0,
        "finish_reason": "function_call",
        "message": {
          "role": "assistant",
          "function_call": {
            "name": "calculator",
            "arguments": "{\n  \"num1\": 73846,\n  \"num2\": 12,\n  \"operator\": \"*\"\n}"
          }
        },
        "content_filter_results": {}
      }
    ],
    "usage": {
      "completion_tokens": 31,
      "prompt_tokens": 89,
      "total_tokens": 120
    }
  }

このレスポンスの中で大事な部分は choicesのところです。

choicesの中にはfunction_callが追加されています。

さらにその中に、nameとして、送信時に指定したcalculatorが入っています。

argumentsには、num1,num2,operatorの値がそれぞれ入っています。

この情報を抽出してくれるということは、 どの関数にどんな引数を渡せば、欲しい答えが返ってくるのか を自動で判断してくれるということですね。

"choices": [
      {
        "index": 0,
        "finish_reason": "function_call",
        "message": {
          "role": "assistant",
          "function_call": {
            "name": "calculator",
            "arguments": "{\n  \"num1\": 73846,\n  \"num2\": 12,\n  \"operator\": \"*\"\n}"
          }
        },
        "content_filter_results": {}
      }
    ],

全然Function関係なさそうなことを聞いてみる。

先ほどのRequestのmessagesの部分だけを変更してリクエストを送ってみます。 今回はめんどくさいので、もう日本語でメッセージを送ります。

"messages": [
{
    "role": "user",
    "content": "酢豚ってどうやって作るの?"
}
],
〜略〜
"choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "酢豚の作り方は以下の通りです。\n\n材料:\n- 豚肉(約300g)、大きめのひと口大に切る\n- 玉ねぎ(中サイズ)、1個、大きめの乱切り\n- にんじん(中サイズ)、1本、半月切り\n- 緑のピーマン、2個、種を取り除いて乱切り\n- パイナップルの缶詰(輪切り)、約200g、ジュースを取っておく\n- 長ネギ、1本、小口切り\n- 豆苗、適量(お好みで)\n- 揚げ油\n\n下味のたれ:\n- 醤油、大さじ3\n- 酢、大さじ3\n- 砂糖、大さじ3\n- 鶏ガラスープの素、小さじ1\n- おろしにんにく、小さじ1\n- オイスターソース、大さじ1\n\n準備:\n1. 豚肉に下味のたれを揉み込み、20分程度おいておきます。\n\n作り方:\n1. 中火で熱したフライパンに揚げ油を入れ、豚肉をカリッと揚げます。\n2. 別のフライパンにごま油を熱し、玉ねぎ、にんじん、ピーマンを炒めます。\n3. フライパンにパイナップルの缶詰のジュースを加え、煮立てます。\n4. おろしにんにくとオイスターソースを加え、さらに煮立てます。\n5. カリッと揚げた豚肉を加え、全体を混ぜ合わせます。\n6. 流水ですすいだ豆苗を加え、さっと炒め合わせます。\n7. 最後に長ネギを加えて炒め、完成です。\n\nお好みでご飯や麺類と一緒に召し上がれます。お楽しみください!"
      },
    }
    〜略〜
]

ほう。

酢豚の作り方が返ってきました。 パイナップル入りの酢豚ですね。

複数のFunctionがあったらどうなるんだろう。

次に、複数のFunctionがあったらどうなるか試します。 ブログに載っていた、簡単な計算をするものと、フルーツを抽出するものです。

"functions": [
        {  
            "name": "extract_fruit",  
            "type": "function",  
            "description": "Extract fruit names from text.",  
            "parameters": {  
                "type": "object",  
                "properties": {  
                    "fruits": {  
                        "type": "array",  
                        "items": {  
                            "type": "object",  
                            "properties": {  
                                "fruit": {"type": "string",  "description": "The name of the fruit."  },  
                                "color": {"type": "string",  "description": "The color of the fruit." },  
                                "flavor": {"type": "string",  "description": "The flavor of the fruit."}  
                            },  
                            "required": ["fruit", "color", "flavor"]  
                        }  
                    }  
                },  
                "required": ["fruits"]  
            }  
        },
        {
            "name": "calculator",
            "description": "A simple calculator",
            "parameters": {
                "type": "object",
                "properties": {
                    "num1": {
                        "type": "number"
                    },
                    "num2": {
                        "type": "number"
                    },
                    "operator": {
                        "type": "string",
                        "enum": [
                            "+",
                            "-",
                            "*",
                            "/",
                            "**",
                            "sqrt"
                        ]
                    }
                },
                "required": [
                    "num1",
                    "num2",
                    "operator"
                ]
            }
        }
    ]

リクエスト例1 フルーツの抽出

ブログにあった、フルーツのデータを抽出するものを試してみます。

Functionはフルーツの抽出と、簡単な計算の二つがリクエストに含まれています。

[
        {
            "role": "system",
            "content": "Assistant is a large language model designed to extract structured data from text."
        },
        {
            "role": "user",
            "content": "There are many fruits that were found on the recently discovered planet Goocrux. There are neoskizzles that grow there, which are purple and taste like candy. There are also loheckles, which are a grayish blue fruit and are very tart, a little bit like a lemon. Pounits are a bright green color and are more savory than sweet. There are also plenty of loopnovas which are a neon pink flavor and taste like cotton candy. Finally, there are fruits called glowls, which have a very sour and bitter taste which is acidic and caustic, and a pale orange tinge to them."
        }
    ]
"function_call": {
    "name": "extract_fruit",
    "arguments": "{\n  \"fruits\": [\n    {\n      \"fruit\": \"neoskizzles\",\n      \"color\": \"purple\",\n      \"flavor\": \"candy\"\n    },\n    {\n      \"fruit\": \"loheckles\",\n      \"color\": \"grayish blue\",\n      \"flavor\": \"tart\"\n    },\n    {\n      \"fruit\": \"pounits\",\n      \"color\": \"bright green\",\n      \"flavor\": \"savory\"\n    },\n    {\n      \"fruit\": \"loopnovas\",\n      \"color\": \"neon pink\",\n      \"flavor\": \"cotton candy\"\n    },\n    {\n      \"fruit\": \"glowls\",\n      \"color\": \"pale orange\",\n      \"flavor\": \"sour and bitter\"\n    }\n  ]\n}"
  }

おお。ちゃんと、フルーツの抽出ができていますね。

リクエスト例2 簡単な計算(systemのcontentはあえてそのままで)

あえて、"role": "system"を残して、簡単な計算の時に使ったメッセージを送ってみます。

[
        {
            "role": "system",
            "content": "Assistant is a large language model designed to extract structured data from text."
        },
        {
            "role": "user",
            "content": "Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?"
        }
    ]
"message": {
    "role": "assistant",
    "content": "To calculate the annual run rate based on last month's sales, we can multiply the sales from last month by 12. This assumes that the sales from last month are representative of the sales for the entire year. \n\nUsing this method, the annual run rate would be calculated as follows:\n\nAnnual Run Rate = Last Month's Sales * 12\n\nAnnual Run Rate = $73,846 * 12\n\nThe annual run rate based on last month's sales would be $885,552."
  },

訳) 先月の売上から年間の売上を計算するには、先月の売上に12を掛けることができます。これは、先月の売上が1年間の売上を代表していると仮定しています。\n\nこの方法を使用すると、年間の売上は次のように計算されます。\n\n年間売上 = 先月の売上 * 12\n\n年間売上 = $73,846 * 12\n\n先月の売上に基づく年間の売上は$885,552になります。

普通に、Function用のレスポンスではなく、文章での返答が返ってきました。 しかも売上の数値が若干計算ミスってるのはご愛嬌。

リクエスト例3 簡単な計算(systemのcontentは消した)

二つのFunctionが含まれていますが、メッセージには最初に試した簡単な計算が返ってきた例文を送ってみます。 希望としては、calcurator用のレスポンスが欲しいところです。

"messages": [
        {
            "role": "user",
            "content": "Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?"
        }
    ],

気になる結果は、、、、

"message": {
    "role": "assistant",
    "content": "To calculate the annual run rate, we need to multiply the monthly sales by 12 (the number of months in a year).\n\nAnnual run rate = Monthly sales * 12\n\nIn this case, the monthly sales is $73,846. Let me calculate the annual run rate for you.",
    "function_call": {
      "name": "calculator",
      "arguments": "{\n  \"num1\": 73846,\n  \"num2\": 12,\n  \"operator\": \"*\"\n}"
    }
  },

予想外の結果ですね。 まさかの、文章でのレスポンスと、function_callの両方が返ってきました。

さいごに

少し面白い結果が返ってきたところもありましたが、 Functionとして、その後にどのようなFunctionが控えているのかを伝えることによって、 Functionの呼び出しを自動で判断してくれるというのは、非常に便利ですね。

そして、大事なこと。 Function callingの機能は、呼び出し元に対して、次にcallする関数(API)と、そのパラメータを教えてくれるというものです。 GPTが直接APIをコールするものではありません。

また、私個人としては、GPTをNERのように使っていたところがあるので、このように構造化してくれるというのは非常に便利な機能だなと思いました。

おまけ

Function callingの機能追加によって、 JavaのSDKも1.0.0-beta.2から1.0.0-beta.3に更新されているのを確認しました。 Microsoft Azure SDK For OpenAI Management » 1.0.0-beta.3

ここで、破壊的な変更が加えられているので、 もし、versionの指定を

[1.0.0-beta.)

のような指定の仕方をしていると既存のchat completionの呼び出し部分でコンパイルエラーが発生するので要注意です。

(betaに対して、こんな指定をしないとは思いますが、、、)