slackでずんだもんと話そう！（GPT-3.5によるキャラクター性をもった発話生成）

# はじめに
この記事は[QualiArts Advent Calendar 2022](https://qiita.com/advent-calendar/2022/qualiarts)の23日目の記事になります。

昨今、様々な生成系AIが話題になっていますね。
特に画像の生成に関しては、今年8月に発表されたStableDiffusionを皮切りに、この半年で飛躍的に進化しており、もはや人間が描いたものかAIが描いたものかほぼわからないレベルに達していると言ってもいいレベルです。
その進化は今をもってとどまることを知らず、最近でも破綻していない人物画を描く技術や、テキストから3Dを生成する技術も発表されてきています。

一方、発話生成に関してもつい最近OpenAIによってChatGPTが発表され、その能力について多くの人に驚きを持って迎えられました。
東京の観光地について聞けば夜の東京タワーの美しさについて話し、「口裂け女」を楽しい話にアレンジしてほしいというと子どもたちと楽しくキャンプをする口裂け女の話が生成され、どこが間違っているかわからないプログラムを見せるとnullが許容されていないことと関数名が最適でないことを教えてくれます。それはまるで今まで無理だ無理だと言われてきた汎用AIが急に完成されて出てきた錯覚にさえ陥ることでしょう。
（ちなみに余談ですが、このブログの構成もChatGPTに提案してもらったものを下地として書いています）

人工無脳というものが流行ったころから、AIとの自然な会話を夢みている私にとってはChatGPTは本当に胸躍る技術です。
しかし、実際にChatGPTを使ってみるとわかるのですが、そのまま素直に使うと、ChatGPTの発言にはキャラクター性はありません。あえて言うなら、とても有能な（まれに知ったかぶりする）秘書のようで、基本的には丁寧に、感情のない回答を返します。
それは私が求めていたものとは少し異なります。私はアニメやゲームのキャラクターと雑談を楽しみたいのです。秘書や先生を求めていたわけではありません。
そこで、この記事では、「ずんだもん」というキャラクターを例に、ChatGPTの技術を用い、キャラクター性をもった発話生成をする方法について提案します。

# ChatGPTについて
ChatGPTはOpenAIによって作られたチャットボットであり、対話に特化したAI言語モデルです。
その基礎となっているモデルは、2年前のアドベントカレンダーで触れた「GPT-3」であり、その改良版である「InstructGPT」です。
それぞれについて簡単に説明します。

## GPT-3とは
GPT-3のGPTとは、Generative Pre-trained Transformerの略で、Googleによる言語モデルである「Transformer」を言語生成タスクで事前学習するモデルということです。
これは2020年6月にベータ版がリリースされ、その性能の高さに大きな話題になりました。
その学習には**Webページを中心に本やWikiなどから**、トータルで5000億弱のトークン（単語など学習の単位）を使用しています。
[もう少し詳しく説明した記事を2年前に書きましたので、興味のある方は見ていただけますと嬉しいです。](https://qiita.com/takashimax/items/a64c70ecb5a26f5bc8e8)

## InstructGPT（GPT-3.5）とは
GPT-3にはいくつかの問題がありました。
例えば、GPT-3に対して「月面着陸について短い文章で6歳児に説明してください」と入力すると「6歳児に重力について説明してください。6歳児に相対性理論について少しの文で説明してください。（以下略）」など、回答としては不適切な文を生成することが多くありました。
加えて、「真実ではなく」「有毒で」「攻撃的な」文章を生成する傾向があったことが大きな問題でした。
その問題は学習したものに起因しています。
そう。GPT-3は前述のようにWebページを中心とした大規模なデータセットから学習しています。
インターネット上には「真実ではなく」「有毒で」「攻撃的な」情報が非常に多くありますが、学習データを作る際にその内容について人間は評価をしていませんでした。

InstructGPTは上記の問題を改善したものになります。
何が違うかというと、InstructGPTは学習の際に人間の評価を反映させるようにしました。
といっても、さすがに学習時に人間がすべての結果を確認して良し悪しを評価しているわけではありません。
簡単に言うと「人間が好む結果が出せたかどうか評価するモデル」を事前に用意して、生成された文をそのモデルを使って評価し学習（調整）していくということを行っているようです。
これにより、人がすべての結果に対して評価をしなくても、人にとって良い結果が得られるようになりました。

## ChatGPTとは
ここまでで、おおよそ良い結果が得られるようになったように見えます。
では、ChatGPTとInstructGPTは何が違うか（何が改善されたか）というと、ChatGPTは対話に特化しているという点でInstructGPTとは異なります。
これにより、InstructGPTよりもさらに自然な回答を行うことができるモデルが出来上がっています。
それによってできたモデルがどの程度のものなのか。[OpenAIのページ](https://openai.com/blog/chatgpt/)からwaitlistに参加すればWeb上からその応答を試すことができます。
これまで触ったことがない場合は、ぜひ触ってみてほしいです。そのあまりの自然さに驚き、そしてこれにより変わっていく未来に心配さえするのではないかと思います。
ちなみに私は「本当はAIなんかじゃなくて数万人のスタッフが裏で入力していました」というニュースが流れたら信じてしまうかもしれません。

ちなみに、この記事の投稿日時点では、残念ながらChatGPTはAPIからは使用できません。
しかし、InstructGPTのモデルはAPIから使用することができます。
そこで、InstructGPTを利用してシステムを構築することとします。

# キャラクター
いざ、キャラクターらしい発言をさせようと思ったとき、ふと頭に浮かんだので、今回は「ずんだもん」を例に作ってみることにします。

## ずんだもんとは
ずんだもんとは東北地方応援キャラクター「東北ずん子」の関連キャラクターです。
<img width="300px" alt="ずんだもん" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/7acd03c3-2d26-fc03-41f7-0632718c4fe7.png">
アニメ「ずんだホライずん」にも登場しており、様々なところで展開されいるこの「ずんだもん」ですが、VOICEVOXというテキスト読み上げソフトで使用できるキャラクターとして採用されたことにより、最近ではいわゆる「ゆっくり」であるSoftolkの代わりに使われるようになり、一気に有名になった印象があります。

## ずんだもんの発言の特徴
ずんだもんの発言には以下のような特徴があります。
- 「かわいい精霊、ずんだもんにお任せなのだ！」というように語尾に「なのだ」をつける
- 一人称は「ボク」
- 「！」を多用する
- 元気な子供のような感じ

これらのような特徴が再現できれば成功とします。

# 実装するシステム
今回は「ずんだもん」のような発言をするBotの実装を目標とします。
簡単な仕様としては以下のようになります。
- Slack上でユーザがBotにメンションをつけて発言する
- ユーザの発言を受け取ると、Botはその内容に応じた「ずんだもん」っぽい発言をSlackに投稿する
- Botが生成する発言は、直前にユーザが発言した内容だけでなく、それまでの文脈も考慮する

# 前準備
今回のシステムを作るにあたって、いくつか準備が必要なので、その方法について記述します。

## InstructGPTを使用するための準備
InstructGPTを使用するためにはOpenAIのアカウントが必要です。
以下のページから登録をしてください。
https://openai.com/api/

登録後、ログインすると、Topページ（ https://beta.openai.com/overview ）の右上にメニューがありますので、
そこから「View API keys」を選択すれば、Keyを生成するページに遷移できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/d35316ab-2164-1c06-bc3a-bdeff5769f99.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/447907e8-9e0b-97f7-0174-fb7dc57fa4e1.png)
この際表示されるキーは、今後再度表示することができませんので、しっかり保存してください。

## Slackとの連携をするための準備
BotがSlackから発言内容を取得したり、発言を投稿するために、SlackAppを作成して必要なトークンを生成する必要があります。

### SlackAppの作成
SlackAppを作成するには、 https://api.slack.com/apps にアクセスして「Create New App」を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/85d7ff7e-00d1-095f-4f84-1991edb25b34.png)
その後、出てくる選択肢で「From scratch」を押すと、アプリの名前とどのワークスペースに作るのかを選択する画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/99e4f761-4198-b9cd-4677-e04f8acd429f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/87695392-57cf-c181-4bde-db4cff16768c.png)
最後に、「Create App」を選択するとアプリが作られます。

### SlackAppの設定
作ったアプリについて、以下の設定を行います。
- WebSocketを使うことができるように設定
- アプリの権限の設定
- イベント購読設定
- Workspaceへのインストール
- チャンネルにアプリを組み込む

#### WebSocketを使うことができるように設定
WebSocketを使うことができるように「Socket Mode」の中にある「Enable Socket Mode」を有効にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/a0f26e6a-b889-bdb5-4def-eb2e3dac3f93.png)
押すと、トークンの名前を設定する画面が出てくるので適当に名前を付け、Generateを押す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/c008cba6-f17e-586f-24b2-57012c2f8e3f.png)
すると、以下のような画面になります。ここで表示されるTokenは後ほど使用しますので、控えておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/35ab88c4-8724-4625-171b-b5a21f58961c.png)

