NTTドコモR&Dの技術ブログです。

ChatGPT x MetaQuest3で自分だけのバーチャルアシスタントを作る!

本記事はNTTドコモのアドベントカレンダー2023 15日目の記事になります。

はじめに

自己紹介

こんにちは、NTTコノキューの浅井です。 NTTコノキューは昨年ドコモから生まれたXR(VR/AR/MR技術の総称)技術を専門に事業を行う新会社です。

私はドコモ時代から今までずっとXRを使った新規事業の企画開発を行っており、 MR(MixedReality)技術を使った遠隔作業支援ソリューションNTT XR Real Supportを提供中です。

www.nttqonoq.com

また、医療向けサービスの実⽤化をめざすプロジェクト「Project the Hands」を先日発表しました。医療向けの熟練医師の技術伝承にも取り組んでいます。

prtimes.jp

上記2つについて、ご興味ある方はぜひご連絡ください!(宣伝)

今回の記事内容

ということで本題です。今回はこんなXRばかりやってる私が、今世界中を轟かせ続けているLLM/生成AIにも取り組んでみよう!ということで、

バーチャルな存在が自分だけのパートナーや、執事のようになってくれるととても夢があるなあ、そういう体験を作れたら楽しそうだなあ、と思い、

10月に発売されたMetaQuest3と、生成AIの火付け役ChatGPTを連携させて、自分だけのバーチャルアシスタントを作ってみました!!!

  • MetaQuestの開発方法
  • OpenAI APIの(Unityでの)使い方
  • それらの連携方法

をまとめます。ぜひ皆さんも自分だけのバーチャルアシスタントを作ってみてください🙌

作ったもの

Quest3 x ChtaGPTアプリ

こんな感じで、Quest3のパススルー機能により私の部屋の中にバーチャルキャラが出てきて、会話できるようになりました! タイムアタックだったので、所要時間は10時間くらいです。

前提知識:MetaQuest3とは?ChatGPTとは?

MetaQuest3とは?

Meta社が2023/11から販売開始した最新型のHMD(ヘッドマウントディスプレイ)です。Oculus時代からVR業界を牽引してきたHMDですが、今回は堂々と「MR」を推しており、カラーパススルーによりカメラの映像をHMDに表示することで、装着したまま現実世界と仮想世界のインタラクションが体験出来るようになっています。

ちなみに弊社(コノキュー)で法人向けに提供していますので、ご興味ある方はぜひご連絡ください!(宣伝)

www.moguravr.com

ChatGPTとは?

OpenAI社が昨年末から提供開始しているLLMを用いた人工知能チャットボットです。今までのAIのイメージ(すごいけどまだ実用出来るのは局所的)から、なんでも出来るAGIへの期待が寄せられるキッカケとなったもので、様々な領域での活用が実現・検討されています。

ちなみに弊社(ドコモ)ではLLM付加価値基盤を開発し、推進しています。

ステップのまとめ

ステップ①〜④の4章構成になります。

①作りたい全体像を固める→②Quest開発→③ChatGPT開発→④バーチャルキャラを追加して、完成!

という流れです。

ステップ①:作りたい全体像を固める

ということでスタートします。

全体構成

まず全体の構成ですが、イメージとしては

QuestGptImage

こんな感じです。

  1. QuestはMeta XR SDKにて提供されるSpeechToTextのアセットがあるので、それをそのまま使います。 これで特にサーバサイドやAPIを何処かから持ってくる必要なく、Questを被った人の声をテキストに起こせます。
  2. 1.でテキスト化した情報をOpenAI APIに送ります。
  3. OpenAI APIから応答(テキスト)を受け取ったら、VR空間上にそれを表示します。

これで、バーチャルキャラと対話する仕組みが完成です! (理想はOpenAI APIのTextToSpeechとか使って3. の応答(テキスト)を音声に変換したかったですが、それはまたの機会に試そうと思います)

環境情報

  • PC
    • MacBookPro(Apple M2 Max)
    • MacOS Ventura(Version 13.6)
  • Unity
    • 2021.3.28f1
    • Meta XR SDK(Version 59.0.0)
  • その他
    • Quest3
    • ChatGPT(gpt-4)

ステップ②:Quest開発(QuestのVoiceSDKを使ってSpeechToText)

MetaQuest向けのMeta XR SDKから、Meta - Voice SDK - Immersive Voice Commandsを使いました。このAssetのSampleSceneから「Live Understanding」を使っています。 Activateボタンを押すとマイクが起動し、音声認識が開始されてテキストを表示してくれる、という仕組みです。 以降のスクリプト等はSampleScene「Live Understanding」にAssetやComponetを追加しています。

