Azure OpenAIでもFunction Callingが使えるようになりました

https://techcommunity.microsoft.com/t5/azure-ai-services-blog/function-calling-is-now-available-in-azure-openai-service/ba-p/3879241

OpenAI APIで利用することが出来る **Function Calling** が Azure OpenAI Service（以後、AOAI）上でも利用できるようになりました。

プロンプトエンジニアリングで意図する答えを引き出すように、OpenAIに向けたFunction（関数）を定義することでAOAIが期待する挙動をさせることが出来ます。

##### Function（関数）とは
Function（関数）は、プログラム内の特定のタスクを実行する小さな部品です。関数は、同じコードを何度も繰り返して書く代わりに、そのコードを1つの場所にまとめ、再利用できるようにするために使用されます。

レストランのメニューにある料理を注文するときの「メニュー番号」のようなものです。メニュー番号を使うことで、料理の名前やレシピの詳細を覚える必要なく、簡単に注文できます。

プログラムにおいて、関数はプログラマーが作成した「特別な料理」で、何度も繰り返し使われるタスクや計算をまとめています。プログラムの他の部分は、お客さんが注文する料理を指定するように、関数を呼び出すことができます。


## function calling の使い方

function calling を使うには、既存の AOAI APIに対して大きく2つの追加が必要になります。

1. **openai.ChatCompletion に function calling について追記する**
※ GPT-3.5-turbo および GPT-4 で利用可能ですが、バージョンは最新の 0613 を指定する必要があります。
※ AOAIの場合、コンソールから利用するモデル（例：gpt-35-turbo）をデプロイしたデプロイ名（ここでは gpt-35-turbo-0613）をengineで指定する必要があります。
```
response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613",
    messages=messages
)
```
に対して以下のように **function calling** を追記します。
```
response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613",
    messages=messages,
    functions=functions, ←追記
    function_call="auto", ←追記 
)
```

２. **4行目で指定している functions の中身（関数）を用意する**

関数の必須項目は **Name** と **parameters** です。
※下記の例の3行目と5行目

それぞれの項目は以下のように設定する必要があります。（参考：[OpenAI公式のリファレンス](https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions)）

項目名|説明
-|-
name|呼び出される関数名。a-z、A-Z、0-9、またはアンダースコアとダッシュを含むもので、最大長は64。
parameters|JSON Schemaオブジェクトとして記述された、関数が受け取るパラメータ。

ルールに基づいて以下のような記述を行います。

```python
functions= [  
    {
        "name": "search_hotels",
        "description": "Retrieves hotels from the search index based on the parameters provided",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The location of the hotel (i.e. Seattle, WA)"
                },
                "max_price": {
                    "type": "number",
                    "description": "The maximum price for the hotel"
                },
                "features": {
                    "type": "string",
                    "description": "A comma separated list of features (i.e. beachfront, free wifi, etc.)"
                }
            },
            "required": ["location"],
        },
    }
]
```

これをプログラムに記述することでユーザーが入力するメッセージに対して、上記関数を適用した結果が出力されます。
関数では、ホテルがある地名・希望するホテルの最高価格・希望する特徴をカンマ区切りでと指定しています。

> Find beachfront hotels in San Diego for less than $300 a month with free breakfast.(サンディエゴのビーチフロントホテルを無料朝食付きで月300ドル以下で検索して)

と入力すると以下のように **search_hotels** で定義した通り地名・最高価格・特徴を抽出してくれます。
ChatGPTやOpenAIだと、思ったように答えてくれないという課題に対して、適切な答えを出すことが出来ました。

```json
{
  "role": "assistant",
  "function_call": {
    "name": "search_hotels",
    "arguments": "{\n  \"location\": \"San Diego\",\n  \"max_price\": 300, \n  \"features\": \"beachfront, free breakfast\"\n}"
  }
}
```



ここからは記事の最初に紹介した公式ブログにある残り２つのユースケースを説明していきます。

## ユースケース: APIやツールとの統合

関数呼び出しを使用することで、モデルの能力を拡張する新しいスキルを追加することができます。たとえば、モデルに計算を行ったり、元々得意ではない他のタスクを実行するための関数へのアクセスを与えたい場合があります。これらの関数を定義することで、コードで定義したさまざまな外部APIや関数と信頼性のあるやり取りができます。

