💬

OpenAIのAPIとUnityで音声会話チャットボットを作る ~ チャット機能編 ~

2023/12/04に公開

はじめに

この記事は Panda株式会社 Advent Calendar 2023 4日目の記事です。
Panda株式会社として初めてのAdvent Calendarとなります。
Panda株式会社は東京大学松尾研究室・香川高専発のスタートアップで、AR技術とAI技術を駆使したシステム開発と研究に取り組んでいます。
このアドベントカレンダーでは、スタートアップとしての知見、AI・AR技術、バックエンドなど、さまざまな領域の記事を公開していきます。

自己紹介

皆さんこんにちは、Panda株式会社代表取締役の田貝奈央です。AR技術が好きで、X(旧:Twitter)でARに関する投稿の巡回をしています。また、私はコーヒー好きで、この記事を執筆するときもPCの隣にはコーヒーを置いています。執筆活動を支援してくれたコーヒーに感謝を。

背景

2023年に4回ほどにわけて「ChatGPT×Unityハンズオン」を小規模に開催しました。2024年にはこのイベントを広く公開しようと考えています。そのため、このタイミングでハンズオンの内容を文書化することに決めました。

2022年11月にOpenAIのChatGPTが公開されてからというもの、多くの人々がチャットボットへの関心を高めています。そんな中、「バーチャル空間でチャットボットを実装してみたいけど、やり方がわからない」という相談をUnity初心者の方から受けることがありました。世の中にはチャットボットを実現するための機能が多く存在しますが、その使用方法や実装手順が明確になっていないため、Unity初心者がチャットボットを作るのは大変です。そこで、「ChatGPT×Unityハンズオン」はUnity初心者でもチャットボットを作れるようなスキルアップイベントとして開催されました。

このハンズオンは、Unityを使用してチャットボットを構築する手順を分かりやすく、実践的に学べる4回のセッションで構成されています。これにより、参加者は音声でのやり取りが可能なチャットボットをUnityで開発することができるようになります。ユーザーが音声で質問すると、「ユニティちゃん」が音声で応答するチャットボットを実装することができます。

本記事では、ハンズオンで取り扱った内容をチュートリアル形式で紹介します。実際に1時間かけて行ったハンズオンを詳細に記事化しているため、内容が豊富になっておりますが、最後までお付き合いいただけると幸いです。

環境

  • Unity 2021.3.9f1以降

システム構成

このシステムでは、ユーザーが音声で質問すると、Unity Technologies Japanが開発者のために提供しているオリジナルキャラクター「ユニティちゃん」が音声で応答するチャットボットを作成します。ユーザーの音声での入力はマイクを使用して録音され、その音声データはテキストに書き起こされます。次に、この書き起こされたテキストをOpenAIのchat completions APIに送信し、ユーザーの質問に対する適切な応答を生成します。chat completions APIに送信するテキストには、ユニティちゃんの喋り方を指定するプロンプトと感情分析を含めることで、返却された応答から「ユニティちゃん」の表情を操作します。さらに、生成された応答はGoogle Cloud Platform(GCP)のText-to-Speechサービスを使用して音声データに変換されます。そして、この音声データをUnity内で再生することで、アバターが音声で応答することを実現します。
それぞれの機能を実現するために本記事を含めて以下の4つの記事に分けて実装について説明します。アドベントカレンダーで公開される記事になっているため、投稿日現在(2023/12/04)、アクセスできない記事が含まれています。

  1. (本記事)OpenAIのAPIとUnityで音声会話チャットボットを作る ~ チャット機能編 ~
  2. OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声入力編 ~
  3. OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声生成編 ~
  4. OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 喜怒哀楽編 ~

チャット用のUIを準備する

チャット機能を実現するには、ユーザがインタラクションできるUIが必要です。このセクションでは、ユーザがメッセージを入力、送信し、応答を受け取れるようなUIを構築する方法について説明します。チャット機能に必要となる主なUI要素は、入力用のテキストボックス、送信ボタン、応答が表示されるメッセージエリアの3つです。

1. テキストボックスを作成

