ChatGPT で抽出 & フォーマットのタスクをするときのテクニック

コンサート検索の Web アプリを個人開発しています。
アプリ紹介記事は[こちら](https://qiita.com/ogatango/items/851e11bda3cf87210c53)

https://qiita.com/ogatango/items/851e11bda3cf87210c53

アプリは[こちら](https://concertmap.page/)

https://concertmap.page/

# ChatGPT で抽出 & フォーマットのタスクをするときのテクニック

前回[スクレイピングした文字列を ChatGPT で簡単にフォーマットする](https://qiita.com/ogatango/items/df7b42e05a5852c665d0)という記事を書きました。
特定の文字列の抽出に正規表現等を使う代わりに ChatGPT を使うととても簡単に実装できてしまうことに感動しました。

実際に使えそうな感触があるため、本格的にアプリに ChatGPT を使う前にいくつかの検証をしました。今回の記事ではその際に分かったテクニックを共有します。

## 前提

### 使用するデータ

今回は次のテキストを使います。横長で恐縮です。

```
オーケストラ コンサート 公演 子どものための管弦楽教室 出演＆スタッフ 指揮：藤岡幸夫（関西フィルハーモニー管弦楽団首席指揮者） 管弦楽：関西フィルハーモニー管弦楽団 公演概要 日時 2023年3月21日（火・祝） 14:00開演  滋賀県立芸術劇場びわ湖ホール 大ホール 大ホールを3Dバーチャルツアーで見る 詳細 2008年度より実施してきた好評のシリーズ。子どもたちが楽しみながら学べるだけでなく、大人も一緒にオーケストラについての知識や理解を深められる公演です。 チケット情報 料金 大人：2,000円 子ども（4歳～中学生）：1,000円 ※全席指定・税込 大ホール 座席表を見る（形式：190KB） 発売日 友の会 2022年12月23日（金）10:00～ 一般 2022年12月25日（日）10:00～ その他  びわ湖ホールチケットセンター
```

## 新しく改善した指示

色々検証する中で、１つのコンサートのテキストにつき次の指示を１度だけ出すことにしました。

```
次の値を抽出し、JSON形式で返せ。当てはまるものがなければ空にせよ。
演奏者名と役割の一覧。{player: [{player: value, roll: value}]}の形式で。当てはまるものがないときは空のリスト。
作曲者と曲名の組み合わせ。{program: [{composer: value, music: value}]}の形式で。当てはまるものがないときは空のリスト。
コンサートのタイトル。{title: value}の形式で。
コンサートの開催日。{concert_date: value}の形式で。value のフォーマットは「0000-00-00」。
コンサートの開演時間。{open_time: value}の形式で。value のフォーマットは「00:00」。
コンサートのジャンル。次のジャンルから選択し、当てはまるものがなければ空文字列。classic,game,jazz,anime,movie,pops,ballet,musical。{genre: value}の形式で。
コンサートの演奏形態。次から選択肢、当てはまるものがなければ空文字列。orchestra,chorus,solo,chambermusic,brassband,opera,band。{format: value}の形式で。
チケット料金。JSON 以外の情報は削除。{price:{key: value}}の形式で。料金の value は単位をつけない数値型で。
```

この指示のもと先程のテキストを渡すと次の結果がでました。

```json
{
  "player": [
    {
      "player": "関西フィルハーモニー管弦楽団",
      "role": "オーケストラ"
    },
    {
      "player": "藤岡幸夫",
      "role": "指揮"
    }
  ],
  "program": [],
  "title": "オーケストラ コンサート 子どものための管弦楽教室",
  "concert_date": "2023-03-21",
  "open_time": "14:00",
  "genre": "classic",
  "format": "orchestra",
  "price": {
    "大人": 2000,
    "子ども": 1000
  }
}
```

なんやかんやで JSON 形式が便利なのでこのようなフォーマットにしました。
気をつけた点を解説していきます。

### 指示をなるべくまとめる

前回の記事では取得する値ごとに指示を出していましたが、その場合トークンの使用量が大きくなるという問題が発生しました。
トークンの利用料は [openai の公式サイト](https://openai.com/pricing)によると 1000 トークンあたり 0.002 ドルです。
今回の場合は

- completion_tokens（ChatGPT が返したテキストのトークン数）：190
- prompt_tokens（ChatGPT に与えたトークン数）：832

で合計約 1000 トークンでした。
これを平均的なトークン数とすると、１つのコンサートあたり 0.3 円弱かかります。
1 ヶ月で 3000 コンサートくらいに使用すると仮定すると、1000 円くらいかかる計算になります。これくらいなら許容範囲です。

指示を分割した場合、同じテキストを繰り返し与えることになるため、 prompt_tokens に重複して同じテキストが計上されることになります。
指示を 8 つに分割し、１つの指示で１つの値だけ取るようにすると、概ね 600 トークンくらい使っていたため、600×8 = 4800 で約５倍の料金になってしまいました。約5000円・・・個人の負担する金額としてはちょっと嫌ですね。
なるべく１つの指示でとれるだけ値をとったほうが得になります。

注意点として、１回の指示で使用できる[トークンの上限は今のところ 4096](https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens) となっています。
ものすごく長いテキストを与えてしまうと容易に超えてしまうため、ある程度テキストを絞って与える必要があります。

### JSON フォーマットを例示して指定する

前回の記事で JSON フォーマットの出力方法には苦労していましたが、次のように例示するのが良さそうです。

```
演奏者名と役割の一覧。{player: [{player: value, roll: value}]}の形式で。当てはまるものがないときは空のリスト。
```

このフォーマットを言葉で説明するのは難しいのですが、このように例示するとうまく解釈してくれます。
実際に返ってきた値はこちらです。

```json
{
  "player": [
    {
      "player": "関西フィルハーモニー管弦楽団",
      "role": "オーケストラ"
    },
    {
      "player": "藤岡幸夫",
      "role": "指揮"
    }
  ],
```

うまく解釈してくれていますね。
人間に対して説明するときも実際の例を見せたほうが早いのと同じように、ChatGPT に対しても例示したほうが正確に解釈してくれそうです。

### 例外処理を指示しておく

ChatGPT は答えが見つからない指示を受けたときに平然と誤った情報を返してくる点が懸念です。抽出タスクなら比較的安心できそうですが、答えに当てはまらない場合にどうなるかよくわかりません。念の為に例外処理やバリデーションがあったほうが良さそうですが、真剣に実装すると手間を省く旨味が消えてしまいます。

そこで ChatGPT に対して値が当てはまらない場合に返す値を指示しました。
program を取得する指示の最後に `当てはまるものがないときは空のリスト。` と追加しています。
この指示があることで安定して `  "program": [],` という値が返ってきます。
なかった場合は

```json
  "program": [
    {
      "composer": null,
      "music": null
    }
  ],
```

となったり

```json
 "program": [
    {
      "composer": "",
      "music": ""
    }
  ],
```

となったり、返ってくる値が揺れてしまう点が困りました。
プログラムを作るときは例外処理を書きますが、ChatGPT に対して指示を出すときも例外時にどのような処理をすべきか明示するとよさそうです。

### 正確な指示を出す

もともと演奏者をその楽器を取得するために次の指示を出していました。

```
演奏者名と楽器名の一覧。{player: [{player: value, instrument: value}]}の形式で。当てはまるものがないときは空のリスト。
```

この指示には問題がありました。
期待していたのは次のデータです。

```json
"player": [
    {"player": "藤岡幸夫", "instarument": "関西フィルハーモニー管弦楽団首席指揮者"},
    {"player": "関西フィルハーモニー管弦楽団", "instrument": "管弦楽"}
  ],
```

実際に返ってきたのは次の通りです。

```json
"player": []
```

理由としては、指揮やオーケストラは楽器ではないからでしょう。冷静に考えればそのとおりです。僕の指示が正確ではありませんでした。
より正確な指示として, 「楽器名」ではなく「役割」としました。

```
演奏者名と役割の一覧。{player: [{player: value, roll: value}]}の形式で。当てはまるものがないときは空のリスト。
```

こうすることで期待通りの結果が返ってくるようになりました。
ChatGPT を使いこなすには正確な指示を出すことが重要なようです。

## まとめ

以上が今回 ChatGPT を使うときに気づいたテクニックです。
うまく指示を出さないと期待通りの結果が返って来ませんでしたが、工夫することでだんだんと期待する結果になっていくところが楽しかったです。

しかしこれだけ自分の指示の不正確さに気づくと、日頃から僕が人に指示している内容も解釈の余地が大きいのではないかという気がしてきて申し訳ない気持ちになっています。
ChatGPT を使いこなせるようになると人に対する指示がうまくなる、なんてこともあるかもしれませんね。