今回は「アバターと自然に会話できる仕組みを作ってみたい」と思い、実装しました。ただ会話するだけでは面白くなく、AIにいろいろ機能をつけたかったので、AIエージェントを使っています。
作ったもの
ユーザーがテキストを入力するとAIエージェントが音声で答えてくれるシステムを作成しました。アバターはLive2D サンプルデータ集(無料配布)の桃瀬ひよりさんを使用しております。今回特にこだわったのは応答生成周りと画面共有機能です。詳細はのちほど書くのですが、応答生成では体感速度を上げる工夫をしたり、画面共有を行い、その内容に即した応答を生成する機能を実装しました。
アーキテクチャ
フロントエンド
UI部分はReactを使って実装しました。バックエンドとの通信はWebSocketで行い、レイテンシーを減らすようにしました。テキストを入力してボタンを押すとAIエージェントにテキストが送信されるのですが、その前にある工夫をしました。
体感速度向上させるためにしたこと
そのままメインエージェントに送ると、ツールの使用などでレスポンスがどうしても遅くなるので、軽量モデルを使用したエージェントを設置しました。ちょっとした応答なら軽量エージェントに応答してもらうようにしています。時間のかかる応答も「~を検索します。少々お待ちください。」のような応答を返す軽量エージェントを挟むことで体感速度を向上させることができます。
ただ、いつメインエージェントに送るか、つまり切り替えをどうするかというところを悩みました。テキストに「検索」という文字が含まれていたらメインエージェントに送るというパターン検知の案もあったのですが、ツールが増えるにつれ検知しないといけないパターンが増えるし、それだけでは対応できないものも出てくると思ったので、機械学習モデルを構築することにしました。
テキストをメインに渡す必要があるかどうかの2値分類モデルにしました。
ただ、学習データをどうするかという新たな問題が出てきました。そこで「こんな特殊なコーパスあるのか?...作るしかないか~」ということで、自作することにしました。方法はシンプルで、ChatGPTに会話を想定した文章のみを生成してもらい、自力でアノテーションをするという方法です。以下のようなものが500件ほどできました。labelに1がついているものが、メインに渡すものです。
text,label
しょうがないな~,0
今日の天気は?,1
天気を教えてぇ,1
「いざ、モデルを作るぞ~」
そう思ったのですが、モデルへの入力はテキストです。そうつまり、形態素解析(分かち書き)を行う必要があります。ただこれらをWebで完結させたかったので、tfidfというものを使用しました。tfidfについてはこちらの記事が分かりやすいと思います。
モデルはランダムフォレストを使い、tfidfはscikit-learnのTfidfVectorizerを使用しました。
def build_pipeline(random_state: int) -> Pipeline:
vectorizer = TfidfVectorizer(
analyzer="word", ngram_range=(1, 1), token_pattern=r"\S+", min_df=1
)
classifier = RandomForestClassifier(
n_estimators=300,
random_state=random_state,
n_jobs=-1,
class_weight="balanced_subsample",
)
return Pipeline([("tfidf", vectorizer), ("rf", classifier)])
そのままではモデルをWebに組み込めないので、ONNX形式に変換してからフロントに組み込みました。基本的なやり方は、こちらの記事にのっています。参考にしてください。
長々書きましたが、フロントエンド部分はUIとWebSocket通信の設定と機械学習を利用した判定システムです。音声合成周りの処理もあるのですが、ここでは省きます。バックエンド側で書きます。
バックエンド
AIエージェントはMastraを使って実装し、フレームワークはHonoを使いました。ここではエージェントの設定などを紹介します。今回は4つのエージェントを使用しています。
- メインエージェント
- 軽量エージェント
- 翻訳エージェント
- VQAエージェント
メインエージェントは対話のメインとなるエージェントです。主にここでツール呼び出しなどを行います。モデルはgemini2.5-flashを使用しています。
軽量エージェントはツール呼び出しを行わないエージェントです。モデルはgemini2.5-flash-liteという軽量モデルを使用しています。
翻訳エージェントは音声合成の際に、英語読み(BookやAmericaなど)をカナ読み(ブックやアメリカなど)に変換するエージェントです。モデルはgemini2.5-flashを使用しています。
VQAエージェントはユーザー画面の画像を元に質問に答えるエージェントです。こちらはメインエージェントのツールとして呼び出されるエージェントになります。モデルはgemini2.5-flash-liteを使用しています。ここのVQAとはVisual Question Answeringのことで、画像とそれに関する質問(自然言語)を入力し、AIが画像内容を解析して適切な答えを生成するタスクです。
MastraにはMemoryという機能があるのですが、メインエージェントと軽量エージェントのMemoryを共有化することで、会話の一貫性を保つようにしています。Memoryの保存先はlibSQLというオープンソースのSQLite互換データベースを使用してます。将来的にはPostgreSQLに保存するようにしたいですね。
音声合成
今回はStyle-Bert-VITS2を使用しています。特にコードをいじっているわけではなく、そのままAPIサーバーとして使用しているので、詳細は省略します。選んだ理由は圧倒的に高速だからです。
まとめ
今回はLive2DとMastraを使用した対話システムを構築しました。もともとLive2Dを使用して対話システムを作ったことがあるのですが、その時はMastraがなくてただのLLMを使用したものでした。あの時と比べるとなかなかいいものができたんじゃないかと思います。
Webにこだわったのは、どこからでもアバターと会話したいという思いがあったからです。今はローカルでないと動きませんがAWSなどにデプロイすることで、どこからでもアクセスできる対話AIができますね。
あまり文章を書くのが得意ではないので、読みにくいところがあったとは思いますが、
最後までご覧いただきありがとうございました。
