GPTとRESASのAPIを使って市町村のサイトのURLのデータを作ってみた

お仕事でデータ集めをすることになりました。重要ですが、ちょっと疲れがちなタスクです。

そこで、[GPT](https://openai.com/product/gpt-4)(リンク先にはGPT-4が現れますが、今回使ったのはgpt-3.5-turboです)を使って楽しくできないか試してみました。最近、私は大型言語モデル（LLM）に夢中なので、なんでもLLMを使うと楽しくなってきます。向いていないとわかっていても、ちょっと試してしまいます。

そんな向いていないだろうと思う作業の中で、面白い感じのものができたので、記事にします。

## 開発環境
Google Colab
python: 3.10.11
openai: 0.27.6
requests: 2.27.1

## 課題
都道府県とか市町村のURLを集めることになりました。
ひとまず、ChatGPTに聞いてみました。

## ChatGPTに聞いてみた結果

ちなみにChatGPTではGPT-4に聞きました。

### プロンプト
```prompt
日本の全都道府県のサイトのURLをおしえてください。
データはJSON形式で返してください。
キーは都道府県名、バリューはURLでお願いします。
```

### 返された結果の画像（一部）

![gazo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85433/f45d026f-d0aa-3fe8-f004-a5e6d25e7cd0.png)

ここに書かれている通り、モデルは2021年までのデータで作られているので、こういう作業には向きません。しっかりやりたい場合は、Googleの[カスタム検索API](https://developers.google.com/custom-search/v1/overview?hl=ja)とかを使った方が良いでしょう。

### 結果を確認する

結果を確認してみました。
都道府県数は47でした。正解。
URLにアクセスできるか調べたところ、次の都道府県にアクセスできませんでした。
- 兵庫県
- 広島県
- 徳島県
- 香川県
- 福岡県

これらが、2021年以降URLを変更したのか調べたかったのですが、すぐにわからなかったので止めました。

## APIからGPT-3.5-turboに市町村について聞いてみる

こういう作業はこなせることが分かったので、次に、GPT-3.5-turboのAPIをたたいて市町村に関する作業を行うことにしました

ちなみに、GPT-4もAPIから使えるようにはなっているのですが、結構お金がかかるので（その分すごい答えが返ってくると思います）、テスト的に使うときはGPT-3.5-turboを使っています。

### ひとまず市町村のURLを聞いてみる

まずはプロンプトを聞く関数を作成します。

```python
def create_chat(prompt: str, model: str ='gpt-3.5-turbo') -> str:
    res = openai.ChatCompletion.create(
        model = model,
        messages = [{'role': 'user', 'content': prompt}],
        temperature=0
    )
    content = res.choices[0].message['content']
    return content
```

次にプロンプトを作成します。
```
prompt = """
    京都府の市町村のウェブサイトのURLを返してください。
    値は次の形のようにJSON形式で返してください。
    キーに市町村名、バリューにウェブサイトのURLを持たせてください。
    分からない市町村のURLは None で返してください
"""
```
返された値は次のような感じでした。市町村1. の方は京都じゃなさそうな市町村が含まれています。市町村2. の方は京都っぽい。しかし、最後に大学がなぜか含まれていました。

市町村1. 
![gazo2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85433/5e39ce07-e3a7-c959-6965-909d410d7e28.png)
市町村2.
![gazo3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85433/309f6589-d9c0-7b93-a575-01c03c4d262a.png)

### どうするか考える

ここでどうしたらよいかと思いました。とりあえず考えてみたら、私も京都にどのような市町村があるかわかっていないことに気づきました。正しい答えを返してもらってもわかりません。

こういう時はプロンプトの基本に戻りましょう。[DeepLearning.AIのChapGPT Prompt Engineering for Developers](https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/)では２つのプリンシプルに触れられていました。

Principle 1: Write clear and specific instructions clear not equal short
Principle 2: Give the model time to “think”

という事で、京都府の市町村を明確にして、プロンプトに含めることにしました。[RESAS API](https://opendata.resas-portal.go.jp/)を使うことで、[市町村のデータ](https://opendata.resas-portal.go.jp/docs/api/v1/cities.html)が取れるはずです。

### RESAS APIを使って京都府の市町村のデータを得る

プロンプトに市町村名をすべて入れるために、RESAS APIからデータを取得します。市町村名を取得するためのコードは次の通りです。

```python
@dataclass
class ResasData:

    api_key: str
    base_url: str = 'https://opendata.resas-portal.go.jp'


    @property
    def headers(self):
        return {'X-API-KEY': self.api_key}

    @property
    def prefs_code(self):
        prefs = '/api/v1/prefectures'
        try:
            r = requests.get(self.base_url + prefs, headers=self.headers)
            return r.json()['result']
        except:
            return r.status_code


    def cities_code(self, pref_name):
        cities = '/api/v1/cities'
        pref_df = pd.DataFrame(self.prefs_code)
        pref_num = pref_df[pref_df['prefName'] == pref_name]['prefCode'].values[0]
        params = {
            'prefCode': pref_num
        }
        try:
            r = requests.get(self.base_url + cities, params=params, headers=self.headers)
            return r.json()['result']
        except:
            return r.status_code

    
    def cities_list(self, pref_name):
        data_list = self.cities_code(pref_name)
        cities_list = list()
        for ds in data_list:
            cities_list.append(ds['cityName'])
        return cities_list
```

コードを実行してデータを取得すると、37の市町村名が返されました。

```
['京都市',
 '京都市北区',
 '京都市上京区',
 '京都市左京区',
 '京都市中京区',
 '京都市東山区',
 '京都市下京区',
 '京都市南区',
 '京都市右京区',
 '京都市伏見区',
 '京都市山科区',
 '京都市西京区',
 '福知山市',
 '舞鶴市',
 '綾部市',
 '宇治市',
 '宮津市',
 '亀岡市',
 '城陽市',
 '向日市',
 '長岡京市',
 '八幡市',
 '京田辺市',
 '京丹後市',
 '南丹市',
 '木津川市',
 '大山崎町',
 '久御山町',
 '井手町',
 '宇治田原町',
 '笠置町',
 '和束町',
 '精華町',
 '南山城村',
 '京丹波町',
 '伊根町',
 '与謝野町']
```

### 市町村名を含めたプロンプトを作成

市町村名のデータが取れたので、それをプロンプトに入れて、APIをたたいてみます。

```
prompt = f"""
    京都府の市町村のウェブサイトのURLを返してください。
    値はJSON形式で返してください。キーに市町村名、バリューに市町村のウェブサイトのURLを渡してください。
    京都府の市町村は次のリストの通りです。
    
    ```{cities}```
    分からない市町村のURLは None で返してください
"""
```

変数 cities に先ほど示した市町村名が入ったリストが渡されています。プロンプトを使ってみた結果は次の通りです。

```
{'京都市': 'https://www.city.kyoto.lg.jp/',
 '京都市北区': 'https://www.city.kyoto.lg.jp/ku/kita/',
 '京都市上京区': 'https://www.city.kyoto.lg.jp/ku/kamigyo/',
 '京都市左京区': 'https://www.city.kyoto.lg.jp/ku/sakyo/',
 '京都市中京区': 'https://www.city.kyoto.lg.jp/ku/nakagyo/',
 '京都市東山区': 'https://www.city.kyoto.lg.jp/ku/higashiyama/',
 '京都市下京区': 'https://www.city.kyoto.lg.jp/ku/shimogyo/',
 '京都市南区': 'https://www.city.kyoto.lg.jp/ku/minami/',
 '京都市右京区': 'https://www.city.kyoto.lg.jp/ku/ukyo/',
 '京都市伏見区': 'https://www.city.kyoto.lg.jp/ku/fushimi/',
 '京都市山科区': 'https://www.city.kyoto.lg.jp/ku/yamashina/',
 '京都市西京区': 'https://www.city.kyoto.lg.jp/ku/nishikyo/',
 '福知山市': 'https://www.city.fukuchiyama.lg.jp/',
 '舞鶴市': 'https://www.city.maizuru.kyoto.jp/',
 '綾部市': 'https://www.city.ayabe.lg.jp/',
 '宇治市': 'https://www.city.uji.kyoto.jp/',
 '宮津市': 'https://www.city.miyazu.lg.jp/',
 '亀岡市': 'https://www.city.kameoka.lg.jp/',
 '城陽市': 'https://www.city.joyo.kyoto.jp/',
 '向日市': 'https://www.city.muko.kyoto.jp/',
 '長岡京市': 'https://www.city.nagaokakyo.lg.jp/',
 '八幡市': 'https://www.city.yawata.kyoto.jp/',
 '京田辺市': 'https://www.city.kyotanabe.lg.jp/',
 '京丹後市': 'https://www.city.kyotango.lg.jp/',
 '南丹市': 'https://www.city.nantan.kyoto.jp/',
 '木津川市': 'https://www.city.kizugawa.lg.jp/',
 '大山崎町': 'https://www.town.oyamazaki.lg.jp/',
 '久御山町': 'https://www.town.kumiyama.lg.jp/',
 '井手町': 'https://www.town.ide.kyoto.jp/',
 '宇治田原町': 'https://www.town.ujitawara.kyoto.jp/',
 '笠置町': 'https://www.town.kasagi.lg.jp/',
 '和束町': 'https://www.town.wazuka.lg.jp/',
 '精華町': 'https://www.town.seika.kyoto.jp/',
 '南山城村': 'https://www.vill.minamiyamashiro.lg.jp/',
 '京丹波町': 'https://www.town.kyotamba.kyoto.jp/',
 '伊根町': 'https://www.town.ine.kyoto.jp/',
 '与謝野町': 'https://www.town.yosano.lg.jp/'}
```

間違っていたのは次の5市町村でした。
- 宮津市
- 亀岡市
- 大山崎町
- 笠置町
- 伊根町

## 追記

GPTのAPIから返されるデータは文字列のJSONです。Pythonで辞書として扱うのに次のように処理しました。

```python
import ast

content = create_chat(prompt)
content = ast.literal_eval(content)

```

## まとめ
というような感じで、GPTとRESASのAPIを使って、うまいことデータを作ってみることを試みました。足りない知識をAPIを使って足したら、なんか面白そうなことができそうだなと実感しました。あと、GPTから別のAPIに流してみたいなこともできると今回のようなタスクは簡単にこなせそうです。

そのあたりlangchainがうまくこなしてくれそうなので、そろそろ使い始めたいなと思っています。もし同じように思っておられる方がおられるなら、5月19日に[私が主催するはんなりプログラミングで「langchainとプロンプトをいじる会」を開催する](https://hannari-python.connpass.com/event/281205/)ので、参加してみてください。