※ タイトルから誤解する方もいるかもしれないので最初に断っておくと、この記事は生成AIを使ったモーションの自動生成について扱うものではありません。
はじめに
OpenAIのChatGPTをはじめとするAIチャットサービスの普及と、ChatGPTのカスタマイズを可能にするGPTsの登場により、AIチャットにキャラクター性を持たせて、キャラクターとの会話を行うキャラクターAIのようなものが誰でも作れるようになりました。
さらに、そのキャラクターAIにガワであるアバターを付けて、アバターとの会話を行えるようなものもチラホラ出てきています。
ですが、アバターがほとんど動かないハリボテであったり、動きはするものの会話の内容と連動しておらず、一定のモーションを繰り返すだけであったりと、まだまだ自然なキャラクターAIとアバターの連携は難しいのかなといった印象です。
そこで、今回は自分なりにキャラクターAIへの自然なモーション付けの方法を考えてみます。
方法の検討
ぱっと思いつく、かつ理想的な方法はキャラクターAIの発言内容に合わせて、自動でモーションを生成して反映するという方法です。
軽く調べたところ、
といったような、モーションを説明するテキストからモーションを生成できるものはいくつかあるようですが、いずれも
- 生成に少し時間がかかるので、リアルタイムでの会話のやり取りの中で利用するには向いていなさそう
- キャラクター性を反映させたような自然なモーション生成はまだ難しそう
といった印象を受けました。
そこで、もう少し現実的な方法としてモーションはあらかじめ人がパターンを用意しておき、その中から発言内容に合うモーションをAI自身に選択させるという方法で考えてみます。
AIにモーションを選択させる
ここからは実際にOpenAIの Assistant API を使って試していきます。
Assistant API はGPTsのAPI版のようなもので、カスタマイズしたChatGPTの作成と利用については、APIを通して行うことができます。
今回は、あらかじめ用意したモーションパターンのリストの中から返答の内容に合うモーションを選択させ、返答と一緒にそのモーションのIDを返すような Assistant を作成してみます。
Assistantの作成・利用はOpenAIのPlayground上でWebUIを通して試すこともできるので、今回はお手軽にPlayground上で作成と実践を試していきます。
準備
まずは、準備として簡単なモーションパターンのリストを用意します。
{
"motions": [
{
"motionID": "001",
"description": "軽く右手を挙げる"
},
{
"motionID": "002",
"description": "首を縦に振る"
},
{
"motionID": "003",
"description": "首を横に振る"
},
{
"motionID": "004",
"description": "首を傾げる"
},
{
"motionID": "999",
"description": "適切なモーションがない場合用の汎用モーション"
},
]
}
motionIDと、モーションの内容を説明したdescriptionを持つ配列です。
motionIDは greeting のような意味のわかりやすいIDを使ってもいいのかもしれませんが、AIの選択がIDの意味に引っ張られることなく、純粋にdescriptionの内容から判断してもらいたいので今回はこのようなIDにしています。
次に、APIとして利用しやすいようChatGPTからの返答をテキストではなくJSONで受け取れるようにします。
Assistant API の返答内容は通常ではテキストなのですが、指定したJSON形式で返答するよう設定することもできます。
返して欲しいJSONのフォーマットを公式のドキュメントに沿って次のように定義します。
{
"name": "answer_with_motion_id",
"strict": true,
"schema": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"description": "返答内容"
},
"motionID": {
"type": "string",
"description": "返答内容に適したmotionID"
},
"reason": {
"type": "string",
"description": "motionIDを選択した判断根拠"
}
},
"required": [
"answer",
"motionID",
"reason"
],
"additionalProperties": false
}
}
propertiesで返して欲しいプロパティを定義しており、今回はanswerとmotionID、reasonを返すよう定義しています。
キャラクターAI用のAPIとして利用することを想定すると、モーションを選択した根拠であるreasonは使用しないプロパティなのですが、reasonの有り無しだと有りの方が体感回答の精度が高いと感じたので、reasonも返すようにしています。
LLMのプロンプトの手法として、問題を答えさせる場合答えだけでなくその導出過程も一緒に答えさせることで回答精度が上がるというものがありますが、そんなイメージです。
Assistantの作成
必要な準備は完了したので、実際にPlayground上でAssistantを作成していきます。
Playground上でAssistantsを選択し、Createボタンを押すと以下のような画面になります。