❗️Meta - Voice SDK関連はQuest向けの環境構築はSampleScene上ではされていないので、自分でセットアップする必要があります。 私の場合は環境構築に加えて、コントローラでActivateボタンを押して音声認識を開始したかったのでその処理を追加しています。 また、Meta - Voice SDKの場合英語音声のみに対応(のよう)なので、今回は入力は英語にしてます。(ChatGPTなら日本語でレス返せるでしょう、という甘い考えです)

ステップ③:ChatGPT開発(OpenAI APIへテキストを送り、応答を受け取って表示)

OpenAI社が提供しているAPIからTextGenerationを選択します。APIエンドポイントは「https://api.openai.com/v1/chat/completions」を使用しました。

まずステップ①のテキスト情報の受け取り→APIへのrequestですが、SampleScene「Live Understanding」のTranscription LabelコンポーネントからTextを取得して、そのTextをそのままAPIに投げています。

欲しいのは↓のText部分なので

gpt-cap-text
↓のスクリプトを適当なオブジェクトにアタッチして、

ChatGPTCall.cs

using System;
using System.Collections;
using System.Collections.Generic;
using AAA.OpenAI;
using UnityEngine;
using UnityEngine.UI;

public class ChatGPTCall : MonoBehaviour
{

    public string openAIApiKey;
    public Text requestToGpt2;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        var chatGPTConnection = new ChatGPTConnection(openAIApiKey);
        
        if (OVRInput.GetDown(OVRInput.RawButton.RIndexTrigger))
        {
            Debug.Log("右人差し指トリガーを押した");
            chatGPTConnection.RequestAsync(requestToGpt2.text.ToString());
        }

    }
}

RequestToGpt2に紐付けています。(1は無視してください)

unity-cap-gpt

↑のAPIKeyは、OpenAIのAPI Keysから発行し、それを記載しています。

❗️無料ユーザーの場合は、クレジットカード登録を行ってからじゃないと使えないので、ご注意ください。

openaiapie-keys

次に、APIを叩いてResponseを受け取ります。 プロンプト・APIのVersionは↓の通りです。

model = "gpt-4"
role = "system", content = "あなたは有名な人語を話すハムスターです。どんな短い言葉でも語尾は「なのだ」です。口調は子供のように明るくしてください。"

今回は可愛い相棒が欲しいなと思ったので、心の中に出てきたハムスターのキャラを想像しながら設定しました🐹

ChatGPTConnection.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;

[Serializable]
public class ChatGPTMessageModel
{
    public string role;
    public string content;
}

//ChatGPT APIにRequestを送るためのJSON用クラス
[Serializable]
public class ChatGPTCompletionRequestModel
{
    public string model;
    public List<ChatGPTMessageModel> messages;
}

//ChatGPT APIからのResponseを受け取るためのクラス
[System.Serializable]
public class ChatGPTResponseModel
{
    public string id;
    public string @object;
    public int created;
    public Choice[] choices;
    public Usage usage;

    [System.Serializable]
    public class Choice
    {
        public int index;
        public ChatGPTMessageModel message;
        public string finish_reason;
    }

    [System.Serializable]
    public class Usage
    {
        public int prompt_tokens;
        public int completion_tokens;
        public int total_tokens;
    }
}

namespace AAA.OpenAI
{
    public class ChatGPTConnection : MonoBehaviour
    {
        //[SerializeField]
        //private TMPro.TMP_Text testText;

        private readonly string _apiKey;
        //会話履歴を保持するリスト
        private readonly List<ChatGPTMessageModel> _messageList = new();
        static public string responseFromGpt = "Now Loading...";

        void Start()
        {
            
        }

        public ChatGPTConnection(string apiKey)
        {
            _apiKey = apiKey;
            _messageList.Add(
                new ChatGPTMessageModel() { role = "system", content = "あなたは有名な人語を話すハムスターです。どんな短い言葉でも語尾は「なのだ」です。口調は子供のように明るくしてください。" });
        }

