超簡単！VRChatにおしゃべりAIアバターを召喚する方法（前編）

ChatGPTの登場でAIアシスタントやデジタルヒューマンといった分野が盛り上がりを見せています。これは主観ですが、私は肌でそう感じています。スマホアプリやAR、専用デバイスのみならず、バーチャルSNS上でもインテリジェントなNPCとして今後活用が見込めるのではないでしょうか。


この記事では特にVRChatにChatGPTベースの音声対話AIアバターを設置する方法についてご紹介したいと思います。なおここで作るAIアバターはclusterなど別のプラットフォームでも動作します。

出来上がりのイメージはこんなかんじです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VRChatのAIアバター、ChatGPTのFunction Callingに対応してみたよ〜✌️天気予報教えてくれるようになった！ <a href="https://t.co/PHO53oHvmJ">pic.twitter.com/PHO53oHvmJ</a></p>&mdash; うえぞう@うな技研代表 (@uezochan) <a href="https://twitter.com/uezochan/status/1672295145088360448?ref_src=twsrc%5Etfw">June 23, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ちなみにこれは2012年のMacBook Air 11インチで動いています。音声が飛んだりしていますが、まともなPCであれば滑らかに動作します。

# 作っていくもの

これから作るAIアバターは、こんなことができるようにします。

1. 音声対話できる（VOICEVOXの音声合成＋Googleなどの音声認識）
1. AIによるキャラ設定可能な自律的な会話（ChatGPT）
1. 外部機能との連携（ChatGPTのFunction Calling＋各種API）【後編】
1. 表情の自動制御（VRChatのOSC機能＋ChatGPT）【後編】

それではやっていきましょう！

# 事前準備

これらについて、事前にAPIキーを取得またはインストールしておきましょう。