#### アプリの権限の設定
アプリが
- メンションがついたメッセージを取得する
- メッセージを投稿する

ことができるように、それぞれ
- app_mentions:read
- chat:write

を「OAuth & Permissions」にある「Scopes」に追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/20db3895-2e75-0027-0961-f0d4ab594d74.png)

#### イベント購読設定
BotにメンションがついたときにBot側に通知がほしいので、イベント購読の設定をします。
イベントを購読するためには「Event Subscriptions」の「Enable Events」を有効にします。
そして、「Subscribe to bot events」の「Add Bot User Event」を押して、「app_mention」を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/d7ab93d1-0887-d7db-239b-0d8f45ba886b.png)

#### Workspaceへのインストール
Workspaceにインストールするには「Oath & Permissions」の「OAuth Tokens for Your Workspace」から行います。
「Install to Workspace」を押すと、ワークスペースにアクセスする権限を確認するページに遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/c91074d7-ee50-b0aa-0124-5f14f3392b9a.png)
ここで表示されているワークスペースが正しいか確認した上で、「許可する」を押してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/5b28602f-2a24-2766-d7fa-6f24c08096b3.png)
すると、トークンが発行されるので、こちらも控えておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/db997a2a-7080-426f-5a4d-004f63a2a4d2.png)

