ターンキータイミングを予測するMaAIが話題になっているので触りました。MaAIは、大規模言語モデル(LLM)や音声合成(TTS)といったジャンルではなく、対話における非言語的行動(ノンバーバル・ビヘイビア)の生成に特化したライブラリでヒューマン・コンピューター・インタラクション(HCI)の分野と言えます。
MaAIとは
MaAIは、対話型AIにおいて、非言語的行動(ターン交代、相づち、うなずき)をリアルタイムかつ軽量に生成(予測)するためのライブラリです。京大が開発している様です。
▼Githubはこちら
https://github.com/MaAI-Kyoto/MaAI
実際に使ってみます。
from maai import Maai, MaaiInput, MaaiOutput
mic = MaaiInput.Mic()
zero = MaaiInput.Zero()
maai = Maai(mode="vap", lang="jp", frame_rate=10, context_len_sec=5, audio_ch1=mic, audio_ch2=zero, device="cpu")
maai_output_bar = MaaiOutput.ConsoleBar(bar_type="balance")
maai.start()
while True:
result = maai.get_result()
maai_output_bar.update(result)
会話の終端を検知する
P_Now (話者発話中): 現在、話者が発話している確率(高いほど話し手が話している)。
P_Future (聞き手発話開始): 聞き手(相槌を打つ側)がこれから発話し始める確率(高いほど相槌に適している)。
import time
from maai import Maai, MaaiOutput, MaaiInput
import numpy as np
# 1. 設定
# マイクを使用するため、ファイルパスはコメントアウト
# AUDIO_FILE_PATH = "sample.wav"
# maai の初期化設定
# 安定動作が確認された "vap" モードを使用
maai = Maai(
mode="vap",
lang="jp", # 日本語設定
frame_rate=10,
context_len_sec=5,
# マイク入力を使用
audio_ch1=MaaiInput.Mic(),
audio_ch2=MaaiInput.Zero(),
device="cpu"
)
# 2. 結果出力設定(ConsoleBarは使用せず、直接値を表示)
print("--- maai リアルタイム予測開始 (マイク入力) ---")
print("マイクに向かって何か話してください。予測結果が直接出力されます。Ctrl+Cで停止します。")
print("---------------------------------------------")
maai.start()
start_time = time.time() # 時間計測開始
# 3. 処理ループ
try:
while True:
# 結果を待機して取得
result = maai.get_result()
if result is None:
continue
# 💡 リスト型対応の処理: result の値がリストの場合、最初の要素 [0] を抽出する
processed_result = {}
for key, value in result.items():
if isinstance(value, list) and value:
# リストの最初の要素を抽出
processed_result[key] = value[0]
else:
# リストではないか、空のリストの場合はそのまま使用
processed_result[key] = value
# 頷き/相槌の値を取得 (キーが "nod" や "aizuchi" になっていることを期待)
nod_val = processed_result.get('nod', processed_result.get('p_nod', -1))
aizuchi_val = processed_result.get('aizuchi', processed_result.get('p_aizuchi', -1))
# ターンテイキングの値を取得
p_now_val = processed_result.get('p_now', -1)
p_future_val = processed_result.get('p_future', -1)
output_line = f"Time: {time.time() - start_time:.2f}s | "
# 頷き/相槌の予測値が含まれているか確認
if nod_val != -1 or aizuchi_val != -1:
# 頷き/相槌の予測がある場合の出力
output_line += f"Nod: {nod_val:.3f} | Aizuchi: {aizuchi_val:.3f} | P_Now: {p_now_val:.3f}"
else:
# 含まれていない場合のデフォルト出力(ターンテイキング予測のみ)
output_line += f"P_Now: {p_now_val:.3f} | P_Future: {p_future_val:.3f}"
print(output_line)
time.sleep(0.1) # 100ms ごとに結果を出力
except KeyboardInterrupt:
print("\n処理を中断しました。")
# 4. 終了処理
maai.stop()
print("\n--- maai 処理終了 ---")
テスト結果
Time: 0.22s | P_Now: 0.649 | P_Future: 0.451
Time: 0.32s | P_Now: 0.260 | P_Future: 0.195
Time: 0.43s | P_Now: 0.174 | P_Future: 0.129
Time: 0.53s | P_Now: 0.117 | P_Future: 0.104
Time: 0.64s | P_Now: 0.092 | P_Future: 0.089
Time: 0.74s | P_Now: 0.100 | P_Future: 0.091
Time: 0.85s | P_Now: 0.082 | P_Future: 0.079
Time: 0.95s | P_Now: 0.079 | P_Future: 0.078
Time: 1.05s | P_Now: 0.084 | P_Future: 0.083
Time: 1.15s | P_Now: 0.094 | P_Future: 0.090
... 長くなるので割愛
時間帯 P_Nowの変化 P_Futureの変化
3.11秒 0.151 → 0.947 0.153 → 0.708
発話開始の瞬間。 静寂から話し手が言葉を出し始めたタイミング。
14.58秒 - 16.02秒 0.922 → 0.464 0.764 → 0.404
最も明確な区切り。 話し手が文を終えて、大きな間を取ったと予測されます。この間に「うん」「はい」などの音声相槌が最適。
23.03秒 - 23.96秒 0.585 → 0.427 0.563 → 0.406
発話継続に迷いが見える。 話し手の声の勢いが弱まり、聞き手が次の発話を期待できるタイミング。
感想
単純にユーザ入力がなくなったタイミングを検知している様にも見えるが、まぁ実用的に使えそうな感じはしなくもないですね。