ユーザがチャットボットにメッセージを入力するためのテキストボックスを準備します。Unityでユーザがテキストを入力できるUI要素を作る際は「Input Field」を使います。
ヒエラルキーを右クリックし、UI → Legacy → Input Field の順で選択し、Input Fieldをシーンに追加します。

Input Fieldを追加する
追加後、Input Fieldの位置を調整し、Canvas要素の下部に移動します。
Input Fieldを配置

2. 送信ボタンを作成

ユーザが入力したメッセージを送信できるようにするボタンを作成します。Unityでクリックイベントを発火させるUI要素を作る際は「Button」を使います。
ヒエラルキーを右クリックし、UI → Legacy → Buttonの順で選択し、Buttonをシーンに追加します。
Buttonを追加する
追加後、Buttonの位置を調整し、先ほど配置したInput Fieldの下に配置します。
Buttonを配置する
このボタンがテキストを送信するためのボタンであることがわかりやすいようにラベルを変更します。ヒエラルキーからButtonの子要素のText要素を選択し、インスペクターのTextを書き換えます。
Buttonのラベルを変更する
これにより、Buttonのラベルを変更することができます。
Buttonのラベル変更後

3. 応答が表示されるテキストエリアを作成

LINEのように送信者と応答者のコメントがわかりやすいUIを作成します。本プロジェクトでは、各メッセージは吹き出し形式で表示されます。また、送信者と応答者のコメントがわかりやすいようにメッセージの色を区別します。送信者のメッセージは右側に、応答者のメッセージは左側に表示します。

まず、メッセージの履歴が多くなってもスクロールして閲覧できるように、Scroll Viewを使用してテキストボックスを表示する領域を作成します。
ヒエラルキーを右クリックし、UI → Scroll Viewの順で選択し、Scroll Viewをシーンに追加します。Scroll Viewのサイズや位置をインスペクターから変更します。
Scroll Viewを配置する
Scroll ViewViewportScrollbar HorizontalScrollbar Verticalの三つの要素で構成されています。ViewportScroll View内の表示領域を定義します。Viewportの子要素にあるオブジェクトがViewportの領域をはみ出ると、Unity上では表示されなくなります。Scrollbar Horizontalは水平方向のスクロールバー、Scrollbar Verticalは垂直方向のスクロールバーを表しています。
Scroll Viewの構成
このプロジェクトでは、LINEのように縦方向にメッセージの履歴を表示するために、垂直方向のスクロールバーであるScrollbar Verticalのみを使用します。そのため、Scrollbar Horizontalを削除します。
Scrollbar Horizontalを削除したScroll Viewの構成

次にScroll ViewのインスペクターでScroll RectHorizontalのチェックマークを外し、Horizontal Scrollbarで選択している要素をNoneに設定します。これにより、Scroll Viewは水平方向のスクロールバーを使用しないように設定されます。
Scroll Viewの設定

Viewportの中にあるContentの子要素にメッセージボックスを並べることで送信者と応答者のメッセージの履歴を表示することができます。LINEのように下の方が最新の投稿で、上に行くほど過去の投稿になるようにするために、Contentの子要素の並べ方を変更します。Contentのインスペクターを開きAdd ComponentをクリックしVertical Layout Groupを追加します。
Vertical Layout Groupを追加する
メッセージが追加されるたびにContentのサイズを広げる必要があるため、UI要素のサイズを子要素のサイズに合わせて自動で調整することができるContentSizeFitterを追加します。Contentのインスペクターを開きAdd ComponentをクリックしContentSizeFitterを追加します。
ContentSizeFitterを追加する
下から積み上げるように表示するために、ContentRect Transformのアンカーを変更します。アンカーとは子要素のUI要素を配置するための基準となる場所のことです。インスペクターのRect Transformの左の四角をクリックすることでアンカーを変更することができます。
Contentのインスペクター
アンカーの設定
本プロジェクトでは、縦方向にサイズを調整する必要があるためVertical FitというプロパティをPreferred Sizeにします。これにより、Contentの垂直方向のサイズを子要素の推奨サイズに合わせることができます。
Vertical Fitを設定する

そして、送信者と応答者のメッセージを表示する吹き出しをコンポーネントとして作成します。送信者と投稿者のメッセージを区別するために、吹き出しの色を変更する必要があるため、Panelを作成します。
ヒエラルキーを右クリックし、UI → Panelの順に選択し、Panelをシーンに追加します。
メッセージを表示するためにText要素をPanelの子要素として追加します。
先ほど作成したPanelを選択した状態でヒエラルキーを右クリックし、UI → Legacy → Textを選択し、Textを追加します。
Panelの子要素として追加されたButton
PanelImageの色を緑色に変更します。
PanelのImageを緑色にする
メッセージを表示するテキストが複数行になった時にメッセージを表示する吹き出しを縦方向に拡大するために、TextにUI要素のサイズを子要素のサイズに合わせて自動で調整することができるContentSizeFitterを追加します。Textのインスペクターを開きAdd ComponentをクリックしContentSizeFitterを追加します。縦方向にサイズを調整する必要があるためVertical FitというプロパティをPreferred Sizeにします。
TextにContentSizeFitterを追加する
Textコンポーネントが複数行に渡る場合、親要素であるPanelのサイズを拡張する必要があります。これを効率的に行うために、UI要素のサイズをその子要素のサイズに合わせて自動的に調整するContentSizeFitterコンポーネントの使用が推奨されます。Panelのサイズを動的に管理するには、まずPanelのインスペクターを開きます。その後、Add Componentボタンをクリックし、ドロップダウンからContentSizeFitterを選択して追加します。

さらに、メッセージの背景サイズをTextの大きさに適応させるためには、Vertical Layout Groupコンポーネントの追加が効果的です。Vertical Layout Groupを追加することで、子要素を垂直方向に整列させ、適切なPaddingを設定することが可能になります。これは、Textコンポーネントのインスペクターを開き、Add ComponentボタンをクリックしてからVertical Layout Groupを選択し追加することで実現できます。
ContentSizeFitterとVertical Layout Groupを追加する

このオブジェクトをプレハブ化することで、後ほどメッセージの送受信をするときにスクリプトからオブジェクトを作成できるようにします。

OpenAI chat completions APIとの連携

ユーザが入力したメッセージに対して適切な返答をするチャットボットを実現するために、OpenAIが提供するGPT-3(Generative Pre-trained Transformer 3)などの大規模言語モデルを利用して、与えられたメッセージに対する応答を生成します。このモデルは、自然言語・コードなどを理解できるようにトレーニングされており、入力されたテキスト(プロンプトと呼ばれます)に基づいて適切なテキストを出力します。プロンプトを変更することで大規模言語モデルへの指示をカスタマイズすることが可能です。

今回は「gpt-3.5-turbo」を利用します。このモデルを利用するには、入力とAPIキーを含むリクエストをOpenAIのAPIに送信し、モデルからの出力を受け取る必要があります。Unityからモデルを利用するには、UnityWebRequestを使ってAPIにリクエストを送信します。

1. OpenAIのAPIキーを作成

まず、OpenAIのウェブサイトにアクセスし、アカウントを作成します。アカウントがない場合は新規登録が必要です。登録したアカウントでログインできたら、OpenAIの開発者クイックスタートのアカウントの設定を参考にOpenAIのAPIキーを作成します。このキーはAPIリクエストを送信する際に必要になります。

2. APIに対するリクエストの送受信

UnityWebRequestを用いて、APIにリクエストを送るコードを作成します。リクエストにはユーザの入力メッセージとプロンプト、APIキーを含める必要があります。
APIを呼び出すために必要なリクエストと返却される形式についてはOpenAIのchat completions APIのドキュメントから確認することができます。
https://api.openai.com/v1/chat/completions 」にメッセージを入れたJSONをPostすることでリクエストを送ることができます。
UnityWebRequestでPostするにはPostをリクエストを作成する必要があります。
以下は、UnityからAPIリクエストを送信・受信するためのスクリプトです。このサイトを参考に送受信に必要なクラスを構築します。

ChatGPTConnection.csの全体
ChatGPTConnection.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

namespace OpenAIGPT{
    // OpenAI GPTとの接続を管理するクラス
    [Serializable]
    public class ChatGPTConnection
    {
        // OpenAI APIキー
        private readonly string _apiKey;
        // メッセージ履歴を保持するリスト
        private readonly List<ChatGPTMessageModel> _messageList = new();
        