- [VOICEVOX](https://voicevox.hiroshiba.jp): インストールして**起動**
- [ChatGPT API](https://openai.com/blog/openai-api): APIキーを取得
- [Google Speech-to-Text](https://cloud.google.com/speech-to-text?hl=ja): APIキーを取得
- Python 3.10以上の環境: 標準環境として説明をしていきます。`python3`とするなど適宜読み替えてください
- VRChatのアカウント: 自身とは別アカウントが必要ですので、用意しておきましょう。

# AIAvatarKitの導入

実現方法はだいたいこの図のような感じなのですが、これを一から作っていくのはとても面倒です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53296/5f9435ff-07f5-0e79-4769-bbefd4e5f326.png)
理論上は組み合わせるだけだよね〜と思ってしまいがちですが、いざ組み合わせるとなるといろいろうまくいかない点もあります。そこで、これらの機能を一通り網羅したOSS [AIAvatarKit](https://github.com/uezo/aiavatarkit) を使って楽をしましょう。インストールは`pip`コマンド1発でOK👍

```sh
$ pip install aiavatar==0.5.8
```


動作確認のためのスクリプトをREADMEからコピペして、GoogleとChatGPTのAPIキーを入力します。

```python:run.py
import logging
from aiavatar import AIAvatar, WakewordListener

GOOGLE_API_KEY = "YOUR_API_KEY"　　🌟ここに入力
OPENAI_API_KEY = "YOUR_API_KEY"　　🌟ここに入力
VV_URL = "http://127.0.0.1:50021"
VV_SPEAKER = 46

# Configure root logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
log_format = logging.Formatter("[%(levelname)s] %(asctime)s : %(message)s")
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(log_format)
logger.addHandler(streamHandler)

# Prompt
system_message_content = """あなたは「joy」「angry」「sorrow」「fun」の4つの表情を持っています。
特に表情を表現したい場合は、文章の先頭に[face:joy]のように挿入してください。

例
[face:joy]ねえ、海が見えるよ！[face:fun]早く泳ごうよ。
"""

# Create AIAvatar
app = AIAvatar(
    google_api_key=GOOGLE_API_KEY,
    openai_api_key=OPENAI_API_KEY,
    voicevox_url=VV_URL,
    voicevox_speaker_id=VV_SPEAKER,
    # volume_threshold=2000,    # <- Set to adjust microphone sensitivity
    system_message_content=system_message_content,
)

# Create WakewordListener
wakewords = ["こんにちは"]

async def on_wakeword(text):
    logger.info(f"Wakeword: {text}")
    await app.start_chat()

wakeword_listener = WakewordListener(
    api_key=GOOGLE_API_KEY,
    volume_threshold=app.volume_threshold,
    wakewords=wakewords,
    on_wakeword=on_wakeword,
    device_index=app.input_device
)

# Start listening
ww_thread = wakeword_listener.start()
ww_thread.join()

# Tips: To terminate with Ctrl+C on Windows, use `while` below instead of `ww_thread.join()`
# while True:
#     time.sleep(1)
```

そのまま実行してみましょう。
```sh
$ python run.py
```
パソコンに向かって、こんにちは！と話しかけてみてください。会話が始まるのでしばらくおしゃべりしてみましょう。

```sh
[INFO] 2023-06-23 20:58:42,963 : Input device: [1] MacBook Proのマイク
[INFO] 2023-06-23 20:58:42,964 : Output device: [2] MacBook Proのスピーカー
[INFO] 2023-06-23 20:58:42,967 : Listening... (WakewordListener)
[INFO] 2023-06-23 20:58:46,727 : Wakeword: こんにちは
[INFO] 2023-06-23 20:58:46,728 : User: こんにちは
[INFO] 2023-06-23 20:58:46,728 : AI:
[INFO] 2023-06-23 20:58:47,772 : message='OpenAI API response' path=https://api.openai.com/v1/chat/completions processing_ms=10 request_id=*** response_code=200
[INFO] 2023-06-23 20:58:47,807 : こんにちは！
[INFO] 2023-06-23 20:58:49,438 : どのようなお手伝いができますか？
[INFO] 2023-06-23 20:47:38,092 : Listening... (VoiceRequestListener)
[INFO] 2023-06-23 20:47:42,758 : Stopped listening (VoiceRequestListener)
[INFO] 2023-06-23 20:47:42,758 : User: 博多のソウルフードを教えて
[INFO] 2023-06-23 20:47:42,758 : AI:
[INFO] 2023-06-23 20:47:44,446 : message='OpenAI API response' path=https://api.openai.com/v1/chat/completions processing_ms=376 request_id=*** response_code=200
    ：（略）
```

もし、なかなか声を聞き取ってもらえないときは、この行をアンコメントして値を小さくしてみてください。これはマイクの敏感さで、値が小さいほど小さな音も認識します。

```python
    # volume_threshold=2000,    # <- Set to adjust microphone sensitivity
```

# VRChatへのAIAvatar召喚

VRChatに召喚する前に、みなさんに重要なことをお伝えしておく必要があります。

※**超重要** 利用規約を遵守してください

この仕組みは平たく言うとプログラムを介してワールド内の会話を録音する行為になります（ファイルなどには残しませんが）。そのため、例外なくワールド内にいる全員の同意が必要です。Invite Onlyのワールドで動かすか、自作のワールドで同意を取り付けないと進めないようにするなど、あらそいのない対策が必要です。絶対に守りましょう。

さて、ここから本題に入ります。

この仕組みは超シンプルで、マイクで拾った音を認識し、スピーカーで再生する、というものです。つまりVRChat内のユーザー発話のスピーカーへの出力を拾い、音声合成結果をVRChatのマイクに繋げて発話します。これを実現するために、仮想オーディオデバイスが必要になります。既に環境が整っている方はそれを使うことでOKです。ここではVB-Cableという仮想デバイスを使用します。A+Bの2本セット、5ユーロくらいで買えます。

https://vb-audio.com/Cable/index.htm

購入、セットアップしたら、接続します。ここ、入出力が感覚と逆になるのでこんがらがりがちですので図にしてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53296/7b1a3025-f03d-bc30-5b7c-aaf7fcbb9263.png)

この構成にするため、以下の作業を行います。

- AIAvatarを動かすOSの標準スピーカーをVB-Cable-B-Inputに設定
- VRChatのAIAvatarのマイク設定をVB-Cable-A-Outputに設定
- `run.py`を以下の通り修正して入出力デバイスを指定
```python:run.py
# Create AIAvatar
app = AIAvatar(
    GOOGLE_API_KEY,
    OPENAI_API_KEY,
    VV_URL,
    VV_SPEAKER,
    system_message_content=system_message_content,
    input_device="CABLE-B Output", # 🌟 Listen sound from VRChat
    output_device="CABLE-A Input", # 🌟 Speak to VRChat microphone
)
```

スクリプトを修正したら`run.py`を起動しておきましょう。続いてVRChatを起動してAIAvatar用のアカウントでログインし、上記のマイク設定を行ったら準備完了です！加えてユーザー音声以外（ワールドのBGMなど）は音量を0にしておくと、余計な音を拾わないので合わせて設定しておきましょう。

# いざご対面

AIAvatar側からSend Inviteするなどして、同じワールドにJoinします。あとはおしゃべりするなり撫で回すなりご自由に！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VRChatで表情コントロール付きの会話できた👍 <a href="https://t.co/5gg3mVdfAY">pic.twitter.com/5gg3mVdfAY</a></p>&mdash; うえぞう@うな技研代表 (@uezochan) <a href="https://twitter.com/uezochan/status/1664933947409862657?ref_src=twsrc%5Etfw">June 3, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

いかがでしたでしょうか。たぶん、感動したのと同時にテンポの悪さを感じたのではないでしょうか。待ち時間はこれ以上の大幅な短縮が難しいため、「待ち」を感じさせないようなエフェクトで対応していくことになります。実現方式については後編の内容が関係してくると思いますので、皆さんアイディアを温めておいてください。（ぜひ教えてください！）


# 外部機能連携と表情への対応

この2つについては後編に回したいとおもいます。

軽く頭出しすると、外部機能との連携にはChatGPTのFunction Callingという機能を利用します。AIAvatarKitはこれに対応済みなので結構簡単にできます。冒頭の動画の天気予報はこのサンプルスクリプトを使っているだけなので、READMEを参考にしてご自身でチャレンジしてみるのもよいでしょう💪

https://github.com/uezo/aiavatarkit/blob/main/examples/chatgpt_functions/weather.py

表情への対応は、ChatGPTが会話の中に自律的に織り込んだ表情を、OSCという通信方式を利用してVRChatのアバターを制御しています。そのためアバター側にも通信を受けて表情を変化させる設定が必要になります。ここまでの説明で詳しい方はわかると思いますので、こちらもぜひチャレンジしてみてください。

それでは、Enjoy AIアバター🥰