GPTのFunction callingを使って周辺情報を取得してみた．

# 初めに
2023年6月13日にGPT Function callingがリリースされました．
入力した文字列に応じて関数の呼び出しが可能にとの事ですが，いまいちよく分からなかったので，とりあえず実装してみました．

参考記事のほとんどが天気情報の関数呼び出しをしているため，今回は周辺情報を取得する関数を使ってみたいと思います．

# 実装
今回はPythonを使って開発していきます．
また，周辺情報の取得に関してはGoogleが提供しているPlacesAPIを使います．
PlacesAPIの使い方に関しては，メインではないので説明を省きます．

## 関数の定義
まずはFunction callingで呼び出す関数を作成する必要があります．
```python:getPlace.py
def get_location(latitude, longitude, radius, keyword):

    base_url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
    api_key = '自分のAPIKey'

    parameters = {
      'location': f'{latitude},{longitude}',
      'radius': radius,
      'keyword': keyword,
      'language': 'ja',
      'key': api_key
    }

    response = requests.get(base_url, params=parameters)
    if response.status_code == 200:
        data = response.json()
        # 名前の情報を格納するリスト
        name_list = []
        # 出力結果から名前を取り出してリストに追加
        for result in data['results']:
            name = result['name']
            name_list.append(name)
        return json.dumps(name_list)
    else:
        return None
```
必須のパラメーターは`location` `radius` `key`の3つです．

`keyword`には地名・住所・施設のカテゴリを入力することができます．これにより検索対象を絞ることができます．`language`は返答分の言語を指定しています．

他のパラメータについて知りたい場合は以下のリンクからご覧ください．
[PlaceAPIのパラメータ](https://developers.google.com/maps/documentation/places/web-service/search-nearby?hl=ja "PlaceAPI")

出力結果の`result['name']`には検索した施設の名称が含まれています．
この施設名称をChatGPTの入力値として使います．

:::note warn
注意
ChatGPTに入力する最大文字数は4096文字です．
そのため，入力値が多すぎる場合はエラーとなります．
:::

## Functionプロパティの定義

```python
place_function =  {
    "name": "get_location",
      "description": "経度緯度の情報から周辺の場所を取得",
      "parameters": {
          "type": "object",
          "properties": {
              "latitude": {
                  "type": "string",
                  "description": "経度の情報",
              },
              "longitude": {
                  "type": "string",
                  "description": "緯度の情報",
              },
              "radius": {
                  "type": "string",
                  "description": "半径の情報",
              },
              "keyword": {
                  "type": "string",
                  "description": "キーワード",
              },
          },
          "required": ["latitude","longitude","radius","keyword"],
      },
}
```
`name`　に先ほど作成した関数名を記述してください．
`description`は任意の項目ですが，関数呼び出しの際に使っている可能性があるため，一応記述しておきます．
`parameters`には関数呼び出しに必要となるパラメータを記述します．
`required`には必須のパラメーターを記述します．

Functionプロパティを定義することで，ユーザーの入力内容から自動でパラメーターの値を生成してくれます．

## OpenAI API
```python:main.py
import openai
import json
import requests

# OpenAI APIキーの準
openai.api_key = "自分のAPIKey"

def main(text):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[{"role": "user", "content": text}],
        #Functionプロパティ
        functions=[place_function],
        function_call="auto",
    )
    message = response["choices"][0]["message"]
    if message.get("function_call"):
        function_name = message["function_call"]["name"]
        arguments=json.loads(message["function_call"]["arguments"])
        function_response = get_location(
            latitude=arguments.get("latitude"),
            longitude=arguments.get("longitude"),
            radius=arguments.get("radius"),
            keyword=arguments.get("keyword"),
        )
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=[
                {"role": "user", "content": text},
                message,
                {
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                },
            ],
        )
        return second_response.choices[0]["message"]["content"].strip()
    #Function callingを使わない場合
    return response.choices[0]["message"]["content"].strip()
```
現在関数呼び出しに対応しているモデルは`gpt-4-0613`もしくは`gpt-3.5-turbo-0613`のみです．

処理の流れは以下の通りです．
1. 入力内容から関数呼び出しが必要かどうか判断
1. 必要だと判断した場合，関数のパラメーターを入力内容から生成
1. 生成したパラメーターを使って関数を実行
1. 実行結果を`messages`に含めて回答文を生成

## 実行結果
### 周辺情報を聞いた場合
入力内容：`スカイツリーから100メートル以内でおすすめのレストランを5件教えて`

以下の内容は`message`の内容で，function_callが実行され，パラメータが生成されているのがわかります
```
{
  "role": "assistant",
  "content": null,
  "function_call": {
    "name": "get_location",
    "arguments": "{\n  \"latitude\": \"35.710063\",\n  \"longitude\": \"139.8107\",\n  \"radius\": \"100\",\n  \"keyword\": \"\u30ec\u30b9\u30c8\u30e9\u30f3\",\n}"
  }
}
```
実行結果は以下の通りです．
```
1. Sky Restaurant 634
2. パラッツォ サン グスト
3. おぼんdeごはん 東京スカイツリータウン・ソラマチ店
4. スペイン料理&ワイン パエリア東京スカイツリータウン・ソラマチ
5. 炭焼きLamp

どれも美味しい食事を楽しむことができるレストランです。ぜひ訪れてみてください！
```
### 日常会話を入力した場合
次は入力内容を日常会話にしてみます．
入力内容：`今日は暑いね`
```
{
  "role": "assistant",
  "content": "\u306f\u3044\u3001\u672c\u5f53\u306b\u6691\u3044\u3067\u3059\u306d\u3002\u5916\u306b\u51fa\u308b\u969b\u306f\u71b1\u4e2d\u75c7\u5bfe\u7b56\u3092\u3057\u3063\u304b\u308a\u3068\u884c\u3044\u307e\u3057\u3087\u3046\u3002\u6c34\u5206\u88dc\u7d66\u3084\u65e5\u5098\u306e\u4f7f\u7528\u306a\u3069\u3001\u6ce8\u610f\u304c\u5fc5\u8981\u3067\u3059\u3002"
}
```
function_callが実行されていません．
実行結果は以下の通りです．
```
はい、本当に暑いですね。外に出る際は熱中症対策をしっかりと行いましょう。水分補給や日傘の使用など、注意が必要です。
```

# 終わりに
関数呼び出しを行うことで，chatGPTの精度はかなり向上できると思われます．
しかし，APIで取得した結果もトークンとしてカウントされるため，トークン量の削減などが今後は必要になってくるでしょう．

また，現在の方法では1つの関数しか呼び出しできません．
実際にアプリなどに組み込む場合は，LangChainなどを利用して複数の関数呼び出しに対応させる必要があります．