        // コンストラクタ:APIキーを設定し、初期メッセージを追加
        public ChatGPTConnection(string apikey)
        {
            _apiKey = apikey;
            _messageList.Add(
                new ChatGPTMessageModel() {role = "system", content = "あなたは猫です。猫語で対話をしてください。"});
        }

        // メッセージモデル
        [Serializable]
        public class ChatGPTMessageModel
        {
            // メッセージの送信者の役割(ユーザーまたはシステム)
            public string role;
            // メッセージの内容
            public string content;
        }

        // ユーザーメッセージに対するOpenAI GPT APIリクエストを送信する非同期メソッド
        public async UniTask<ChatGPTResponseModel> RequestAsync(string userMessage)
        {
            // OpenAI GPT 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"}
            };

            // APIリクエストのオプション(モデルとメッセージリスト)
            var options = new ChatGPTCompletionRequestModel()
            {
                model = "gpt-3.5-turbo",
                messages = _messageList
            };
            var jsonOptions = JsonUtility.ToJson(options);

            // UnityWebRequestを使用してOpenAI GPT 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);
                _messageList.Add(responseObject.choices[0].message);
                return responseObject;
            }
        }

        // OpenAI GPT APIリクエストのJSONモデル
        [Serializable]
        public class ChatGPTCompletionRequestModel
        {
            public string model; // 使用するモデル名
            public List<ChatGPTMessageModel> messages; // メッセージリスト
        }

        // OpenAI chat completions APIの応答モデル
        [System.Serializable]
        public class ChatGPTResponseModel
        {
            public string id; // 応答の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; // 合計使用トークン数
            }
        }
    }
}

全体のコードを元に、OpenAI chat completions APIにリクエストを送る方法を紹介します。

UnityでJSON形式のPOSTリクエストを送るには、JSONに変換したいクラスを定義し、そのクラスのインスタンスをJsonUtility.ToJsonメソッドを利用してJSON形式のオブジェクトに変換する必要があります。
以下の部分で、OpenAI chat completions APIにリクエストを行うためのJSONモデルを定義しています。OpenAIのchat completions API referenceのリクエストボディの欄を確認すると、以下の二つが必須項目であることがわかります。

  • messages array:
    • これまでの会話を構成する一連のメッセージのリスト
    • 例えば、ユーザからの質問とそれに対するボットの応答などを記述する
    • システムプロンプトを含めることができる
  • model string:
    • 使用するモデルのID
    • 利用するモデルを指定する
      この二つを持つクラスをChatGPTCompletionRequestModelと定義し、[Serializable]属性をつけます。これにより、APIサーバにポストリクエストをJSON形式で送るために、クラスのインスタンスをJsonUtility.ToJsonを用いてシリアライゼーションできるようになります。
ChatGPTConnection.csの抜粋
// OpenAI chat completions APIリクエストのJSONモデル
[Serializable]
public class ChatGPTCompletionRequestModel
{
    public string model; // 使用するモデル名
    public List<ChatGPTMessageModel> messages; // メッセージリスト
}

messagesで指定できるメッセージオブジェクトの属性は複数あります。今回、チャットボットを実現する上で使うのは次の二つです。

  • システムメッセージ
    • プロンプトを設定できます
    • content string or null: システムメッセージの内容
    • role string: メッセージ送信者の役割「system」
  • ユーザーメッセージ
    • ユーザーが送信したメッセージを記入できる
    • content string or null: ユーザメッセージの内容
    • role string: メッセージの送信者の役割「user」
      どちらもcontentroleが必要なため、以下のようなメッセージモデルを表すクラスChatGPTMessageModelを定義します。送信時にJSONにシリアライズする必要があるため、[Serializable]属性をつけます。
ChatGPTConnection.csの抜粋
// メッセージモデル
[Serializable]
public class ChatGPTMessageModel
{
    // メッセージの送信者の役割(ユーザーまたはシステム)
    public string role;
    // メッセージの内容
    public string content;
}