この画面上でAssistantの設定を行っていくのですが、今回設定を行う項目は
- Name
- Instructions
- Model
- Response format
です。順に設定していきます。
まず、Nameはその名の通りAssistantの名前になります。
APIでAssistantを呼び出す際は、Nameではなく自動で割り振られるidを使うので、ここではわかりやすい名前をつけておけば良いと思います。
今回はMotionTestにしました。
次に、Instructionsですが、これがAssistantの振る舞いを決定する肝になります。
今回は以下のように設定しました。
ユーザーの発言に対して返答する際は、
必ず以下に示す motions を参照して返答の発言内容に適したモーションを判定し、
その motionID も合わせて返答してください。
```
{
"motions": [
{
"motionID": "001",
"description": "軽く右手を挙げる"
},
{
"motionID": "002",
"description": "首を縦に振る"
},
{
"motionID": "003",
"description": "首を横に振る"
},
{
"motionID": "004",
"description": "首を傾げる"
},
{
"motionID": "999",
"description": "適切なモーションがない場合用の汎用モーション"
},
]
}
```
motionsは先ほど用意したモーションのリストです。
今回はAIにモーションを選択させることのみを目的としているのでシンプルな内容になっていますが、実際にキャラクターAIとして利用する場合にはキャラクターの設定等もここに書くことになります。
Modelは使用するモデルです。今回はgpt-4o-miniを選択しています。
最後にResponse formatです。
ここでjson_schemaを選択するとフォーマットの指定を促されるので、先ほど定義したJSONのフォーマットを設定します。
以上で設定完了です。
実践
では、実際にPlayground上で作成したAssistantとやり取りをしてみます。

THREADエリアにてチャット形式でやり取りを試すことができます。
「こんにちは」という入力に対し、
{
"answer": "こんにちは!今日はどんなことをお話ししましょうか?",
"motionID": "001",
"reason": "軽く右手を挙げることで挨拶を表現しました。"
}
という返答が返ってきています。
形式も指定通りのJSONで返ってきており、軽く右手を挙げるモーションであるmotionID: 001が選択され、reasonも真っ当です。
モーションリスト内には「挨拶」のような具体的なワードを一切含めていないのにもかかわらず、「挨拶」として適切なモーションを選択できています。
適当にもう少し長いやり取りを試した内容が以下になります。
User
こんにちは
MotionTest
{
"answer": "こんにちは!今日はどんなことをお話ししましょうか?",
"motionID": "001",
"reason": "軽く右手を挙げることで挨拶を表現しました。"
}
User
今日の天気ってわかる?
MotionTest
{
"answer": "今日は晴れ時々曇りの予報です。お出かけに良い天気ですね!",
"motionID": "002",
"reason": "首を縦に振ることで、天気に関して肯定的に答えていることを示しました。"
}
User
今何時?
MotionTest
{
"answer": "今はちょうど3時です。",
"motionID": "999",
"reason": "特に適切なモーションがないため汎用モーションを選びました。"
}
User
夕食は何にしよう?
MotionTest
{
"answer": "夕食にはパスタやカレーがおすすめです!どちらがいいですか?",
"motionID": "004",
"reason": "首を傾げることで、選択肢を考えている様子を表現しました。"
}
User
嫌いな食べ物ある?
MotionTest
{
"answer": "特にこれといった嫌いな食べ物はないですが、苦手なものもありますね。",
"motionID": "003",
"reason": "首を横に振ることで、あまり好んでいない気持ちを示しました。"
}
User
何が苦手?
MotionTest
{
"answer": "例えば、辛すぎる食べ物が苦手ですね。",
"motionID": "002",
"reason": "首を縦に振ることで、自分の苦手なものについて確信を持って答えています。"
}
User
じゃあ辛くない食べ物を買ってくる
MotionTest
{
"answer": "いいですね!辛くない食べ物なら安心ですね。何を買ってくるか楽しみです!",
"motionID": "001",
"reason": "軽く右手を挙げることで、賛同する意思を表現しました。"
}
User
いってきます
MotionTest
{
"answer": "いってらっしゃい!楽しんでくださいね!",
"motionID": "001",
"reason": "軽く右手を挙げることで、お見送りの挨拶を表現しました。"
}
用意したモーションのパターンが少ないのはありますが、それでも十分適切なモーションを選択できている印象です。
また、モーションの選択傾向をもう少しこちらで絞りたいような場合には、モーションリストに
{
"motions": [
︙
{
"motionID": "004",
"description": "首を傾げる",
"useCases": [
"疑問",
"提案"
]
},
︙
]
}
のように、useCasesのようなプロパティを追加し、どういうケースでそのモーションを使って欲しいかの例を示すのも良さそうです。
さいごに
ということで、キャラクターAIにモーションを選択させる方法を考えて簡単ながら実践してみました。
試してみた感触としては思っていた以上に好感触でしたが、実用を目指す場合には
- モーションパターンがより多く、複雑になった場合どうなるか
- 読み上げと合わせることを想定しても、一つの回答に対して一つのモーションを当てるのが限界そう
- 長い・複雑な回答に対して一つのモーションを選択するとなると精度的にどうなるのか、どこまで自然に見えるのか
あたりが課題になるのかなと思います。
余談
Assistant作成の際、Instructionsの中にモーションリストのJSONをベタ書きするのではなく、File searchを有効にして、モーションリストをJSONファイルとしてアップロード、回答の際には必ずそのファイルを参照してモーションを選択させるようにするパターンも試しました。
結果、挙動自体は問題なさそうだったのですが、
- token数が爆上がりした
- File searchを有効にすると
Response formatにtextしか選択できない
といった理由で、今回はベタ書きにしました。








