ChatGPTに人格と記憶と声を持たせて話し相手になってもらう

# はじめに

今回は、私が作成したCLIアプリ「[chat_my_assistant](https://github.com/u1and0/chat_my_assistant)」を紹介します。

このアプリは、gpt-3.5-turboモデルを使用して自分専用のチャットボットを作成することができます。GPT-3は、OpenAIが開発した最先端の自然言語処理技術で、あらゆるテキストを生成することができます。chat_my_assistantは、ChatGPTのCLIアプリであり、人格、記憶、声を持たせ、より人間に近い会話を目指しています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/KWh5uhvWWgQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# インストール

`chat_my_assistant`は、GitHubからクローンして使うことができます。そして、pipで必要なライブラリをインストールします。

```
$ git clone https://github.com/u1and0/chat_my_assistant
$ cd chat_my_assistant
$ pip install -r requirements.txt
```

以上でインストールは完了です。次に、基本的な使い方について説明します。




# 基本的な使い方
openaiのAPIを使うので、環境変数に[openaiのAPI](https://platform.openai.com/account/api-keys)[^1]を設定する必要があります。

[^1]: [【2023年版】OpenAIのAPIキ発行手順！ChatGPTや文生成AI、画像生成AIを利用可能](https://auto-worker.com/blog/?p=6988)

```sh
export CHATGPT_API_KEY='sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
```

GitHubからクローンしたディレクトリ上で下記コマンドを実行します。

```shell-session
$ python chatme.py
```

![chat_my_assistant_introduction.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/08c2ad27-049c-d153-ab1e-02fadd04923c.gif)



1. コンソール上でアプリケーションが立ち上がって入力待ち受け状態になります。質問を入力してください。空行(行に何も入力しないでEnter)が入力されるまで、入力を受け付けます。
1. 入力を確定すると回答待ち受け状態になります。回答が来ると1文字ずつ回答が出力されます。
1. 回答を表示しきると1へ戻って、質問待ち受け状態になります。1-3を繰り返します。
1. 終了したいときは"q"または"exit"を入力して入力を確定すると終了処理(会話の要約と記憶)が走って、しばらくするとステータス0でコマンドが終了します。


以上が基本的な使い方です。ここまでだとただのChatGPTをコマンドラインから使えるようになっただけです。次項からこのコマンド特有のキャラクター設定、音声の再生、会話の記憶について説明します。



# オプション
chatme.pyコマンドには次のオプションがあります。

```txt:chatme.py/usage
usage: chatme.py [-h] [--character CHARACTER] [--voice] [--speaker SPEAKER]
                 [--yaml YAML]

ChatGPT client

options:
  -h, --help            show this help message and exit
  --character CHARACTER, -c CHARACTER
                        AIキャラクタ指定(default=ChatGPT)
  --voice, -v           AI音声の生成先 (-v=SLOW, -vv=FAST, -vvv=LOCAL, default=None
                        = 音声で話さない)
  --speaker SPEAKER, -s SPEAKER
                        VOICEVOX キャラクターボイス(str or int, default None)
  --yaml YAML, -y YAML  AIカスタム設定YAMLのファイルパス
```


## キャラクターのカスタマイズ


--yamlオプションの説明です。

あなたのgist上にcharacter.ymlという名前のYAMLファイルを作成してください。YAMLファイルの書き方はsample/character.ymlに例があります。このファイルではAIのキャラクター、性格、話のトーン、話の長さを決めることができます。
gistの準備、gistを操作できるGitHubトークンが必要です。
　

仮にここでは"俺専用GPT"という名前で作成します。
デフォルトのAIキャラクタはコメントアウト内の設定を読み込みます。

```yaml
# デフォルトのAIキャラクタ
# BaseAIクラスに定義してあります。
#
# - name: "ChatGPT"
#   max_tokens : 1000
#   temperature : 1.0
#   system_role: "さっきの話の内容を聞かれたら、話をまとめてください。"
#   filename: "chatgpt-assistant.txt"
#   voice: Mode.NONE
#   speaker: CV.ナースロボタイプ楽々
#
# カスタムキャラクタを設定してください。
# https://api.github.com/gists/{gist_id}/character.yml
# にこのファイル内の設定を記載してもロードできます。
# ローカル上のファイルを使う場合は次のようにYAMLファイルのパスを指定してください。
# $ python memory_chat_digest.py -y path/to/character_sample.yml
#
- name: "俺専用GPT"
  filename: "ai_memory.txt"
  max_tokens : 500
  temperature : 0.5
  system_role: "
            XXXとして振る舞ってください。\
            XXXXXな口調で話してください。\
            さっきの話の内容を聞かれたら、話をまとめてください。\
            "
```


## キャラクターの割り当て

`--character`オプションの説明です。
先ほどのYAMLファイルに書き込んだnameを-c, --characterオプションに割り当てます。


```
$ python3 chatme.py -c 俺専用GPT

```


<iframe width="560" height="315" src="https://www.youtube.com/embed/IR6HpFGXwUA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

動画ではさっきの動画と同じ挨拶から始まりますが、キャラ付けにより返答内容にバリエーションをもたせています。


## 会話の記憶
上記で説明したgist上に例えば`ai_memory.txt`というファイルを作ります。さらにそのファイル名`filename: ai_memory.txt`をcharacter.ymlファイルに書き込んでください。

さらに環境変数に[Githubトークン](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)とgistのIDを書き込みます。Gist IDはGistのURLのユーザー名の後に続く文字列です。例えGistのURLが`https://gist.github.com/u1and0/b47b454ac12a335bacd62ac5b0e8c6da3`だったら、`b47b454ac12a335bacd62ac5b0e8c6da3`がGistIDです。


```sh
export GITHUB_TOKEN='ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
export GIST_ID='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
```


この設定により、会話ごとの要約を別のChatGPT APIが作成し、gistへアップロードします。一旦会話を終了してから会話を再開すると要約文gistを読み込み、その文脈を元にChatGPTが返答を考えてくれるので、先ほどの会話の内容を憶えていてくれるかのような振る舞いができます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/SLdykmBDzfo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


この時点での彼女の記憶の中身をgist上で確認するとこうなっていました。

```txt:chatgpt-chan.txt
-Kristelが映画か散歩かを提案し、Junichiroは映画なら何でもいいと回答する。
-Kristelが映画に決定し、ポップコーンとソフトドリンクを持っていくことを提案する。
-Assistantが好みのドリンクや食べ物について尋ねる。
-Kristelが炭酸が苦手であり、チキンが食べたいことを回答する。
-Assistantが甘い味の飲み物と特製ウタマロソースを提案する。
-Kristelが感謝の言葉を述べ、楽しい時間を過ごすことを約束する。
```

アシスタントの名前がクリステルで私が進次郎なんですけど、いつの間にか英語でKristelになってるし、私の名前が純一郎(Junichiro)でお父さんの名前になっちゃっています。
よくあることです。プロンプトの改良が必要です。

> -Kristelが炭酸が苦手であり、チキンが食べたいことを回答する。
-Assistantが甘い味の飲み物と特製ウタマロソースを提案する。
-Kristelが感謝の言葉を述べ、楽しい時間を過ごすことを約束する。

この部分はユーザーとアシスタントが逆になってはいますが、先ほどの動画の会話を反映しています。それより前の映画とポピーシードの話は動画より前に話していた内容を記憶しています。



## 音声再生
Ubuntuでは[sox](https://packages.ubuntu.com/search?keywords=sox)と[^5]いうライブラリが必要です。

[^5]: [Sox、UbuntuターミナルからMP3ファイルを再生します](https://ubunlog.com/ja/sox-reproduce-mp3-terminal/)


```shell-session
$ apt update && apt install sox
```

音声の合成方法は方法は3つあります。

1. API(低速)[^1]を使う。
1. API(高速)[^2]を使う。
1. ローカル環境のVOICEVOX[^3]を使って文字列から音声(wavファイル)を作る。

[^1]: [WEB版VOICEVOX API（低速）](https://voicevox.su-shiki.com/su-shikiapis/ttsquest/) (オプション-v)
[^2]: [WEB版VOICEVOX API（高速）](https://voicevox.su-shiki.com/su-shikiapis/) (オプション-vv)
[^3]: [VOICEVOX](https://voicevox.hiroshiba.jp/) (オプション-vvv)

1は設定不要です。ただし、リクエスト間隔に間を開けないと音声の合成が完了していない場合があるため、このクライアントソフト上からの呼び出しでは動作がまだ不安定です。
2は[apiKeyを設定](https://su-shiki.com/api/)した上で、環境変数` VOICEVOX_API_KEY`が必要です。ただしリクエスト回数がポイント制で上限があります。24時間ごとにリセットされます。

```sh
export VOICEVOX_API_KEY='XXXXXXXXXXXXXXXX'
```

3はVOICEVOXをインストールして、ローカルマシンに起動しておく必要があります。

それぞれコマンドラインオプション`-v, -vv, -vvv`で呼び出せます。
vの数が多くなるほどに設定難易度が難しくなる代わりに、レスポンスが高速で確実性があります。


## キャラクターボイスの設定

--speakerオプションの説明です。

キャラクターの声はVOICEVOXで生成しています。キャラクターボイスは基本的に先ほどのcharacter.ymlにプリセットしますが、コマンドラインオプションで好きに変えることもできます。指定できるオプションはVOICEVOXで指定できるキャラクターボイスの文字列か、それに紐づいたIDです。例えばID=0は四国めたんあまあまがセットされます。


## Gistレート制限の緩和
Gistのリクエスト制限(60分あたり60回)を緩和するために、GistクライアントのIDとトークンを環境変数に設定します[^6]。

[^6]: [GitHubのAPIのリクエスト制限を避けるためにclient idなどを取得する](https://rcmdnk.com/blog/2017/04/22/blog-octopress-github/)


## 設定まとめ
以上をまとめてホームディレクトリに.secretなどというファイルを作成し、.bashrcなどに`source ~/.secret`と書いて読み込んでください。

```sh:.secret
#!/bin/sh
export OPENAI_API_KEY='sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
    GITHUB_TOKEN='ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
    GIST_ID='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
    VOICEVOX_API_KEY='XXXXXXXXXXXXXXXX' \
    GIST_CLIENT_ID='XXXXXXXXXXXXXXXXXXXX' \
    GIST_CLIENT_SECRET='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
```

```shell-session
$ chatme.py -c 俺専用GPT  -vv -s 0
```

オプションの意味は次のとおりです。
* -c: 俺専用GPTという名前のキャラクター設定を読み込む
* -vv: VOICEVOX高速APIで音声合成をする
* -s 0: ID 0 の四国めたんあまあまで発声させる


# Vim/Neovimから使う
コーディングのお供になります。イメージとしては[GitHub Copilot](https://github.com/features/copilot)です。

![copilotgif](https://techcrunch.com/wp-content/uploads/2023/03/github_chat.gif)

まず、`:source plugin/chat_function.vim`コマンドを実行して、プラグインの関数を読み込みます。その後、　<kbd>leader c</kbd> キーバインドを割り当てているため、テキストを選択し、<kbd>leader c</kbd> により、選択範囲のテキストをchatme.pyコマンドの標準入力に貼り付けることができます。

さらに、追加のテキストを入力することで、様々なタスクを実行することができます。

タスクの例

* 上記のコードのリファクタリングをして
* 上記のコードのデバッグをして
* 上記のコードのテストコード作って
* 上記のコードのドキュメント / READMEを作って

![chat_my_assistant_doc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/d3db54f1-96a0-8b88-a12b-6c559c6155c3.gif)
docを書かせています。

![chat_my_assistant_refactor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/518a639d-6e69-e8c0-b355-aa17a3a6dd4f.gif)
更にリファクタリングさせています。
`:se ft=python`でシンタックスハイライトを有効にしています。

![chat_my_assistant_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/87cfb23d-7757-e57e-c083-5a3c05cf3886.gif)

別の関数のテストを書かせています。


これらのコマンドを使って、よりスムーズなコーディングを実現することができます。

似ているプラグイン[^5]があった、と言うかこのプラグインに影響されて作り始めたこのプロジェクトなのですが、影響を受けたプラグインでは下記のような実装で、私のやりたいこととズレが有りました。

[^5]: [mattn/vim-chatgpt](https://github.com/mattn/vim-chatgpt)

* ファイルのコードまるごと送っていたので、特定の関数だけに関する質問ができない。
* Vimコマンドに組み込まれた指示しか呼び出せないので、ケースバイケースの質問ができない。

`chat_my_assistant`では選択範囲のテキストをyank/pastしてすぐにChatGPTへ送ることができ、返答をyank/pasteしてすぐにコードへ反映することができます。そして、指示を思いついたままに自然言語で指示することができます。


# おまけ機能: 入力がなくても勝手に話し始める
5分黙っていると勝手にChatGPTが話し始めます。

実態は入力待ち中に非同期に5分カウンターを回し、5分入力がないと下記の入力をランダムに選んでChatGPTに渡しています。

* 続けて
* 他の話題は？
* これまでの話題から一つピックアップして
* ""

最後のダブルクォーテーションは「無言」の意味です。
無言を渡すと再び5分タイマーが起動します。

要するに、「5分黙っていると75%の確率でなんか喋りだす」AIになります。
起動させっぱなしにご注意ください。無制限にトークンを消費します。


# 関連
Siriの音声認識と再生機能を使って、iPhoneを介してChatGPTと会話することができます。`chat_my_assistant`と同様にキャラクターの設定や記憶、声が備わっています。ただし、Siriの制限のため、会話の履歴は要約文の形式になるため、`chat_my_assistant`に比べて会話の精度が低下する可能性があります。また、声は男性と女性の2種類しか選択できません。

以下は、関連する記事のリンクです。

[人工知能ChatGPTが作る永遠の会話。Gistを使った会話の記憶方法とその可能性](https://qiita.com/u1and0/items/e439597ff67c3ad2560b)
[ChatGPTとSiriを利用してスマホに触れずにバーチャル彼女と会話する](https://qiita.com/u1and0/items/587054d7471ffc70a698)


# さいごに
APIの恩恵により、私が作ったCLIはクエリとデータをエンドポイントに送信するだけで、ChatGPT APIでセリフと要約を考え、要約をGist APIで保存し、VOICEVOX APIで発声することができました。このような便利なAPIを提供してくれるChatGPT開発元のOpenAI、Gistの開発元Github、VOICEVOXの作者に感謝いたします。私たちの生活は、APIによってより快適で効率的になっていることを改めて実感しました。