また、関数呼び出しはデータベースへのデータの書き込み、注文の作成、通知の送信などのアクションにも使用できます。ただし、このような機能を実装する前に、アクションを起こした際に何か問題が発生した場合の影響を理解することが重要です。一般的に、モデルが有害な副作用を引き起こす可能性のあるアクションを実行する前に、ユーザーの確認フローを常に組み込むことをお勧めします。

```python
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"],
        },
    }
]  

response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613",
    messages=messages,
    functions=functions,
    function_call="auto", 
)

print(response['choices'][0]['message'])
```

ここではChatGPTが苦手と言われる計算を、どう解釈されているかを確認することで実用性を高めています。この関数は、入力する文章に対して四則演算でどの様に表せるかを出力するように指定しています。

> Last month Fabrikam made $73,846 in sales. Based on that, what would the annual run rate be?(先月のファブリカムの売上は73,846ドルでした。それに基づくと、年間売上高はいくらになりますか？)

という入力に対して、直接年間売上高を取得するのではなく、どの値とどの値を組み合わせて四則演算しているのかを確認することで、結果に対するチェックが出来ます。

```json
{
  "role": "assistant",
  "function_call": {
    "name": "calculator",
    "arguments": "{\n  \"num1\": 73846,\n  \"num2\": 12,\n  \"operator\": \"*\"\n}"
  }
}
```

> 73846(先月の売上) [ × ] 12（年間=12ヶ月）

のように、入力した内容に対して、どの様に計算しているかが出力されます。

## ユースケース: 構造化された出力の作成

これらのモデルがJSONを出力することはすでに可能ですが、しばしば追加の解析が必要であり、モデルが応答を「こちらがJSONです：」のように始めたり、他のテキストを応答に追加したりする場合にはやや複雑になることがあります。

しかし、関数呼び出しを使うことで、モデルがテキストから構造化されたJSONの出力をより信頼性のある方法で生成できるようになります。これは、テキストからエンティティを抽出する必要がある場合や、整理されて解釈しやすい出力が必要なタスクに対処する際に役立ちます。

```python
messages= [
    {"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."}
]

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"]  
        }  
    }  
]  

response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613",
    messages=messages,
    functions=functions,
    function_call={"name": "extract_fruit"}, # force the model to call the function
)

print(response['choices'][0]['message'])
```

このケースでは今までといくつかの違いがあります。
- function_call="auto" ではなく、function_call={"name": "extract_fruit"} として呼び出す関数を指定している
- functions の name と同じ階層に "type": "function" が追加されている
- properties で定義している fruits の中に更に "type": "array" として配列の処理が追加されている

この定義により、今までは文章に対して処理をしていた（今回のケースだと「テキストから果物の名前を抽出する」）のに対して、抽出したフルーツに対して更に名前・色・香りを抽出するという処理を行っています。

ちょっと変わった例ですが、以下の様な文章から果物を抽出し、更に色や香りを抽出するということを行っています。

> 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.(最近発見された惑星グークラックスにはたくさんの果物がある。ネオスキズルは紫色でキャンディのような味がする。ロヘックルは灰色がかった青い果物で、レモンのような酸味がある。プーニッツは鮮やかな緑色で、甘いというより香ばしい。ネオンピンクで綿菓子のような味のループノバもたくさんある。最後に、酸味と苛性のある非常に酸っぱい苦味と、淡いオレンジ色を帯びたグロールと呼ばれる果物がある。)

```json
{
  "fruits": [
    {"fruit": "neoskizzles", "color": "purple", "flavor": "candy"},
    {"fruit": "loheckles", "color": "grayish blue", "flavor": "tart"},
    {"fruit": "pounits", "color": "bright green", "flavor": "savory"},
    {"fruit": "loopnovas", "color": "neon pink", "flavor": "cotton candy"},
    {"fruit": "glowls", "color": "pale orange", "flavor": "sour and bitter"}
  ]
}
```

無事、惑星グークラックスの未知の名称の果物の情報を抽出することが出来ました。

## まとめ

いかがでしたでしょうか。Function Calling を適切に使うことで、ChatGPTやOpenAIの課題であった **処理がブラックボックス** や **思った対応をしてくれない** といった課題に対して対応することが出来ます。 

プロンプトだと思った様な答えが出せない、都度答えが違って困っていると言ったケースでも Function Calling なら解決できるケースは数多くあります。ChatGPTの有用性はわかっているけど、業務で活かすことが出来ないといった課題にも Function Calling を利用することで幅広く活用することができます。