OpenAI Realtime API に映像を流せないので 1fps の静止画で代用した
OpenAI の Realtime API で、カメラの前で起きることを実況する音声 AI を作っています。音声の会話は速く返りますが、カメラの前で何かが起きても反応できないか、反応しても古い絵に答えます。実測では 21 秒前の絵に対して、言い回しだけそれらしく反応していました。
Realtime API には映像ストリームの入力がありません。画像は「会話に写真を一枚入れる」扱いなので、いつ・どの画像を見せるかを決める仕組みはアプリが作るしかありません。
最初の失敗: 画像を送ると返事を求めることになる
最初は、場面が変わったと思ったときに画像を送って応答を求めていました。これだと次の問題が起きます。
- 送った画像が「返事を求める入力」になるので、画像を送るたびに発話が発生し、黙って見ていてほしい場面でも喋る
- 「場面が変わった」の判定をアプリ側のラベル(動いた、止まった、明るさが変わった)で書くと、場面の種類が増えるたびに定義表へ条件を書き足し続けることになる
- 判定と送信が遅れると、モデルが見ているのは何秒も前の絵になる
どう直したか: 見せると喋らせるを分ける
設計を 2 つに分けました。
見せる(文脈): カメラのフレームを 1fps で会話に積み続けます。返事を求めない文脈として送るので、画像が入っても発話は起きません。直近の N 枚だけを保持し、古いものは捨てます。これでモデルの文脈には常に最新のフレームが入っています。
喋らせる(トリガー): 「今、実況を切り替えるべき出来事が起きたか」だけを、軽量な視覚言語モデル(VLM)に一問で聞きます。出来事の種類を列挙した定義表は持たずに切り替えの判断をモデルに任せ、アプリはフレームと問いを判定モデルに渡すだけです。判定が「切るべき」なら、そこで初めて Realtime API に応答を求めます。
実測
- 判定の所要時間は中央値で 1.7〜1.9 秒
- 手の動きだけの場面は正しく「切るべきでない」と棄却された
- ラベルの定義表を渡していないのに、判定モデルは手元に掲げた実物の名前を言い当て、何が映っているかを理解していた
限界: 人の手の速度と同じオーダー
反応までの時間を足し合わせると次のようになります。
- 検出 0.25 秒(検出器の設計は検出器の精度ではなく誤検知時の体験で仕様を書く)
- 判定 1.7 秒
- 発話開始まで 0.8〜1.9 秒
合計で 3〜4 秒。人が手元の対象を切り替える速度は 2〜4 秒に一つなので、反応は構造的に一つ遅れ気味です。これはモデルの賢さの問題ではなく、判定と発話の合計時間が人の動作と同じオーダーにあるという物理的な限界です。この遅れへの対処は実況 AI が一歩遅れるので語るのをやめて相槌にしたに、再生待ちの応答が積まれて遅れる別の原因はOpenAI Realtime API の response.cancel が効かない原因に書きました。
次に打つ手として考えているのは次の 2 つです。
- 判定を「MC が喋っている時だけ」に限定する。黙っている間は切り替えの必要がないので、判定そのものを省ける
- 発話側の推論量を最小(none)にして、発話開始までの時間を削る
映像入力が来れば要らなくなり、それまではトークンと問いの文面が制約になる
- この構成は Realtime API 側に映像入力が正式に来た時点で不要になります。鮮度は短い〜中です
- 1fps のフレームは会話の文脈として積まれるので、入力トークンを消費し続けます。直近 N 枚に絞っているのはそのためで、N を増やせば「過去」を見られますが、コストも N に比例して増えます
- 体験の質は判定モデルに渡す問いの文面に左右されます。定義表を持たない代わりに、問いの文面が仕様になります
状況の列挙をやめ、見せる経路と喋らせる経路を分ける
- 状況をラベルで列挙して検出器を書くと、場面が増えるたびに条件を足し続けることになる。問いを一つに絞って判断をモデルに任せ、アプリはフレームと問いを渡すだけにする
- 「見せる(文脈)」と「喋らせる(トリガー)」を分け、画像を送っても返事が来ないようにする
- 反応時間は検出 + 判定 + 発話の合計で評価する。どれか一つが速くても、合計が人の動作より遅ければ一つ遅れる
映像ストリームのない API で映像に反応させるには、映像を「見せ続ける」経路と「今切るべきか」を問う経路を分ける必要がありました。これで映像には反応できるようになりましたが、反応の速さはまだ人の手の動きに追いついていません。