以下の部分でメッセージモデルのリストを作成し、APIの送信するメッセージの履歴を保持するリストを作成します。

ChatGPTConnection.csの抜粋
// メッセージ履歴を保持するリスト
private readonly List<ChatGPTMessageModel> _messageList = new();

先ほど作った_messageListに対して、プロンプトを加えます。本プロジェクトでは猫語で応答が返却されるようにしたいため、「あなたは猫です。猫語で対話してください。」というテキストをシステムメッセージに登録することで、プロンプトを指定しています。

ChatGPTConnection.csの抜粋
_messageList.Add(
	new ChatGPTMessageModel() {role = "system", content = "あなたは猫です。猫語で対話をしてください。"});

ユーザーの入力を_messageListに追加するには、以下のコードでユーザーメッセージを追加します。

ChatGPTConnection.csの抜粋
// ユーザーメッセージをリストに追加
_messageList.Add(new ChatGPTMessageModel {role = "user", content = userMessage});

このようにして作成したメッセージのリストを以下のコードでJSON形式に変換し、APIに送信できる形にします。modelにはgpt-3.5-turboを指定したリクエストモデルをインスタンス化し、JsonUtility.ToJsonでJSON形式のテキストに変換します。

ChatGPTConnection.csの抜粋
// APIリクエストのオプション(モデルとメッセージリスト)
var options = new ChatGPTCompletionRequestModel()
{
model = "gpt-3.5-turbo",
messages = _messageList
};
var jsonOptions = JsonUtility.ToJson(options);

以下にOpenAI chat completions APIにPOSTリクエストを送るためのコードを記述します。まずUnityWebRequestオブジェクトを作成し、APIのエンドポイントとHTTPメソッドであるPOSTを指定します。リクエストボディ(uploadHandlerで指定できます)には先ほど作成したjsonOptionsUnityWebRequestオブジェクトを設定します。downloadHandlerにAPIからのレスポンスを受け取るためのバッファを設定します。

ChatGPTConnection.csの抜粋
// UnityWebRequestを使用してOpenAI GPT APIにリクエストを送信
using var request = new UnityWebRequest(apiUrl, "POST")
{
	uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(jsonOptions)),
	downloadHandler = new DownloadHandlerBuffer()
};

OpenAI APIは認証にAPIキーを使用するため、すべてのAPIリクエストのヘッダーに以下のようにAPIキーを含める必要があります。

ChatGPTConnection.csの抜粋
// OpenAI APIリクエストに必要なヘッダー情報
var headers = new Dictionary<string, string>
{
{"Authorization", "Bearer " + _apiKey},
{"Content-type", "application/json"}
};
・・・
// リクエストヘッダーを設定
foreach (var header in headers)
{
request.SetRequestHeader(header.Key, header.Value);
}

ここまでで作成したリクエストを送信し、応答が返却されるまで待機します。

ChatGPTConnection.cs
// リクエストを送信し、応答を待機
await request.SendWebRequest();

リクエストが返却されたら、その結果を処理します。リクエストがうまくいかなかった場合はエラー処理し、うまくいった場合は、レスポンスをクラスに変換し、Unity側から扱えるようにします。JsonUtility.FromJson
を用いることで、JSON形式のテキストをclassオブジェクトにシリアライズすることができます。これにより得られたOpenAI chat completions APIで生成された応答をメッセージとして記録するために、_messageList.Add(responseObject.choices[0].message);を用いて会話履歴に登録します。

ChatGPTConnection.csの抜粋
// リクエストの結果を処理
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);
	_messageList.Add(responseObject.choices[0].message);
	return responseObject;
}

OpenAI chat completions APIから返却されるレスポンスは次のようになっています。

  • id string: 一意の識別子
  • object string: 今回は常にchat.completion
  • created integer: 返答が作成されたUnixタイムスタンプ
  • choices object: 生成されたメッセージの選択肢のリスト。今回は生成数を指定していないので1つだけ返却される
    • index integer: 生成されたメッセージの選択肢のインデックス
    • message object: モデルによって生成されたメッセージ
    • finish_reason string: モデルがトークンの生成を停止した理由(stop: 自然なところで止まった lenght: 最大トークン数に到達した etc)
  • usage object: 生成結果の統計情報
    • prompt_tokens integer: 生成されたメッセージのトークン数
    • completion_tokens integer: プロンプトのトークン数
    • total_tokens integer: リクエストで使用されたトークン数の合計(プロンプト+生成されたメッセージ)

