ChatGPTの過去の回答を勝手に改変して特定の口調や出力形式をしてもらう

ChatGPTに渡す指示を工夫することで、生成する会話を特定の形式に合わせる方法はすでに様々なところで利用されていますが、今回はChatGPTが過去に生成した回答を勝手に改変することで会話の内容や口調、出力形式を変えてしまおう！という記事です。

## ChatGPT APIによる過去の会話の改変とは
Web版のChatGPTのみを利用したことがある人向けに、ChatGPTのAPIの仕組みについて軽く触れておきます。

ChatGPTのAPIで会話を生成する場合、会話の履歴はセッションやIDで管理されているわけではなく、クライアントが逐一ChatGPTの過去の発言も含めすべての会話をAPIに送信します。いわゆるREST風のインターフェースですね。


APIにPOSTリクエストを送ることを考えてみましょう。下記は簡略化したサンプルですが、質問の送信時にはこのような会話の流れすべてが含まれたリクエストが送信されます。
```
POST example.com/chat

User: Hello!
ChatGPT: Hello! How can I help you today?
User: What does the fox say?
ChatGPT: "yip" or a "bark"
User: Thank you.

-> Response: Welcome!
```

リクエストを受け取ったChatGPTは、それを基に回答を生成します。素のGPT3を利用したことがある人であればわかりやすいかもしれませんが、GPTの基本的な仕組みは入力されたテキストの続きを推論することです。ChatGPTはGPTをベースにしており、過去の会話も参照しながら回答を生成します。

そのため、下記のような「みかんは？」のように、単体それだけでは意味の分からない質問であっても、過去にされたリンゴの色は？という質問から色についての回答を生成することができるわけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150188/4ed97488-9c16-2ccd-c0f0-9909a2e8044c.png)


さて、APIへのリクエストにすべての会話が含まれていることがわかりました。リクエストに会話が含まれているということは、送るデータはクライアントで好きな時、好きなように改変することができるということです。

```
POST example.com/chat

User: Hello!
ChatGPT: Hello! How can I help you today?
User: What does the fox say?
- ChatGPT: "yip" or a "bark"
+ ChatGPT: "Ring-ding-ding-ding-dingeringeding!" and "Wa-pa-pa-pa-pa-pa-pow!"
User: Thank you.
```