#### チャンネルに組み込む
最後にBotと会話するチャンネルにアプリを組み込みます。
Slackでチャンネルを開いたとき、チャンネル名の横に下三角のボタンがありますので、それを押すと、チャンネル情報などが確認できます。
その中にインテグレーションの設定があるので、そこでアプリを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/088ef0e9-55b6-2280-4e01-05d3b54b9441.png)
「アプリを追加する」を押し、先程作ったアプリ名を検索するとこのように表示されているかと思います。
表示されているアプリを追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/8bfbd0c1-321b-3296-b88a-5b8d1df47203.png)

以上でSlackを使用するための前準備は以上です。

## キャラクターを定義するファイルの作成
ずんだもんらしい発言をしてもらうために、ずんだもんの設定と発言例をjsonとして記述しておきます。
以下が作ったキャラクター定義ファイルです。
https://github.com/qua-tkmax/character-chatbot/blob/master/character.json
一部抜粋して説明します。
```character.json
{
  "name": "ずんだもん",
  "overview": [
    "東北ずん子の武器である「ずんだアロー」に変身する妖精またはマスコット",
    "一人称はボク"
  ],
  "serif": [
    "ボクはずんだもんなのだ！",
	"ハーッハッハッハ！！ ずんだもんは人間とは格が違うのだ！！ ずんだもんをあがめるといいのだー！！"
  ]
}
```

このように、このファイルでは以下の3点について記述されています。
- キャラクター名
- キャラクター設定（概要）
- セリフ例

なお、ずんだもんのセリフ例に関してはずんだもんのボイスから抜粋して使用しています。
ボイスの内容については[こちらのブログ](https://tsts4767.hatenablog.com/entry/ar1949298)を参考にしています。

# 実装と結果
実装したものは以下のとおりです。
https://github.com/qua-tkmax/character-chatbot/blob/master/main.py

このシステムの肝は発話生成の部分になりますので、その点について例を用いて説明します。
例えば、ユーザが「はじめまして。自己紹介をお願いします。」と送ったとします。
システムはGPTに特定のキャラクターらしい発話生成をさせるために、以下のような内容をGPTに入力します。
```
以下はずんだもんの設定です。

東北ずん子の武器である「ずんだアロー」に変身する妖精またはマスコット
一人称はボク

以下はずんだもんのセリフです。

ずんだもん「ボクはずんだもんなのだ！」
ずんだもん「ハーッハッハッハ！！ ずんだもんは人間とは格が違うのだ！！ ずんだもんをあがめるといいのだー！！」

ずんだもんっぽく、以下に返信してください。
マスター「はじめまして。自己紹介をお願いします。」
```

このように、設定と発言例をGPTに一緒に送ることで、GPTは発話内容を作る際にその続きに繋がりそうなものとしてそのキャラクターっぽい文を生成します。
今回の場合であれば以下のような結果が得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/2a86c6bd-1711-0a91-8acb-f4bd48ab924b.png)

