【ChatGPT × Unity】ChatGPT APIを使ってAIキャラクターと会話してみた

## はじめに
最近話題になっているChatGPT APIを使ってUnityからAPIを叩き、動かす方法をご紹介します。
今回は、自分だけのAIキャラクター「ヒナタちゃん」を設定して会話するところまでをやってみました！

## 注意点
Unityで開発を始める前に下記の2つの作業も行う必要があるため、そちらも合わせてご紹介します。
- OpenAIのアカウント取得
- APIキーの取得

## 参考記事
今回、ChatGPTを使用する際に参考にさせていただいた資料は下記です。
ありがとうございます！
- [公式ドキュメント](https://platform.openai.com/docs/api-reference/chat/create)
- [ChatGPT APIをUnityから動かす。](https://note.com/negipoyoc/n/n88189e590ac3)
- [ChatGPTにギルガメッシュ王の人格を与えるには？](https://note.com/fladdict/n/neff2e9d52224)

## 制作したもの
今回私が制作したものはこちらになります！

ヒナタちゃんもちゃんと存在させたかったので、
- ユーザーがテキストを投げるまでは待機中モーション
- テキストを送信してAPIから回答が返ってくるまでは考え中モーション

を表示するようにしてみました。

また、ヒナタちゃんとの会話内容も見れるようにScrollView内にやりとりの内容を表示できるようにしました。

(画質があまり良くないかもしれませんがご了承ください(´･ω･｀))
![ezgif-4-d42ab47e27.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/0b137052-5679-9e37-06b7-4c5055703c93.gif)


## 開発環境
- Unityバージョン : 2021.3.8f1

## 作業内容
### 1. OpenAIに登録する
- [OpenAIのサイト](https://openai.com/product)にアクセスし、「Get started」をクリックしてアカウントを登録する。
![openai_start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/f3f9d8bf-9480-20c6-e30f-291770bef48c.png)

- アカウントを作成する画面が表示されるので、どれかを選択する。
    - メールアドレスで登録する場合は、電話番号も必要です。
      ※今回は下のGoogleアカウントで登録しました。
![CreateAccount.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/5e692371-10f8-e88b-74ec-c0edc61ba542.png)

- 下記のような画面が表示されたら、アカウント作成完了！
![スクリーンショット 2023-03-16 11.22.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/861e538f-955b-81f6-e683-a4cfe367c0f1.png)


### 2. APIキーを取得する
- 右上の自分のアカウントが表示されている箇所をクリックし、「View API Keys」をクリックする。
![スクリーンショット 2023-03-16 11.25.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/1529b31f-51c3-3159-0672-4858e3751305.png)

- 「＋ Create new secret key」をクリックする。
![スクリーンショット 2023-03-16 11.28.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/d9c523e4-cba3-3466-6968-5e72ed489e6f.png)


- API Keyが作成されるので、コピペしておく。
    ※ API キーを生成した後は、再度表示されることはないため注意！
![スクリーンショット 2023-03-16 11.32.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/0329e2f9-276d-77b1-796a-83f44ac06c41.png)


### 3. Unityで開発する
ここからUnityで実際に開発を進めていきます。
基本的には参考にさせていただいたサイトに記載されている内容と同じですが、一部修正している箇所もあります。
※ AIキャラクター・会話内容の表示関係については説明を省きます。

#### ▼ ChatGPT APIとやりとりするクラス (ConnectionChatGPT.cs)
```C#
using System;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class ConnectionChatGPT
{
    //APIキー
    private readonly string _apiKey;
    //会話履歴を保持するリスト
    private readonly List<ChatGPTMessageModel> _messageList = new List<ChatGPTMessageModel>();

    public ConnectionChatGPT(string apiKey,string settingText)
    {
        _apiKey = apiKey;
        //AIの応答のプロンプトを記録
        _messageList.Add(
            new ChatGPTMessageModel() { role = "system", content = settingText});
    }

    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"}
            };


        //利用するモデルやトークン上限、プロンプトをオプションに設定
        var options = new RequestModel()
        {
            model = "gpt-3.5-turbo",
            messages = _messageList,
            max_tokens = 200,
            top_p = 1
        };
        var jsonOptions = JsonUtility.ToJson(options);

        //HTTP（POST）の情報を設定
        using UnityWebRequest request = new UnityWebRequest(apiUrl, "POST")
        {
            uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(jsonOptions)),
            downloadHandler = new DownloadHandlerBuffer()
        };

        //HTTPヘッダーを設定
        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);

            // _messageListには今までのやりとりが追加されていくため、常に一つ前のやり取りのみ保持しておくようにする
            // _messageList[0]には、AIキャラの設定をしているプロンプトが入っている
            if (_messageList.Count > 3)
            {
                _messageList.RemoveRange(1,2);
            }
            return responseObject;
        }
    }
}

```


#### ▼ モデルクラス (Models.cs)

```C#
using System;
using System.Collections.Generic;
using UnityEngine;

//ChatGPT APIのmessageに含まれる要素
[Serializable]
public class ChatGPTMessageModel
{
    public string role;
    public string content;
}

//ChatGPT APIにRequestを送るためのJSON用クラス
//modelとmessagesについては必須項目
[Serializable]
public class RequestModel
{
    public string model;
    public List<ChatGPTMessageModel> messages;
    public int max_tokens;
    public int top_p;

}

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

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

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


//ChatGPTからのResponse例
/*
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "\n\nHello there, how may I assist you today?",
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}
*/

```

#### ▼ 使用方法

```C#
    /// <summary>
    /// テキスト送信ボタン押下
    /// </summary>
    public void OnTap()
    {
        _ = SendButtonAsync();
    }

    // -------

    /// <summary>
    /// 送信ボタン押下後の処理
    /// _openAIApiKey : APIキー
    /// _nowSettingText : AIの設定を記述しているプロンプト
    /// _requestText : ユーザーが入力したテキスト
    /// </summary>
    async Task SendButtonAsync()
    {
        var connection = new ConnectionChatGPT(_openAIApiKey, _nowSettingText);
        // returnChatGPTTextにはModels.csで定義したChatGPTResponseModelの形で入っているので、ここから情報を取得して使用することができます。
        var returnChatGPTText = await connection.RequestAsync(_requestText);
    }
```


## 最後に
今回初めてChatGPTを使ってみたのですが、まだまだ活用方法もたくさんありそうだし、これからも引き続き学習していきたいなと思います。

ちなみにヒナタちゃんの設定をどうしようか迷い、一旦関西弁を喋らせてみようとした結果、ちゃんと関西弁でしゃべれる女の子が誕生しました！
(自分の推しキャラを生成するのもそう遠くないかもしれません、頑張ろうと思います。)
![Screenshot_2023-03-17-19-58-48-560_com.DefaultCompany.ChatGPT_Practice_Project.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669177/a4a0b625-0027-2716-fe83-af178b171692.jpeg)