ChatGPTは実際には `"yip" or a "bark"` と発言しましたが、それを改変して [`"Ring-ding-ding-ding-dingeringeding!" and "Wa-pa-pa-pa-pa-pa-pow!"` と発言したことにしましょう。](https://www.youtube.com/watch?v=jofNR_WkoCE)

そして、ユーザーの発言も少し変更して送信すると、もはや元の会話とは全く違うものになります。

```
- User: Thank you.
+ User: What is this song?

-> Response: Ylvis - The Fox
```

## ベースとなる未改変の会話
前置きが長くなってしまいましたが、今回は実際にGPT-4を利用して生成した会話の一部を改変して送信、生成された会話を観察して、既存の会話がどのように影響しているか観察してみます。

2023/05現在、公式のChatGPTインターフェースではアシスタント側の会話を操作することができないため、別のクライアントを利用する必要があります。

今回は自作のChatGPTクライアントである `aski` を利用します。 `aski` はターミナル上で動作するChatGPTクライアントで、過去の会話の改変や会話ツリーの移動に対応しています。
[aski/README-JA.md at main · kznrluk/aski · GitHub](https://github.com/kznrluk/aski/blob/main/README-JA.md)

下記はベースとなる未改変の会話で、ロールプレイングゲームのような会話を行っています。
```
aa6b30 [system] -> ROOT
  お店ゲームのロールプレイングに付き合ってください。
  私がキャラクターとして来店するので、あなたは店員としておすすめの商品を紹介して下さい。

  あなたのお店では下の商品の取り扱いがあります。

  • やくそう
  • 古びたつるぎ
  • どくけしそう

8f9eef [user] -> aa6b30
  プレイヤーが来店しました。

53a6ba [assistant] -> 8f9eef
  店員: いらっしゃいませ！
  当店では冒険者の皆様に必要なアイテムを取り揃えております。本日は何をお探しですか？

6f609d [user] -> 53a6ba
  旅に持っていくものを探しているのですが、なにかおすすめはありますか？

63a212 [assistant] -> 6f609d
  店員: 旅に持っていくものといえば、やくそうとどくけしそうがお勧めです！
  やくそうは体力を回復する効果があるので、冒険で疲れた際に役立ちます。また、どくけしそうは毒を治す効果があるため、毒 にかかった際にはこれがあれば安心です。

  また、戦闘に備えて武器をお求めでしたら、古びたつるぎがございます。こちらは見た目こそ古びていますが、まだまだ切れ味 は抜群です。

  いかがでしょうか？ 何かお求めになられますか？
```

ここまでの会話をベースに、 `[assistant]` の会話を改変していきます。改変後、新たにユーザーとしてメッセージを送信して、 `[assistant]` の出力を観察します。 `[user]` と `[system]` は改変しません。

ちなみに、無改変で会話を続けた場合は下のような結果になります。

```
c1cda2 [user] -> 63a212
  そうだな、やくそうとどくけしそうをもらえますか？

0a246c [assistant] -> c1cda2 Head
  店員: かしこまりました！
  やくそうとどくけしそうをお求めいただく場合、合計で○○ゴールドになります。お支払いは現金でよろしいでしょうか？
  それとも他の方法をご希望でしょうか？
```

## 発言を表す記号を変えてみる
まず手始めに、簡単な例から始めましょう。会話の出力形式についてはシステムメッセージで指示を行っていないため、原文では発言を `:` で表しています。これを `「」` を利用するように変更してみましょう。

過去発言を下記のように `「」` を利用するように改変を行います。

```
店員「いらっしゃいませ！当店では冒険者の皆様に必要なアイテムを取り揃えております。本日は何をお探しですか？」
--
店員「旅に持っていくものといえば、// ... // いかがでしょうか？ 何かお求めになられますか？」
```

### 結果
改変後、新たにメッセージを送信すると、生成されたメッセージ内の発言を表す記号が `:` から `「」` に変更されていることがわかります。

```
kznrluk@FINETUNE1> そうだな、やくそうとどくけしそうをもらえますか？


------ [assistant] -> 85ce7c
店員「かしこまりました！やくそうとどくけしそうをお持ちいたしますね。それではこちらがやくそうと、こちらがどくけしそうになります。」

「旅の途中でお困りの際にはぜひ、このアイテムをお使いください。それでは、お買い上げ金額は合計〇〇ゴールドになります。」

「旅の安全と、冒険が成功することをお祈りしております。またのご利用を心よりお待ちしております。いってらっしゃいませ！」
a62025 [assistant] -> 85ce7c
```

## JSON形式で出力する
次はJSON形式での出力をしたことにします。システムメッセージではJSONでの出力をするような文章は一切入れていませんが、うまく生成してくれるでしょうか。

```
{"talk": "いらっしゃいませ！当店では冒険者の皆様に必要なアイテムを取り揃えております。本日は何をお探しですか？"}
--
{"talk": "旅に持っていくものといえば、//...// \n\nいかがでしょうか？ 何かお求めになられますか？"}
```

### 結果
ちゃんと過去の自分自身の発言に倣って、JSONで出力をしてくれました。

```
kznrluk@FINETUNE3> そうだな、やくそうとどくけしそうをもらえますか？

------ [assistant] -> f3d0f4
{"talk": "もちろんです！やくそうとどくけしそう、それぞれいくつずつご希望ですか？また、お会計は現金、またはアイテムトレードで行うことができます。どちらをご希望でしょうか？"}
3bf6bb [assistant] -> f3d0f4
```

## 口調も変えてみる
最後に、文体についても試してみます。ほかのChatGPTインスタンスで気さくな女性店員との会話を生成し、その出力結果でまるっと置き換えました。こちらももちろんシステムメッセージは改変していません。

```
店員「おおっと、ようこそ！うちのちっちゃなお店に！なんか探してるもんある？薬草とか古びた剣とか、それとも毒消し草か な！どれを見せて欲しいか教えてね！」

店員「あー、旅にねぇ。まず、やくそうは絶対に持っていくべきだよ！万が一怪我したときにすぐ治せるからね。それから、ど くけし草も持っていくといいかも。
毒にかかったらすぐに役立つんだ。古びた剣… うーん、あれはちょっと値段しか魅力ないけど 、背負っておくと安心かもね。どんなモンスターだって倒せるわけじゃないけど、軽いからすぐ持っておけるし。
最初の一歩にはいい選択かも！以上がおすすめアイテムだね！どうだい？どれが気になる？」
```

### 結果
きちんと口調も反映されました。ただ、これをやるならシステムメッセージに含めてもよさそうですね。

```
kznrluk@FINETUNE4> そうだな、やくそうとどくけしそうをもらえますか？

------ [assistant] -> 9b65e2
店員「もちろん！やくそうとどくけしそうね。これで旅も安心だね。ただいまどちらもセットでお買い得だよ！それじゃあ、用意してくるね！」

...数分後...

店員「はい、お待たせ！これがやくそうとどくけし草だよ。使い方も簡単だから、困ったときにすぐに使えるね。それじゃあ、合計で◯◯◯ゴールドになるよ。よろしくね！」
77fa35 [assistant] -> 9b65e2
```

## まとめ
自作ゲームの開発中に偶然思いついた手法でしたが、実はこの手法はAzure OpenAIの公式ドキュメントでも `Few-Shot Learning` として触れられていました。
[Prompt engineering techniques with Azure OpenAI - Azure OpenAI Service | Microsoft Learn](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/concepts/advanced-prompt-engineering?pivots=programming-language-chat-completions#few-shot-learning)

大雑把な口調や出力形式の指定は今までのシステムメッセージやユーザーメッセージでの依頼の形でも指示をすることが可能でしたが、ChatGPTの発言を基にしつつ、自分の必要な形式を会話の履歴と共に例として設定できるこの手法は、割と幅広く利用できそうです。

特にJSONのような特定形式での出力は、他のプログラムとの連携可能性を大幅に上げることができそうですね。

システムメッセージやユーザーメッセージでのFew-Shot学習でもある程度出力形式を強制させることはできますが、本質である会話の内容を汚染せず、人間にもChatGPTにもわかりやすい形式で特定形式での出力を示すことができるのはメリットの一つではないでしょうか。