Unity上でChatGPT「text-davinci-003」APIを実行する話

この記事は、面白法人グループ Advent Calendar 2022 の13日目の記事です。

カヤックボンドでエンジニアをやっております中野です。

今回はChatGPTで使用されているGPT-3.5系のモデル「text-davinci-003」のAPIを
Unity上から実行してデータを取得するお話をします。

ChatGPTとは

openai.com

2022年11月30日に発表されたOpenAIが開発するGPT-3.5系の言語モデルを用いたチャットアプリです。
対話形式で、UnityのコードからDB設計まで様々なこと出力してくれます。 かなり賢い。

ChatGPTの使い方について下記が詳しくまとまっております。 qiita.com

最初に

どうやら有志の方が作成したUnity用のAPIライブラリがすでにありました。
確認したところ、2022/12/10現在 最新の「text-davinci-003」は対応されていなかった。。。

https://beta.openai.com/docs/libraries/python-bindings

GitHub - hexthedev/OpenAi-Api-Unity: Integration for the OpenAi Api in Unity

ということで「text-davinci-003」を対応したシンプルなものを作ろう!

事前準備

OpenAIの公式ページよりアカウント発行とAPIのKEYの発行を行います。

アカウント発行

https://auth0.openai.com/u/login/

API KEYの取得

下記ページ内の「Create new secret key」ボタンを押してKEYを発行します。

https://beta.openai.com/account/api-keys

※KEYはアカウント内で再度表示されないため、ご注意ください。
KEYがわからなくなった場合は同ページ内で再発行する必要があります。

検証環境

名前 備考
Windows10
Unity 2021.3.14 https://unity.com/ja/download
UniTask Ver.2.3.3 https://github.com/Cysharp/UniTask/releases
Newtonsoft.Json APIリクエスト/レスポンスのシリアライズ・デシリアライズに使用。 下記を参考にさせていただきました。 Unity2020でNewtonsoft.Jsonを入れる - Qiita

実装

検証スクリプト

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System;
using System.Linq;
using Cysharp.Threading.Tasks;


public class SendChatGPT: MonoBehaviour
{
    /// <summary>
    /// APIエンドポイント
    /// </summary>
    const string API_END_POINT = "https://api.openai.com/v1/completions";
    /// <summary>
    /// API KEY
    /// </summary>
    const string API_KEY = "USER_API_KEY";
    /// <summary>
    /// 入力欄
    /// </summary>
    [SerializeField] 
    private InputField Input;
    [SerializeField]
    private Text Output;

    [SerializeField]
    private Button ExecButton;
    [SerializeField] 
    private Button QuitButton;

    private void Start()
    {
        // API実行ボタン
        ExecButton.onClick.AddListener(async () =>
        {
            //入力取得
            string prompt = Input.text;
            if (!string.IsNullOrEmpty(prompt))
            {
                //レスポンス取得
                var response = await GetAPIResponse(prompt) ;
                //レスポンスからテキスト取得
                string outputText = response.Choices.FirstOrDefault().Text;
                Output.text = outputText.TrimStart('\n');
                Debug.Log(outputText);
            }

        });
        // 終了ボタン
        QuitButton.onClick.AddListener(() =>
        {
            Application.Quit();
        });
    }

    /// <summary>
    /// APIからレスポンス取得
    /// </summary>
    /// <param name="prompt"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public async UniTask<APIResponseData> GetAPIResponse(string prompt)
    {
        APIRequestData requestData = new()
        {
            Prompt = prompt,
            MaxTokens = 300 //レスポンスのテキストが途切れる場合、こちらを変更する
        };

        string requestJson = JsonConvert.SerializeObject(requestData, Formatting.Indented);
        Debug.Log(requestJson);

        // POSTするデータ
        byte[] data = System.Text.Encoding.UTF8.GetBytes(requestJson);


        string jsonString = null;
        // POSTリクエストを送信
        using (UnityWebRequest request = UnityWebRequest.Post(API_END_POINT, "POST"))
        {
            request.uploadHandler = new UploadHandlerRaw(data);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");
            request.SetRequestHeader("Authorization", "Bearer " + API_KEY);
            await request.SendWebRequest();

            switch (request.result)
            {
                case UnityWebRequest.Result.InProgress:
                    Debug.Log("リクエスト中");
                    break;
                case UnityWebRequest.Result.Success:
                    Debug.Log("リクエスト成功");
                    jsonString = request.downloadHandler.text;
                    // レスポンスデータを表示
                    Debug.Log(jsonString);
                    break;
                default: 
                    throw new ArgumentOutOfRangeException();

            }
        }

        // デシリアライズ
        APIResponseData jsonObject = JsonConvert.DeserializeObject<APIResponseData>(jsonString);

        return jsonObject;
    }
}

APIリクエスト/レスポンス スクリプト

using Newtonsoft.Json;

/// <summary>
/// APIリクエスト
/// 
/// https://beta.openai.com/docs/api-reference/authentication
/// </summary>

[JsonObject]
public class APIRequestData
{
    [JsonProperty("model")]
    public string Model { get; set; } = "text-davinci-003";
    [JsonProperty("prompt")]
    public string Prompt { get; set; } = "";
    [JsonProperty("temperature")]
    public int Temperature { get; set; } = 0;
    [JsonProperty("max_tokens")]
    public int MaxTokens { get; set; } = 100;
}

/// <summary>
/// APIレスポンス
/// 
/// https://beta.openai.com/docs/api-reference/authentication
/// </summary>
[JsonObject]
public class APIResponseData
{
    [JsonProperty("id")]
    public string Id { get; set; }
    [JsonProperty("object")]
    public string Object { get; set; }
    [JsonProperty("model")]
    public string Model { get; set; }
    [JsonProperty("created")]
    public int Created { get; set; }
    [JsonProperty("choices")]
    public ChoiceData[] Choices { get; set; }
    [JsonProperty("usage")]
    public UsageData Usage { get; set; }
}

[JsonObject]
public class UsageData
{
    [JsonProperty("prompt_tokens")]
    public int PromptTokens { get; set; }
    [JsonProperty("completion_tokens")]
    public int CompletionTokens { get; set; }
    [JsonProperty("total_tokens")]
    public int TotalTokens { get; set; }
}

[JsonObject]
public class ChoiceData
{
    [JsonProperty("text")]
    public string Text { get; set; }
    [JsonProperty("index")]
    public int Index { get; set; }
    [JsonProperty("logprobs")]
    public string Logprobs { get; set; }
    [JsonProperty("finish_reason")]
    public string FinishReason { get; set; }
}

動作確認

1. Unityについて質問してみる。

入力 : Unityとは何ですか?

2. おすすめ観光地について聞いてみる。

入力 : 日本のおすすめ観光地はどこですか?

うまく取得できているみたいですね! APIより取得できたテキストが途切れる場合は、 リクエストの「max_tokens 」を大きな値に調整する必要があります。

最後に

今回の記事では、UnityからChatGPTのAPI実行についてお話しました。

最後までお読みいただきありがとうございました!!