LLM は喋っているのに音声が出ない原因は進行装置のフラッシュ
更新: Web開発者向け
カメラの前で起きることを音声で実況する AI を作っていました(実況 AI が一歩遅れるので語るのをやめて相槌にした)。土台は OpenAI の Realtime API で、その上に番組進行のための装置 — フェーズを持つ状態機械、フェーズ転換時のキュー注入、追い越されたターンの破棄 — を被せています。
ある時点から、モデルは喋っているのに何も聞こえなくなりました。サーバー側の使用量を見ると出力トークンは計上され続けていて、音声は生成されているのにスピーカーからは何も出ていませんでした。
実測: 60 秒に 7 回のフェーズ空転 × フラッシュ = 音声ゼロ
原因は進行装置の側にありました。
- 映像の検出器が誤発火して、フェーズが 60 秒間に 7 回転換していた
- 私の実装は、フェーズ転換のたびに「前のフェーズの発話は古い」とみなして再生キューをフラッシュし、追い越したターンを破棄していた
- 応答が生成されてから音が出るまでの数秒のあいだに次の転換が来るので、すべての応答がスピーカーに届く前に捨てられていた
同じ条件でフラッシュだけを外すと発話が成立しました。モデルにも音声経路にも問題はなく、進行装置のフラッシュが生成済みの音声を再生前に捨てていただけでした。
機構: 合図が増えるほど、モデルの注意は人間から逸れる
フラッシュを外しても、もう一つ残った問題があります。進行装置が注入する合図(「フェーズが変わった」「今は沈黙の場面」「次の話題へ」)が多いほど、MC 役のモデルは目の前のゲストより合図について喋るようになります。ゲストと合図がモデルの注意を取り合っていました。
制御を足して直そうとするたびに注意の取り合いは悪化するので、進行装置の役割を次のように減らしました。
- 状態機械の責務をセッションの寿命管理(開始・終了・再接続)だけに縮める
- フェーズという概念をアプリ側から消し、進行はモデルに任せる
- 生成中・再生中の出力を切る操作は、演出上どうしても必要な 1 箇所だけに残す(OpenAI Realtime API の response.cancel が効かない原因)
フラッシュを外しても古い発話と 1 箇所の打ち切りは残る
- フラッシュを外したので、モデルが古い場面について語り続けることはあります。次の入力でモデル自身が話題を切り替えるのに任せ、アプリは割り込みません
- 「演出上必要な 1 箇所」の打ち切りは残っているので、その打ち切りではまだ出力が失われます
- 60 秒に 7 回という空転の数値は、検出器の誤発火が多かった当時の条件で出た値です(検出器は別途作り直しました。検出器の精度ではなく誤検知時の体験で仕様を書く)。フラッシュによる音声の欠落は、転換の回数が少なくても起きます
ライブ音声 LLM に被せる装置は出力を切る操作を最後の手段にする
- ライブ音声 LLM の上に装置を被せるなら、「点火はする・破壊はしない」を原則にする。生成中の出力を切る操作は最後の手段
- 「喋らせる仕組み」と「黙らせる仕組み」を分けて設計する。黙らせる仕組みは演出上必須の 1 箇所だけに絞る
- 無音の原因を疑う順番は、モデル → 音声経路 → 自分の制御層。出力トークンが計上されているなら、音声を捨てているのは自分の制御層(自前の完了判定が音声を取りこぼした例: Gemini Live API で turnComplete 後に音声が届かない)
モデルを制御しようとして私が書いた「黙らせる仕組み」が、出力トークンの計上された音声を再生前に捨てて無音にしていました。