なんだかそれっぽい感じの発言が得られていますね。
入れた発言例を利用していますが、そのままは使用せず、アレンジされた感じになっています。

次に、文脈を反映する方法について説明します。
文脈を考慮する方法として、その生成した文を含む会話を全て最後に追加していきます。
例えば、上記の例であれば、ユーザが「ん？何に返信するって？」と返信したとします。
すると、次の発話生成のときには以下のような内容をGPTに入力することになります。

```
以下はずんだもんの設定です。

東北ずん子の武器である「ずんだアロー」に変身する妖精またはマスコット
一人称はボク

以下はずんだもんのセリフです。

ずんだもん「ボクはずんだもんなのだ！」
ずんだもん「ハーッハッハッハ！！ ずんだもんは人間とは格が違うのだ！！ ずんだもんをあがめるといいのだー！！」

ずんだもんっぽく、以下に返信してください。
マスター「はじめまして。自己紹介をしてください」
ずんだもん「わーい！ ボクはずんだもんなのだ！ 東北ずん子の武器である「ずんだアロー」に変身する妖精なのだ！ ハーッハッハッハ！！ ずんだもんをあがめるといいのだー！！」
マスター「ん？何に変身するって？」
```

このようにすべて入力することで、文脈を反映します。
今回の例であれば、以下のような結果が得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/06370e30-ed49-f65e-2140-389114ed66e7.png)

ちゃんと「何に変身」と言っただけで、「ずんだアロー」についての内容を返してくれていますね。

最初に目標とした
- Slack上でユーザがBotにメンションをつけて発言する
- ユーザの発言を受け取ると、Botはその内容に応じた「ずんだもん」っぽい発言をSlackに投稿する
- Botが生成する発言は、直前にユーザが発言した内容だけでなく、それまでの文脈も考慮する

の3点は達成したと思っていいかなと思います。

# 課題と展望
さて、今回実装してみて課題に感じた箇所がいくつかあります。
- 一度システムを落とすと文脈を考慮できなくなる
- 何度もやり取りすると、GPTに入力できる上限を超えてしまう

それぞれについて、今考えている解決策を書きます。

## 「一度システムを落とすと文脈を考慮できなくなる」への対策
これは簡単で、slackを利用しているので、それまでの投稿履歴を取得すれば解決します。
ただ、もう一つの課題にも関わりますが、どの程度の履歴を取得するかは問題になります。

## 「何度もやり取りすると、GPTに入力できる上限を超えてしまう」への対策
5回程度ボットとやり取りすると以下のようなエラーが発生しました。
```
ERROR:slack_sdk.socket_mode.builtin.client:Failed to run a request listener: This model's maximum context length is 4097 tokens, however you requested 4128 tokens (3104 in your prompt; 1024 for the completion). Please reduce your prompt; or completion length.
```
これは入力する文字数が上限を超えてしまったために発生しているものと思われます。
この課題に対する対策は2つ考えられます。
- 発言例の量を削減する
- 文脈を反映する方法を変える

発言例については、現在1500文字程度使っているのですが、これだけの量は不要かもしれません。
ずんだもんの発言をわかりやすく表した例だけに絞ることで、より多くの会話ができるようになると思います。
しかし、それにより反映できる文脈を多くすることはできますが、それでも上限はあります。
そこで、もう一つの対策が必要になります。

現在、文脈としてすべての文をそのまま入力として使用しています。
しかし、この方法を使用している以上、上限に引っかかる問題は避けられません。
そこで、古い発言に関しては要約した状態で利用することを提案します。
それにより、直近の発言は明確に反映することができ、過去の発言は少し反映することができるようになると思われます。
要約についてもGPTで行うことができるので、別のシステムを使用することなく実現できる想定です。

# まとめ

キャラクター性を持った発話生成をおこない、Slackでユーザと会話するシステムを作りました。
発話の例として、ずんだもんを使用して以下のような会話ができるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532731/278d8d85-e86e-08e4-64ec-4a57d22d91e3.png)
今回使用したGPT-3.5は今話題のChatGPTの前身のものなので、最新のモデルが使えるようになると更により良い結果が得られるようになると思われます。
キャラクターと楽しく雑談できるAIができる日も近そうで楽しみですね！