ChatGPT APIを使ってキー・バリューなど扱いやすい出力を得る方法
プロンプトエンジニアリングの記事です。
ChatGPTなどGPT-3.5系である程度安定して、加工しやすい出力を得るためのノウハウができたので書きました。土日に別の実験をしていて副産物的に得られたものです。
サンプルコードはTypeScriptですが、プログラミング言語に依存した話ではありません。簡単な正規表現による文字列置換のサンプルです。
出力を得られると何が嬉しいのか?
自然言語を自然言語で加工して、キーと値のペアを取得する、JSONを取得するなどすることができるようになるため、テキストを処理できる汎用ミドルウェアとしてLLMを使えるようになります。おそらくLLMを本格的にソフトウェアに組み込んでいく上で、基礎テクニックとなるでしょう。
異なる複数のプロンプトをつなぐときにも大切なテクニックです。
基本的な考え方
GPT-3.5系ではフォーマットを提示するとそのフォーマットに沿ったテキスト補完を行う傾向が強いです。
たとえば
以下の会話文から、ミーティングの情報をフォーマットに従って抽出せよ。
## 会話文
erukiti: あ、例のアップデートで、めっちゃバグり散らかしてるんですけど、これ対応します?
hoge: 言うて、かなりエッジケースだし緊急で対応するほどじゃないですよね。
erukiti: じゃぁとりあえずMTGの予定だけ決めておきますかね。今日でいいですよね、何時にします?
hoge: 私15:00から空いてるんで、そこからの15分でお願いします。
## フォーマット
MTG内容: MTGの内容の概略
開催日時: 日時
参加者: 参加者
というプロンプトを投げると
MTG内容: アップデートのバグ修正対応
開催日時: 今日の15:15〜15:30
参加者: erukiti, hoge
が補完されます。
色々な意味で賢いですよね。なお 15:15〜15:30
になってるのは ChatGPT ちゃんのお茶目だと思っておきましょう。
フォーマットを例示すると従ってくれる確率はかなり高いです。場合によってはfew-shotでサンプルを例示してもいいかもしれませんが、個人的な感覚では今回使ったプロンプトのようにzero-shotで十分な気がしてます。
今回の内容はかなりシンプルなので処理しやすいですが、このとき複雑な内容を処理していると、出力がブレることがあります。たとえば、コロンが全角になったり、コロンの後ろにスペースが入ったり入らなかったり、フォーマットとして出力される項目に空行が差し込まれたりとかです。
後処理
そこで後処理をしてみました。
const normalize = (content: string) => {
return content.trim()
.replaceAll(/:/g, ":")
.replaceAll(/: /g, ":")
.replaceAll("\n\n", "\n") + "\n";
};
- trim()で文字列の前後の空白を削除
- 全角コロンを半角コロンに置換
- 半角コロン+半角スペースを半角コロンに置換
- 改行の連続を単独の改行に置換
- 末尾に改行を追加
ここまでやるとかなり安定します。
ただし、場合によっては出力フォーマット以外の文章を生成することもあります。特にChat completions APIだと、チャットボットの本性のせいか、前後に解説文章をつけてくれるときがあります。このときは、何かしらの正規表現で判定するしかないと思います。
一番確実なのは項目名と一致する正規表現です。
/MTG内容:([^\n]+)\n開催日時:([^\n]+)\n参加者:([^\n]+)\n/
このような正規表現を使うと確実になります。最悪でも正規表現にマッチしない場合は、再度同じプロンプトを投げてリトライする仕組みを組み込めば良いでしょう。
キー名を固定にしたくない場合
キー名を固定にしたくない場合は /([^\n:]+):([^\n]+)\n/
という正規表現でいいと思いますが、その場合は指示の出し方が変わってきます。
以下の会話文から、ミーティングの情報を抽出して、抽出できた項目をフォーマットに従って出力せよ
## フォーマット
項目名:内容
ただし、キー名が安定しなくなるので、これが実用的かどうかはわかりません。キー名が安定しなくても良い場合には使えるテクニックかもしれません。
さらに発展させてみた(追記)
ミクミンPさんの https://twitter.com/ksasao/status/1632239162219720704 をみて思いついたのが、次の方法
以下の会話文から、ミーティングの情報をフォーマットに従って抽出せよ。
## 会話文
erukiti: あ、例のアップデートで、めっちゃバグり散らかしてるんですけど、これ対応します?
hoge: 言うて、かなりエッジケースだし緊急で対応するほどじゃないですよね。
erukiti: じゃぁとりあえずMTGの予定だけ決めておきますかね。今日でいいですよね、何時にします?
hoge: 私15:00から空いてるんで、そこからの15分でお願いします。
fuga: @erukiti 17:00から30分、お時間いただけますか?外部結合試験の話したいんですけど
erukiti: はい
## フォーマット
MTG1: "日時", "MTGの概略", "参加者"
CSVっぽくする。
MTG1: "今日15:15", "アップデートのバグ対応について", "erukiti, hoge"
MTG2: "今日17:00", "外部結合試験の話", "erukiti, fuga"
ダブルクォートが入ってきた時に面倒か。
前後を囲うことで余計な文章が入っても大丈夫にする(追記)
## フォーマット
---
日時: 日時
概略: MTGの概略
参加者: 参加者
---
つまり出力したいものの前後を ---
で囲うのもあり。
出力結果は、
---
日時: 今日15:15〜15:30
概略: アップデートバグ対応MTG
参加者: erukiti, hoge
---
---
日時: 今日17:00〜17:30
概略: 外部結合試験の話
参加者: erukiti, fuga
---
のようになるので、より出力を判定しやすく、かつ処理しやすくなった。
JavaScriptインタプリタになってもらう
どこまで実用的かはわかりませんが、GPT-3.5はそこそこの精度でJavaScriptインタプリタをやってくれるので、JavaScriptで加工ルールを例示する方法もあります。
以下の会話文から、ミーティングの情報を抽出して、MTG内容をdescriptionに入れて、開催日時をdatetimeにいれて以下のJavaScriptの実行結果を出力せよ
```js
console.log(JSON.stringify({ description, datetime }))
```
## 会話文
erukiti: あ、例のアップデートで、めっちゃバグり散らかしてるんですけど、これ対応します?
hoge: 言うて、かなりエッジケースだし緊急で対応するほどじゃないですよね。
erukiti: じゃぁとりあえずMTGの予定だけ決めておきますかね。今日でいいですよね、何時にします?
hoge: 私15:00から空いてるんで、そこからの15分でお願いします。
を実行すると
{
"description": "アップデートのバグ修正についてのMTG",
"datetime": "今日15:00"
}
MTG内容: アップデートのバグ修正対応
開催日時: 今日の15:00からの15分間
実行結果:
{"description":"アップデートのバグ修正対応","datetime":"2021-09-23T15:00:00.000Z"}
などが帰ってきます。
temperatureを下げると安定したりすることもありますが、複雑な内容を食わせるとミスが増えるので、実用的か?と言われると悩ましいところです。出力した結果をさらに処理するみたいな時に使えるかもしれませんが、それなら最初のやり方でキーと値のペアにしてからプログラミング言語で処理した方が確実です。
JSONで出力する
そもそもJavaScriptで処理しなくても、素直にJSONを出力させてもいいでしょう。
以下の会話文から、ミーティングの情報を抽出して、MTG内容をdescriptionに入れて、開催日時をdatetimeにいれてJSONとして出力せよ
## 会話文
erukiti: あ、例のアップデートで、めっちゃバグり散らかしてるんですけど、これ対応します?
hoge: 言うて、かなりエッジケースだし緊急で対応するほどじゃないですよね。
erukiti: じゃぁとりあえずMTGの予定だけ決めておきますかね。今日でいいですよね、何時にします?
hoge: 私15:00から空いてるんで、そこからの15分でお願いします。
を実行すると
{
"description": "アップデートのバグ対応についてのMTG",
"datetime": "2021-09-01T15:00:00"
}
ちなみにJSを使う場合、JSONを直接出す場合問わず、JSONの細かい部分は割と誤差が生じやすいです。
考察
MKRLやReActと同じで、特定のLLMは出力フォーマットに対する賢さが段違いです。公開されてないのでわからないですが、おそらくMetaのToolchainなんかも同じことができるのではないでしょうか?自然言語汎用ミドルウェアとして活用したい筆者としては、公開されることを期待しています。
MarkdownやJSONなどで書式を与えるテクニックは応用が効くのであれこれ実験してみて損はないと思います。
Discussion