この要素を持つクラスをChatGPTResponseModelとして定義します。以下のように[System.Serializable]属性を指定し、JSON形式のテキストをシリアライズできるようにします。

ChatGPTConnection.csの抜粋
// OpenAI chat completions APIの応答モデル
[System.Serializable]
public class ChatGPTResponseModel
{
    public string id; // 応答の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; // 合計使用トークン数
    }
}

OpenAI chat completions APIにリクエストの送受信を実現するスクリプトの解説は以上です。

次に、実際にチャットを送信するためのスクリプトを以下に示します。

SendChat.csの全体
SendChat.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using OpenAIGPT;
using UnityEngine.UI;

public class SendChat : MonoBehaviour
{
    // OpenAI APIキー
    [SerializeField]
    private string openAIApiKey;
    // ユーザーの入力を受け取るためのInputField
    [SerializeField]
    private InputField inputField;
    // チャットメッセージを表示するコンテンツエリア
    [SerializeField]
    private GameObject content_obj;
    // チャットメッセージのプレハブオブジェクト
    [SerializeField]
    private GameObject chat_obj;

    // 送信ボタンが押されたときに呼び出されるメソッド
    public void OnClick()
    {
        // InputFieldからテキストを取得
        var text = inputField.GetComponent<InputField>().text;
        // メッセージを送信
        sendmessage(text);
        // InputFieldをクリア
        inputField.GetComponent<InputField>().text = "";
    }

    // メッセージを送信し、応答を取得する非同期メソッド
    private async void sendmessage(string text)
    {
        // OpenAI GPTとの接続を初期化
        var chatGPTConnection = new ChatGPTConnection(openAIApiKey);
	
        // ユーザーのメッセージを表示するオブジェクトを生成
        var sendObj = Instantiate(chat_obj, this.transform.position, Quaternion.identity);
        sendObj.GetComponent<Image>().color = new Color(0.6f, 1.0f, 0.1f, 0.3f);
        GameObject Child = sendObj.transform.GetChild(0).gameObject;
        Child.GetComponent<Text>().text = text;
        // 生成したオブジェクトをコンテンツエリアの子要素として追加
        sendObj.transform.SetParent(content_obj.transform, false);

        // OpenAI GPTにリクエストを送信し、応答を待つ
        var response = await chatGPTConnection.RequestAsync(text);
        // 応答があれば処理を行う
        if (response.choices != null && response.choices.Length > 0)
        {
            var choice = response.choices[0];
            Debug.Log("ChatGPT Response: " + choice.message.content);
            // GPTの応答を表示するオブジェクトを生成
            var responseObj = Instantiate(chat_obj, this.transform.position, Quaternion.identity);
            responseObj.GetComponent<Image>().color = new Color(1.0f, 1.0f, 1.0f, 0.3f);
            GameObject Child_responce = responseObj.transform.GetChild(0).gameObject;
            Child_responce.GetComponent<Text>().text = choice.message.content;
            // 応答オブジェクトをコンテンツエリアの子要素として追加
            responseObj.transform.SetParent(content_obj.transform, false);
        }
    }
}

SendChat.csスクリプトを記述し、送信ボタンのオブジェクトにアタッチします。
インスペクターからOpenAIApiKeyに先ほど取得したOpenAIのAPIキーを記述し、InputField、Content_obj、Chat_objにそれぞれのオブジェクトをアタッチします。
SendChat.csにアタッチする

3. メッセージをAPIに送信する

以下は送信ボタンが押された時に入力されているInputFieldのテキスト内容をOpenAIのAPIに送信するスクリプトです。送信後にInputFieldを空にします。

SendChat.csの抜粋
// 送信ボタンが押されたときに呼び出されるメソッド
public void OnClick()
{
// InputFieldからテキストを取得
var text = inputField.GetComponent<InputField>().text;
// メッセージを送信
sendmessage(text);
// InputFieldをクリア
inputField.GetComponent<InputField>().text = "";
}

