WebSocket中継 vs WebRTC直結 ― 両方実装して固有名詞で殴り合わせたら、精度の勝敗とバグが2匹出てきた

リアルタイム音声AIの設計初日、「ブラウザとOpenAI Realtime APIをどう繋ぐか」で2つのアーキテクチャを比較した。全トラフィックが自サーバーを通るWebSocket中継と、ブラウザがOpenAIへ直接繋ぐWebRTC。当時は中継方式を選んだが、今回両方実装してUIで切り替えられるようにした。机上の比較表を、実測で答え合わせできる状態にしたわけである。

WebRTC直結モードの設計

WebRTCでは音声(メディアトラック)もイベント(データチャネル oai-events)もブラウザ⇄OpenAI直結になる。ではサーバーは失業するかというと、そうはならない。

  1. 一時キーの発行 — サーバーがAPIキーで client_secrets を叩き、ペルソナ設定を埋め込んだ数分で失効する一時キー(ek_…)を発行。APIキー本体は今まで通りブラウザに渡さない
  2. 検索の実行代行 — function callingのイベントはブラウザに直接届くので、ブラウザが検知して /api/search にお願いし、結果をデータチャネルでOpenAIへ返す
  3. 履歴の受け取り — 中継が会話を見られないため、ブラウザが文字起こしを自己申告で送ってくる

つまり「音声の通り道」は手放しても、「頭脳の置き場所」は選べる。

検証には小技を使った。開発環境のブラウザにはマイク権限がないので、getUserMediaAudioContext の合成ストリームに差し替え、macOSの say コマンドで作った音声を流し込む。偽マイクでWebRTCの文字起こしまでE2Eテストできる

実測: 固有名詞で殴り合わせる

同じマイク・同じ部屋で、両回線に同じ質問をぶつけた。題材はブルワーズの剛腕ルーキー「ジェイコブ・ミジオロウスキー」——音声認識には地獄のような子音の連続である。

  • WebRTC: 「ジェイコブ・ミジオロウスキーとどっちが強いの、大谷翔平は」→ ほぼ完璧に認識
  • WebSocket: 「ヘイグーに上手きと大谷将暉は大勢成績を調べてくれる」→ 単語サラダ。しかもAIは佐藤健と神木隆之介の共演作品を調べ始めた(誰も頼んでいない)

精度はWebRTCの圧勝だった。理由は構造的で、Realtime APIのWebSocket入力は24kHz PCM固定なのに対し、WebRTCはOpus 48kHzのまま届く。倍の帯域と、Googleが10年磨いた音声パイプライン(AEC・ノイズ抑制一体型)の差である。

バグ1: WS側だけ劣化する「鮮度」問題

ただしWSの惨敗には、もう一つ原因が潜んでいた。WebRTCは接続のたびにマイクを取り直すのに、WSは録音チェーンをページを閉じるまで使い回す設計だった。回線をA/B切替すると、WS側だけ何セッションも前のデバイス選択・OS音声処理状態のまま話し続ける。「接続ごとに録音チェーンを作り直す」修正を入れた——「電話は常に1本」の哲学をマイクにも適用した形だ。

バグ2: デグローム融合事件

精度が上がった後、今度は面白いハルシネーションが出た。「ミジオロウスキーと大谷はどっちが投手として優れている?」と聞いたら、AIが「ジェイコブ・デグローム(本名ジェイコブ・ミジオロウスキー)」と答えたのである。実在する2人の投手を融合し、架空の本名関係まで発明した。

原因を分析すると、質問が「どっちが優れている?」という意見の形をしていたため、「事実を聞かれたら検索する」というルールをすり抜けていた。モデルは知らない名前を、一番似ている既知の投手に勝手に名寄せした——エンティティ融合と呼ばれる型で、厄介なのはモデルが「知らないことを知らない」点だ。

対策として共通ルールに「確信のない固有名詞は、意見や比較を求められた場合でも答える前に検索する」「裏付けのない本名・別名の断定を禁止」を追加した。同じ質問で再検証したところ、今度は両選手それぞれに検索が発火し、実際の成績に基づく比較が返ってきた。デグロームは二度と現れなかった。

結論

精度のWebRTC、監査のWebSocket。 個人利用ならWebRTCを既定にし(実際デフォルトを変えた)、全会話の記録・介入・監査が要る用途では中継を選ぶ。両方持っておけば、用途が決めてくれる。

コードは yoshiharu-ishii/realtime_voice で公開している。


リアルタイム音声AI 開発シリーズ

シリーズ目次(全7回のあらすじ)はこちら

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール