UnityからOpenAIのAPIを呼び出してChatGPTと会話するテストプログラムを作成しました
作ったプログラムの内容と実装方法を説明します
実行画面
実行すると下のような画面が表示されます。 会話文を入力します。 送信ボタンをクリックすると回答が返ってきます。 会話文を入力して再度送信をクリックすると、回答が返ってきます。
実装方法
使用ライブラリ
今回はコミュニティで公開されているUnity用の com.openai.unity by RageAgainstThePixel ライブラリを使用しました。
ライブラリのインストール
Unity EditorのProject Settingsを開いてPackage Managerところで 下記のregistryを追加します。
Name: OpenUPM
URL: https://package.openupm.com
Scope(s):
com.openai
com.utilities
Package Managerを開いて My Registriesを選択します。 OpenAI packageを選択してダウンロードとインポートを行います。 依存パッケージは自動でダウンロードされます。
プログラム構成
プログラムは3つのクラスにわかれています。
①OpenAIのAPIを扱うOpenAiApiServiceクラス
②UI上の情報取得と更新を行うUIManagerクラス
③メインのプログラムを実行するProgramクラス
以下順番に解説します。
◇OpenAiApiServiceクラス
概要
フィールド、コンストラクタ、関数定義にわかれています。
フィールド定義
ApiKeyとAPIを呼び出すときに使うOpenAIClient変数を定義しています。
private const string ApiKey = "キー"; private readonly OpenAIClient openAiClient;
コンストラクタ
ApiKeyを引数にしてOpenAIClientオブジェクトを作成しています。
public OpenAiApiService() { openAiClient = new OpenAIClient(ApiKey); }
関数定義
ChatGPTに質問を投げる関数として
CreateCompletionAsync(SynchronizationContext context, string systemPrompt, string firstQuestion, string lastAnswer)を定義しています
引数は
変数名 | 説明 |
---|---|
SynchronizationContext context | メインスレッドのコンテキストを受け取るための変数 |
string systemPrompt | ChatGPTに演じてほしい役割を受け取る |
string firstQuestion | ChatGPTへ渡したいテキストを受け取る |
string lastAnswer | ChatGPTの前回の回答を受け取る |
関数の中身
public async ValueTask CreateCompletionAsync(SynchronizationContext context, string systemPrompt, string firstQuestion, string lastAnswer) { var messages = new List<Message> { new Message(Role.System,systemPrompt), new Message(Role.User,firstQuestion), new Message(Role.Assistant,lastAnswer) }; var response = await openAiClient.ChatEndpoint.GetCompletionAsync(new ChatRequest(messages, Model.GPT3_5_Turbo)); var choice = response.Choices[0]; var content = choice.Message.Content; context.Post(_ => { UIManager.SetAnswer(content); //ChatGPTからの回答を画面に表示 }, null); }
ヒント
最初知らずにプログラムを書いた時は、何も考えず public class OpenAiApiService : MonoBehaviour としていました。
この状態だと下記の警告がでます。
MonoBehaviourやその継承クラスをnewしてはいけないようです。 継承を削除したところ警告は消えました。 今回のケースでは、GameObjectを使用していないのでMonoBehaviorの継承は不要でした。
参考
OpenAiApiService.csコード
using OpenAI; using System.Collections.Generic; using System.Threading.Tasks; using System.Threading; using UnityEngine; using OpenAI.Models; using OpenAI.Chat; public class OpenAiApiService { #region フィールド定義 private const string ApiKey = "キー"; private readonly OpenAIClient openAiClient; #endregion #region コンストラクタ public OpenAiApiService() { openAiClient = new OpenAIClient(ApiKey); } #endregion #region CreateCompletionAsync()関数 public async ValueTask CreateCompletionAsync(SynchronizationContext context, string systemPrompt, string firstQuestion, string lastAnswer) { var messages = new List<Message> { new Message(Role.System,systemPrompt), new Message(Role.User,firstQuestion), new Message(Role.Assistant,lastAnswer) }; var response = await openAiClient.ChatEndpoint.GetCompletionAsync(new ChatRequest(messages, Model.GPT3_5_Turbo)); var choice = response.Choices[0]; var content = choice.Message.Content; Debug.Log(content); context.Post(_ => { UIManager.SetAnswer(content); }, null); } #endregion
◇UIManagerクラス
概要
UIManagerでは3つの関数を定義しています。
関数名 | 説明 |
---|---|
GetQuestion()関数 | Input Fieldに入力したテキストを取得して返す |
GetAnswer()関数 | 画面に表示されているChatGPTからの回答を取得して返す |
SetAnswer()関数 | ChatGPTからの回答(string)を引数に取り、回答欄に表示する |
UIManager.csコード
using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { #region GetQuestion()関数 //Input Fieldに入力したテキストを取得して返す public static string GetQuestion() { var go = GameObject.Find("QText"); var question = go.GetComponent<Text>(); return question.text; } #endregion #region GetAnswer()関数 //画面に表示されているChatGPTからの回答を取得して返す public static string GetAnswer() { var go = GameObject.Find("Answer"); var answer = go.GetComponent<Text>(); return answer.text; } #endregion #region SetAnswer()関数 //ChatGPTからの回答(string)を引数に取り、回答欄に表示する public static void SetAnswer(string answertext) { var go = GameObject.Find("Answer"); var answer = go.GetComponent<Text>(); answer.text = answertext; } #endregion }
◇Programクラス
概要
Programクラスではフィールド定義の後、Start()関数の定義とButtonClick()関数 の定義を行っています。
フィールド定義
フィールドではメインスレッドを保存する変数を定義しています。
private static SynchronizationContext context;
Start()関数
context変数にメインスレッドを保存しています。
context = SynchronizationContext.Current;
ButtonClick()関数
UI上のボタンがクリックされたら、必要なパラメータを設定してCreateCompletionAsyncを呼び出す関数
public static async void ButtonClick() { var openAiApiService = new OpenAiApiService(); var systemPrompt = "あなたは私は仲の良い友人です。二人はとても仲が良くフランクな口調で会話します。"; var firstQuestion = UIManager.GetQuestion(); var lastAnswer = UIManager.GetAnswer(); await openAiApiService.CreateCompletionAsync(context, systemPrompt, firstQuestion, lastAnswer); }
この関数をボタンオブジェクトのOn Clickに設定しています。
値を返さない関数(void)にしています。値を返す関数だとボタンオブジェクトのOn Clickに設定しようとしても リストにでできませんでした。voidにするとでてくるようになりました。そもそもasyncだと返り値を返せないけれど。。
Program.csコード
using UnityEngine; using System.Threading; public class Program : MonoBehaviour { #region フィールド private static SynchronizationContext context; //スレッドを保存する変数 #endregion #region Start()関数 public void Start() { context = SynchronizationContext.Current; } #endregion #region ButtonClick()関数 public static async void ButtonClick() { var openAiApiService = new OpenAiApiService(); var systemPrompt = "あなたは私は仲の良い友人です。二人はとても仲が良くフランクな口調で会話します。"; var firstQuestion = UIManager.GetQuestion(); var lastAnswer = UIManager.GetAnswer(); await openAiApiService.CreateCompletionAsync(context, systemPrompt, firstQuestion, lastAnswer); } #endregion }
まとめ
コミュニティで公開されているライブラリを使って、OpenAIの APIを呼び出して会話プログラムを実装する方法を解説しました。 ライブラリを使えば簡単にOpenAIのAPIを呼び出すことができるので、これを使えばいろいろ面白いことができそうですね!
参考
下記Udemyの講座を参考にしました。 講座ではC#用のBetalgo.OpenAI by Betalgoのライブラリを使用しています。 Betalgo.OpenAIライブラリはC#ver10.0の機能まで使用しているようで、Unityでは使えませんでしたが、 講座でのコードの書き方等が参考になりました。(UnityではC#Ver8.0の一部までしか使えないようです)