このスクリプトをButtonOnClick()イベントにアタッチすることで、ボタンのクリックイベントに対応させます。これにより、ボタンがクリックされたときに指定した処理が実行されます。
スクリプトは、InputFieldコンポーネントからユーザが入力したテキストを取得し、sendmessageメソッドを通じてそのテキストをOpenAI APIに送信します。メッセージの送信後、InputFieldのテキストをクリアすることで、ユーザーは新しいメッセージを入力できるようになります。

4. APIリクエストの送信と応答の待機

以下は、OpenAIのAPIにリクエストを送信し応答を待つコードです。chatGPTConnectionオブジェクトのRequestAsyncメソッドを呼び出して、ユーザーからの入力されたテキストをOpenAI GPT-3 APIに送信します。awaitキーワードは非同期処理を示し、APIからの応答を待ちます。

SendChat.csの抜粋
// OpenAI GPTにリクエストを送信し、応答を待つ
var response = await chatGPTConnection.RequestAsync(text);

5. 応答の確認と処理

以下は、APIからの応答がnullでなく、少なくとも一つの選択肢が含まれているかを確認します。条件を満たす場合、最初の応答選択肢(response.choices[0])を取得し、その内容をデバッグログに出力します。

SendChat.csの抜粋
// 応答があれば処理を行う
if (response.choices != null && response.choices.Length > 0)
{
    var choice = response.choices[0];
    Debug.Log("ChatGPT Response: " + choice.message.content);
    ...
}

6. 応答を表示する

APIからの応答を受け取ったら、その内容(生成されたテキスト)を応答としてメッセージに表示します。これによりユーザは自身が送信したメッセージに対して応答が来るようなチャットボットを実現することができます。以下は、応答を表示するためにUnityのシーン内に新しいオブジェクトを生成し配置するコードです。

まず、応答を表示するための新しいオブジェクト(responseObj)を生成し配置します。chat_objプレハブからresponseObjをインスタンス化する際に現在のオブジェクトの位置や回転を引数として与えることで、現在のオブジェクトの位置に配置することができます。

SendChat.csの抜粋
// GPTの応答を表示するオブジェクトを生成
var responseObj = Instantiate(chat_obj, this.transform.position, Quaternion.identity);

次に、responseObjの色を灰色に指定し、LINEのような見た目の応答表示を実現します。

SendChat.csの抜粋
responseObj.GetComponent<Image>().color = new Color(1.0f, 1.0f, 1.0f, 0.3f);

そして、応答テキストをresponseObjの子オブジェクトにあるTextコンポーネントに設定します。これによりメッセージの応答をUnityのシーン上に表示することができます。

SendChat.csの抜粋
GameObject Child_responce = responseObj.transform.GetChild(0).gameObject;
Child_responce.GetComponent<Text>().text = choice.message.content;

最後に、content_objオブジェクトの子要素として追加します。これにより、テキストエリアの領域にメッセージを積み上げるように表示することができます。

SendChat.csの抜粋
// 応答オブジェクトをコンテンツエリアの子要素として追加
responseObj.transform.SetParent(content_obj.transform, false);

これにより、ユーザのメッセージに対する応答が視覚的に表示されるようなチャットボットを実現することができます。

おわりに

今回は「OpenAIのAPIとUnityで音声会話チャットボットを作る ~ チャット機能編 ~」というテーマでPanda株式会社 Advent Calendar 2023 4日目を執筆させていただきました。
本記事では、UnityでOpenAIのAPIを使ってテキストベースのチャットボットを実装する方法を紹介しました。
OpenAIのAPIキーを用意し、リクエストを作成し送信することで簡単にAPIと通信ができることがわかりましたね。Unityで何かしら自然な応答が必要な時などに使ってみてください。
明日の記事も私、Panda株式会社代表取締役の田貝奈央による「OpenAIのAPIとUnityで音声会話チャットボットを作る ~ 音声入力編 ~」です。この記事を読めば音声入力ができるチャットボットをUnityで作成できるようになります。明日の記事をお楽しみに!

Panda株式会社

Discussion