        public async UniTask<ChatGPTResponseModel> RequestAsync(string userMessage)
        {
            //文章生成AIのAPIのエンドポイントを設定
            var apiUrl = "https://api.openai.com/v1/chat/completions";

            _messageList.Add(new ChatGPTMessageModel { role = "user", content = userMessage });

            //OpenAIのAPIリクエストに必要なヘッダー情報を設定
            var headers = new Dictionary<string, string>
            {
                {"Authorization", "Bearer " + _apiKey},
                {"Content-type", "application/json"},
                {"X-Slack-No-Retry", "1"}
            };

            //文章生成で利用するモデルやトークン上限、プロンプトをオプションに設定
            var options = new ChatGPTCompletionRequestModel()
            {
                model = "gpt-4",
                messages = _messageList
            };
            var jsonOptions = JsonUtility.ToJson(options);

            Debug.Log("自分:" + userMessage);

            //OpenAIの文章生成(Completion)にAPIリクエストを送り、結果を変数に格納
            using var request = new UnityWebRequest(apiUrl, "POST")
            {
                uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(jsonOptions)),
                downloadHandler = new DownloadHandlerBuffer()
            };

            foreach (var header in headers)
            {
                request.SetRequestHeader(header.Key, header.Value);
            }

            await request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError ||
                request.result == UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError(request.error);
                throw new Exception();
            }
            else
            {
                var responseString = request.downloadHandler.text;
                var responseObject = JsonUtility.FromJson<ChatGPTResponseModel>(responseString);
                Debug.Log("ChatGPT太郎:" + responseObject.choices[0].message.content);
                _messageList.Add(responseObject.choices[0].message);
                responseFromGpt = responseObject.choices[0].message.content;
                return responseObject;
            }
        }
    }
}

いよいよステップ③のラストです。受け取った応答をVR空間上に表示します。 表示用のCanvasオブジェクトを配置し、そこに表示します。

❗️Unityのデフォルトだと日本語フォントに対応していないので、TMPのインポートと日本語フォントのインポートを行います。(詳細は参考情報より)

以下のようにCanvasにコンポーネントをアタッチします。

response-canvas

ChatGPTConnection.responseFromGptは↑のスクリプトにて設定しています。publicにして引用出来るようにしています。

ChatGPT2Canvas.cs

using System.Collections;
using System.Collections.Generic;
using AAA.OpenAI;
using TMPro;
using UnityEngine;

public class ChatGPT2Canvas : MonoBehaviour
{

    [SerializeField]
    private TMPro.TMP_Text testText;
    string response = ChatGPTConnection.responseFromGpt;
    string responseMatch;

    // Start is called before the first frame update
    void Start()
    {
        testText.SetText(response);
    }

    // Update is called once per frame
    void Update()
    {
        testText.SetText(ChatGPTConnection.responseFromGpt);
    }
}

ステップ④:バーチャルキャラを追加して、完成!

最後はバーチャルキャラとの対話らしさとして、3Dモデルをインポートします。 🐹太郎っぽい無料アセットを探したら全然無かったので、結果🐀剣士になりました。 assetstore.unity.com

あとはキャラとテキストの配置をそれっぽくして、完成です!

unity-final

ということで最初のGIFに戻りますが、こんな感じで「Activateボタン」をクリック→テキスト送信→OpenAI APIからレスポンス→テキスト表示、という流れになります。

gpt-comunication

まとめ

参考情報のおかげ…ですが、かなり簡単にQuest3上でChatGPTと対話が出来ました。開発中APIをUpdate関数で叩きまくってしまったらgpt-4が一時的に停止してしまったので、その間だけgpt-3.5で開発を進めましたが、応答のクオリティが明らかに異なっていて、進化の速さの恐ろしさを実感しました。

XRはバーチャル世界と現実世界を如何にリンクさせるか?が個人的には重要だと考えているので、バーチャルキャラとの対話が出来るのは本当に夢が広がります。

引き続き色々試していきたいと思います!

余談ですが、今回の開発で使ったAPI使用量はトータル2$くらいでした。↑の通りAPIを無闇に叩きすぎてめちゃくちゃかかってしまったので、今後は気をつけたいなと思います。(当然ですが開発中にもお金かかるの大変ですね〜)

api-fee

(最後の宣伝)NTTコノキューでは、こういった技術記事などをまとめる自社サイトを開設しました!XRに興味がある・ない方もぜひご覧ください。

qompass.nttqonoq.com

参考情報

以下のドキュメント、ブログ等を参考にさせていただきました。ありがとうございました!

Tutorial - Create Your First VR App on Meta Quest Headset | Oculus Developers

Unityでボタンを設置して、コントローラーからのレーザーでクリックする方法 [Meta Quest Pro] - Dyuichi Blog

ChatGPT APIをUnityから動かす。|ねぎぽよし

Unity + Meta Quest開発メモ - フレームシンセシス

[Unity] Text Mesh Proで日本語を表示する方法