バッチ処理革命。コンバータもgpt-3.5-turbo (ChatGPT API)でほぼ作れてしまうという話

# 結論

gpt-3.5-turbo が下記の性能をもつため、バッチシステムのコンバータでの商用利用が十分可能そうです

1. コストが比較的安い (1000 tokenで0.002USD)
1. messages の工夫とtemperature=0の指定で出力形式が安定する（システムに組み込むために重要）
1. messages の工夫で性能を改善できる（コードが全く書けなくても！コードが書ける人でも普通にコード書くよりも全然早く作れそう）

# 検証内容

今回、対象にしたコンバータは[経産省の住所変換コンポーネント](https://info.gbiz.go.jp/tools/imi_tools/index.html)です。ここのreadmeに記載のアウトプットのフォーマットは以下です

```
{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "霞が関2-1-10",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2",
    "番地": "1",
    "号": "10"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}
```
これはinputもjson形式で書く必要がありますが、文字列を受け付けてそれっぽく返すか検証します。

# 検証結果

messages に指定するための文字列です。＊ここのパターンの工夫次第で性能はまだまだあがりそうです
```
role_system ='日本の住所をパースする機能。IMI語彙記法の規約に従う'

question_1 = "霞が関2-1-10"
answer_1 = '''{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "霞が関2-1-10",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2",
    "番地": "1",
    "号": "10"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}'''
question_2 = "霞が関　二の一の十"
answer_2 = '''{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "霞が関2-1-10",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2",
    "番地": "1",
    "号": "10"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}'''

question_3 = "東京都千代田区霞が関２丁目１番地１０号"
answer_3 = '''{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "霞が関2-1-10",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2",
    "番地": "1",
    "号": "10"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}'''


```

問い合わせてみます

```
ask = "東京都千代田区千代田1番1号"

res = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-0301",
  messages=[
        {"role": "system", "content": role_system},
        {"role": "user", "content": question_1},
        {"role": "assistant", "content": answer_1},
        {"role": "user", "content": question_2},
        {"role": "assistant", "content": answer_2},
        {"role": "user", "content": question_3},
        {"role": "assistant", "content": answer_3},
        {"role": "user", "content": ask}
    ]
)
print(res["choices"][0]["message"]["content"])
```
その出力
```
{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "千代田1-1",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "千代田",
    "丁目": "1",
    "番地": "1",
    "号": ""
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.693840",
    "経度": "139.753136"
  }
}
```
地理座標もだいたい正しく（番地単位では不正確）、市区町村コードも正しい結果が返ってきました。

以降、askの変数だけのせます

Qiita社の住所
入力
```
ask = "愛知県名古屋市中村区名駅3-28-12"
```
結果
```
{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "愛知県名古屋市中村区名駅3-28-12",
    "都道府県": "愛知県",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C23000",
    "市区町村": "名古屋市中村区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C23208",
    "町名": "名駅",
    "丁目": "3",
    "番地": "28",
    "号": "12"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.170764",
    "経度": "136.881032"
  }
}
```

すげぇ！！！

https://qiita.com/miya0001/items/598070abcdf0799daebc を参考に鼻血がでそうな入力で試します

入力
```
ask = "京都府京都市東山区大和大路通正面下る大和大路2"
```

出力
```
{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "京都府京都市東山区大和大路通正面下る大和大路2",
    "都道府県": "京都府",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C26000",
    "市区町村": "京都市東山区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C26105",
    "町名": "",
    "丁目": "",
    "番地": "大和大路通正面下る",
    "号": "2"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "34.994032",
    "経度": "135.771858"
  }
}
```

入力
```
ask = "七尾市藤橋町亥45番地1"
```
出力
```
{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "七尾市藤橋町亥45-1",
    "都道府県": "石川県",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C17000",
    "市区町村": "七尾市",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C17206",
    "町名": "藤橋町",
    "丁目": "",
    "番地": "亥45",
    "号": "1"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "37.042778",
    "経度": "136.964167"
  }
}
```
おしい！

最後にプロンプトをチューニングしたらいけるか検証

```question_4 = "京都府京都市東山区大和大路通正面下る大和大路2"
answer_4 = '''{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "京都府京都市東山区大和大路通正面下る大和大路2",
    "都道府県": "京都府",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C26000",
    "市区町村": "京都市東山区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C26105",
    "町名": "大和大路",
    "丁目": "2",
    "番地": "",
    "号": ""
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "34.990157",
    "経度": "135.771012"
  }
}'''

question_5 = "七尾市藤橋町亥45番地1"
answer_5 = '''{
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "石川県七尾市藤橋町亥45-1",
    "都道府県": "石川県",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C17000",
    "市区町村": "七尾市",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C17205",
    "町名": "藤橋町",
    "丁目": "1",
    "番地": "45",
    "号": "1"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "37.037160",
    "経度": "136.953824"
  }
}'''

ask = "京都府京都市東山区大和大路通正面下る大和大路1"

res = openai.ChatCompletion.create(
  model="gpt-3.5-turbo-0301",
  messages=[
        {"role": "system", "content": role_system},
        {"role": "user", "content": question_1},
        {"role": "assistant", "content": answer_1},
        {"role": "user", "content": question_2},
        {"role": "assistant", "content": answer_2},
        {"role": "user", "content": question_3},
        {"role": "assistant", "content": answer_3},
        {"role": "user", "content": question_4},
        {"role": "assistant", "content": answer_4},
        {"role": "user", "content": question_5},
        {"role": "assistant", "content": answer_5},
        {"role": "user", "content": ask}
    ],
  temperature=0
)

print(res["choices"][0]["message"]["content"])
```

出力
```
申し訳ありませんが、京都市東山区には「大和大路通正面下る大和大路1」という住所は存在しません。もう一度住所を確認していただけますか？
```

流石に難しいか

気になるお値段ですが、Qiita社の住所のケースで1027tokenだったので、約0.002USDです。本当に大量に使う場合はChatGPT APIを叩く前にキャッシュするとか工夫できそうですが、0から開発するコストを考えるとかなり安いはずです。

今回はすでに公開されている開発済みのコンバータの再現なので、そのままの需要はなさそうですが、新規で新しいコンバータを開発するさいは、こんな感じで入出力のサンプルだけ用意しておけば、かなりの性能が出ますし、自前で開発してもどうせエッジケースの処理は苦しむので、まずは試してみる！が良さそうです

これはバッチ処理でも革命が起こりそうです