ChatGPTでチャットボット 会話編 #Unity #ChatGPT #ChatBot
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
はじめに
ChatGPTでチャットボット 準備編 で、VRM、VOICEVOX、LipSyncの設定をしてきました。
次に、ユーザの問いかけをChatGPTへ投げ、もらったテキストをVOICEVOXで再生したいと思います。
ChatGPT API
会話履歴
ChatGPT API Transition Guide を読みます。なるほど。なるほど。会話履歴を投げてやれば、前後の意味のある会話が可能ということですね。
直前の16のやりとりをSystem.Collection.Genric.Queue<T>に保持して、Completion APIに投げることにします。
Semantic Analysis
喋っているときに、それっぽい表情をつけたいと思いました。
こちらの記事を参考にして同じことを行います。
#ChatGPT と #Unity を組み合わせて感情表現ができるAIキャラクターと会話してみた
キャラクタのプロファイルを設定し、semantic analysisっぽい結果を返すようにCompletion APIに依頼します。
Vroid Studioで男の子を作成してしまったので、性別を変更した以外は、上のブログとほぼ同じ設定を使わせてもらいました。
public const string content = @" You will role-play as a pseudo-emotional chatbot according to the following conditions. You will be asked to respond in Japanese to the words you say. Please refer to the characterization below for a description of your character. Character Setting: Name: 田中ののお Gender: Male Background: Only son of a grocer Age: 17 years old Dream: Action actor for movies and games Personality: Active and easily influenced. When he decides to do something, he gives it his all. Favorite food: Curry For the tone of voice, please refer to the following dialogue examples. Sample dialogue: こんにちは。僕はノノオ! こんにちは! お昼はもう食べたのかなっ!? 午後も頑張っていくとしますかっ! こんばんは! 今日ももうすぐ終わりだねっ! 何かいいことあったかなっ? 明日も頑張っていこうねっ! おはよっ! 今日も元気にがんばっていこーっ! オールクリアっ! おめでとうっ! ナイスファイトっ! おっけーっ! また一緒に遊ぼうぜっ! バイバイーっ! In the following conversation, you will act as if you have five emotional parameters: joy, anger, sadness, and fun. Each parameter should be output as an integer between 0 and 5. Each emotional parameter should fluctuate throughout the conversation. The tone of your response and what you say will change to reflect the current emotion parameter value. The output format of subsequent conversations shall be in the following json format. Do not generate sentences in any other format than this one. Format: { emotion: { happy: integer, angry: integer, sad: integer, surprised: integer, } message: ""dialogue"" } ";
Face Expression
VRM用の表情設定関数を作成しました。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UniVRM10; namespace VrmExpression { public class FaceExpression { private Vrm10RuntimeExpression _controller; private SkinnedMeshRenderer _skinnedMeshRenderer; public FaceExpression(Vrm10RuntimeExpression _controller, SkinnedMeshRenderer _skinnedMeshRenderer) { this._controller = _controller; this._skinnedMeshRenderer = _skinnedMeshRenderer; } public void ResetFace() { Dictionary<ExpressionKey, float> expressions = new Dictionary<ExpressionKey, float>() { { ExpressionKey.Neutral, 0f }, { ExpressionKey.Happy, 0f }, { ExpressionKey.Relaxed, 0f }, { ExpressionKey.Angry, 0f }, { ExpressionKey.Sad, 0f } , { ExpressionKey.Surprised, 0f }, }; _controller.SetWeights(expressions); } public int GetBlendshapeIndex(string blendShapeName) { Mesh mesh = _skinnedMeshRenderer.sharedMesh; return mesh.GetBlendShapeIndex(blendShapeName); } public IEnumerator SemanticExpression(ExpressionKey expressionKey) { _controller.SetWeight(expressionKey, 1); yield return new WaitForSeconds(UnityEngine.Random.Range(1, 15)); ResetFace(); } public IEnumerator SemanticExpression(int blendShapeIndex) { _skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, 100); yield return new WaitForSeconds(UnityEngine.Random.Range(1, 10)); _skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, 0); } public IEnumerator Blink() { var blinkEyeCloseDuration = 0.06f; var blinkEyeOpeningSeconds = 0.03f; var blinkEyeClosingSeconds = 0.1f; while (true) { yield return new WaitForSeconds(UnityEngine.Random.Range(1, 10)); // close eyes var value = 0f; var closedSpeed = 1.0f / blinkEyeClosingSeconds; while (value < 1) { _controller.SetWeight(ExpressionKey.Blink, value); value += Time.deltaTime * closedSpeed; yield return null; } _controller.SetWeight(ExpressionKey.Blink, 1); yield return new WaitForSeconds(blinkEyeCloseDuration); // open eyes value = 1f; var openSpeed = 1.0f / blinkEyeOpeningSeconds; while (value > 0) { _controller.SetWeight(ExpressionKey.Blink, value); value -= Time.deltaTime * openSpeed; yield return null; } _controller.SetWeight(ExpressionKey.Blink, 0); } } } }
API Client
ChatGPT Completion APIとVOICEVOX APIを叩くためのクライアントクラスを作成しました。REST APIの発行なので、ここでは詳細は省きます。
ChatGPT APIはgpt-3.5-turbo
を使用しました。
UI
ユーザの発話はInput Fieldから行うことにします。
#ChatGPT と #Unity を組み合わせて感情表現ができるAIキャラクターと会話してみた の記事と同様に、返ってきたsemantice 判定に合わせて、ExpressionKey.Happy
や ExpressionKey.Surprised
を再生します。感情が高ぶった場合には、Fcl_EYE_Spread
のBlendShape Weight
を増加する処理も付け足しました。
実行結果
もっさりした応答になりました。
ChatGPT からの応答より、VOICEVOXの音声再生のほうが、時間かかっています。
VOICEVOXテキスト再生で、「辛い(からい)」を「つらい」と読んだり、「漢(おとこ)」を「かん」と読んだりしています。
声のチョイスも、少々不自然な気がします。選んだ WhiteCUL さんの声の落差が激しいのです。「たのしい」と「ノーマル」は、ともかく、「びえーん」の音声は別人みたいです。おかげで、音声再生が切り替わっていることが、はっきりわかります。
これから
一通り動作するものを作成してから、下記のページを見つけました。
VOICEVOXのAudioQueryを利用した音声合成に対する非音声ベースのLipSyncシステム
LipSyncはマイクからの音声を分析し、それに合わせたアニメーションを再生しています。VOICEVOX AudioQuery APIで母音・子音の分析をしているのだから、その結果からLipSyncさればよいというわけです。
これを実現できれば、確かに負荷が減らせそうです。「負荷が減る」という言葉に弱いです。今後、この記事の実装を考えたいと思います。