【VRChat】ワールドでChatGPTを使う方法

2024/3/20追記
Replitのサービス変更によりサーバーを無料で立てることができなくなりました。

# 何をするか
VRChatのワールドからChatGPTを呼び出せるUdonギミックを作ります。
対象レベルは簡単なギミックが作れる人を想定しています。
U#のコードはコピペでいいので書けなくても大丈夫です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">押すとChatGPT君が面白いことを言ってくれるスイッチ <a href="https://t.co/MdUN5R8XxD">pic.twitter.com/MdUN5R8XxD</a></p>&mdash; なまのなまこ (@Iiron_Lung) <a href="https://twitter.com/Iiron_Lung/status/1666366911276515329?ref_src=twsrc%5Etfw">June 7, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 仕組み
## 利用するもの 
- U#
- Node.js
- Replit
- ChatGPT API

## 構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/54489361-8fe8-c308-3583-4adc9d3b684b.png)

Node.jsを使ってAPIサーバーを構築し、UdonのVRCStringDownloaderでサーバーにアクセス
サーバーからChatGPT APIを呼んで結果をVRChatに返しています。
今回はReplitというサービスを使ってサーバーを立てます。

https://qiita.com/shun-shobon/items/63714f9ae802251c90bd

# 気をつけること
### 利用料金
ChatGPT APIは従量課金制です。ワールドをpublic化する時は使いすぎに気をつけてください。Usageからリミットをかけておくことをおすすめします。
ちなみに初回登録時に$5程度のお試し枠が配られ、その後は支払い情報登録が必要です。

### API Keyの取扱い
Replitは無課金だとソースコードが自動で全て公開されます。
API Keyなどの秘匿情報はソースコード内に絶対に書かずSecretから呼び出すようにしましょう。

### Allow Untrusted URL設定
VRChatの外部通信はデフォルトではホワイトリストにある特定のサービスに限られるため、今回のギミックを動かすには設定のAllowUntrustedURLにチェックを入れる必要があります。

# 手順
実装していきます。
1. Replitでサーバーを立てる
1. Udonでサーバーにアクセスする
1. ChatGPT APIを使えるようにする

## Replitでサーバーを立てる
[Replit](https://replit.com/) でアカウントを作成し、Create a ReplボタンからNode.jsの環境を作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/b4f6e927-10e8-dd5d-5bca-06dec82b9f2e.png)

examplesをクリックしServer(Express)を選択すると、サンプルのサーバー用コードが自動で設定されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/38d4b786-c45e-3859-00bb-e7ff8941ef14.png)

画面上部の緑色の「Run」ボタンを押すとサーバーが起動します。
(初回は必要なインストールがあるため少し時間がかかります。)

右上にWebViewが追加され、「Hello Express app!」と表示されれば起動済みです。
上部の窓に表示されているのがサーバーのURLになり、これでサーバーへアクセスができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/9c05b252-7b36-fbc9-5016-9783095fe259.png)

## Udonでサーバーにアクセスする
VRCStringDownloaderを使ってサーバーから文字列を読み込みます。
以下のサンプルコードをつかってU#を作成してください。

```csharp:StringLoader.cs
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UnityEngine.UI;
using VRC.SDK3.StringLoading;
using VRC.Udon.Common.Interfaces;

public class StringLoader : UdonSharpBehaviour
{
    [SerializeField] private VRCUrl _url;
    [SerializeField] private Text _text;

    public override void Interact()
    {
        VRCStringDownloader.LoadUrl(_url, (IUdonEventReceiver)this);
        _text.text = "Loading...";
    }
    public override void OnStringLoadSuccess(IVRCStringDownload result)
    {
        _text.text = result.Result;
    }
    public override void OnStringLoadError(IVRCStringDownload result)
    {
        _text.text = "Error.";
        Debug.Log(result.Error);
    }
}
```
キューブにセットし、URLには先ほど立てたサーバーのURLを設定します。
TextはuGUIのやつをよしなに設定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/beebdbbf-ddbb-c8ac-a3b7-5579c1a5405e.png)

VRCでインタラクトして、Textに「Hello Express app!」が表示されたら成功です。
表示されない場合はAllow Untrusted URL設定を見直してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/33fc7050-d6cf-803c-ac62-c2d363506946.png)


## ChatGPT APIを使えるようにする
OpenAIアカウントを登録し、ChatGPTのAPIキーを取得してください。
やり方は調べればたくさん出てくると思います。
APIキーは他人に教えないようにしましょう。

https://qiita.com/kotattsu3/items/b936a65d173a5d39dad0

Replitに戻ります。
左下のToolsからSecretsを選択し、先ほど取得したAPIキーをOPENAI_API_KEYという名前で設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/9751cce6-bb26-5206-e666-e6f3f50ce0a8.png)

左下のToolsからPackagesを選択し、「openai」を検索してインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/4ffeb036-de96-0f05-3270-ebf5fd5fafbd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/153fc572-44a5-fe2e-cd68-a23d498e69c4.png)


サンプルのサーバー用コードをChatGPT用に書き換えます。
```javascript:index.js
const express = require('express');
const app = express();

const { Configuration, OpenAIApi } = require("openai");
const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);


app.get('/', (req, res) => {
  res.send('Hello Express app!');
});

app.get('/OmoshiroikotoItte', async (req, res) => {
  const systemRole = "あなたは超一流コメディアンとしてふるまってください。";
  const message = "何か面白いことを言ってください。";
  const completion = await openai.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [{role: "system", content: systemRole},{role: "user", content: message}],
  });
  res.send(completion.data.choices[0].message.content);
});

app.listen(3000, () => {
  console.log('server started');
});
```
Unity側のURLも末尾に/OmoshiroikotoItteを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/825901de-2a83-756e-42cf-fb0c06d21b65.png)

動作確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495102/c25fbb06-2e28-597a-bac0-9bfea307519a.png)

# 最後に
VRCStringDownloaderでサーバーにアクセスすることでUdonの制限を超えて遊ぶことができます。
今回使ったJavaScript向けにはChatGPTの他にもDiscord.jsやTwitterAPI、非公式ですがVRChatAPIのようなものも公開されています。
受信したテキストはそのまま表示するだけでなくUdonでさらにほかのギミックに接続したりといった使い方も可能なので、いろいろなギミックを作ってみてください。

https://discord.js.org/#/ 

https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/overview 

https://vrchatapi.github.io/

### 作例
いろいろなサービスを組み合わせてこんなのも作れます

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ChatGPTで挨拶してくれるやつ作った<br>日時、天気、Discordの投稿とか使ってワールド入るたびに毎回違う挨拶してくれる🙌 <a href="https://t.co/59I8TT4FeY">pic.twitter.com/59I8TT4FeY</a></p>&mdash; なまのなまこ (@Iiron_Lung) <a href="https://twitter.com/Iiron_Lung/status/1665140686864392193?ref_src=twsrc%5Etfw">June 3, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ワールド設定をVRChat内で保存できるパネル<br>次回以降ワールドに入った時に自動でロードされる <a href="https://t.co/5MYdAb5VLn">pic.twitter.com/5MYdAb5VLn</a></p>&mdash; なまのなまこ (@Iiron_Lung) <a href="https://twitter.com/Iiron_Lung/status/1666368639849885698?ref_src=twsrc%5Etfw">June 7, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# おまけ
## 双方向会話がしたい
VRChatの仕様上データを外に送るのが難しいですが一応クエリパラメータというのが使えます。

https://nodejs.keicode.com/nodejs/express-params-query.php