⚒️

音声AIとテキストAIの大きな違い: 音声AIの基本アーキテクチャに足りないこと

に公開

G's Academy* 東京DEV3期卒のIsakaです
*: デジタルハリウッドが運営する起業家・エンジニア養成スクール
https://g-s.dev/

今回は音声AIエージェントのアーキテクチャを2度作り直した話をします。音声AIをこれから作りたい方に参考になるのではと思います

まとめ

  • 音声AIエージェントにはテキストAIエージェントとは大きく異なる設計が必要
    • 音声AIにはLLMの通常出力は長過ぎて適さない
    • テキストAIは待ち時間中に他作業がしやすいが、音声AIにはそれが難しく非同期tool callが望ましい

執筆の背景

G's Academyでは何かを作りたい人が集まる会があり、12月後半からその会に参加しています。毎月何かのアウトプットを出すことが推奨されていて、この記事もその一環で書いています

開発しているのは音声AIエージェント。以下がそのプロトタイプ

https://x.com/isaka_aipdm/status/2017596122114789866?s=20

構成はVercel + Next.js + OpenAI Realtime API。全てAIで作っており、自分は一切コードを書いていません

しかし上記に至るまでシステムのアーキテクチャを2度見直しました。リポジトリも一度ゼロから作り直しましたし、インフラもVercel→ Railway→ Vercelと変更を重ねました(もう一度Railwayに切り替え予定)

他の方にも役立つ経験だったのでは、と思い何が必要だったのかなどを記事に残すことにしました。ます一般的な音声AIのアーキテクチャとその不足から話します

音声AIの基本的なアーキテクチャ

音声AIエージェントを実現する基本的なアーキテクチャは以下の2つです


音声AIの基本的なアーキテクチャ(ChatGPTによるイメージ図)

  1. パイプライン型
    • 以下の流れで音声を一度テキストに返還してから音声化する方式
      • 音声認識(ASR/Speech to Text: STT)
      • LLM(Text to Text)
      • 音声合成(Text to Speech: TTS
    • 時間がかかりやすいが、回答内容を制御しやすい。デバックも楽
    • オープンソースのモデルが充実しつつあり、費用を抑えやすい
  2. End to End型(E2E)
    • パイプライン型と異なりモデルは一つだけ。音声データに対して音声データを返す*
    • 応答時間は短いが、凝った回答がしにくい。デバックも難しい
    • オープンソースの良いモデルがなく、費用を抑えにくい
      *: モデルの前後にトークン化処理などが入る模様

アーキテクチャの変遷

パイプライン型で開発開始→ 出力と同期型tool callが音声対話にそぐわず

パイプライン型で開発開始

自分はE2E型のデメリットを懸念してパイプライン型で開発を始めました。ただし、すぐにその難しさに気付きます

出てきた問題1: LLMの出力が音声対話にそぐわない


音声向け出力とテキスト向け出力の違い(ChatGPTによるイメージ図)

それはLLMの一般的な出力が音声対話に適していないということです。

例えばDeep Researchの出力はかなりの長文です。これを音声で聞くのは耐え難い。テキストなら流し読みできますが、音声は全て聞く必要があります

LLMの主な学習データはWeb上のテキストであり、これが音声対話に適さないのは仕方ありません。その点ではE2E型に優位性があります

出てきた問題2: 音声対話ではtool callの結果を待てない


音声対話とテキスト対話の違い(ChatGPTによるイメージ図)

次の問題がtool callの待ち時間です。これはテキストAIエージェントでは問題になりにくいですが、音声AIエージェントでは致命的です

テキストAIエージェントを使う時は結果が文字として残るため、ユーザーは待ち時間で別の作業を行い、それが一段落したら結果を確認することができます。タブを複数開いて複数の相談を同時に回すことも可能です

音声会話は文字に残りませんし、突然音声が再開されても困ります。タブを複数開いて待つのも馴染まず、ユーザーが別作業をせずに1つの画面で待ち続ける想定が必要です。そうなるとtool callの待ち時間を減らす仕組みが必要でした

1度目のアーキテクチャ変更→ 非同期tool callの実装に苦しむ

非同期tool callの導入

AIと一緒にいくつか改善案を考えましたが、最終的に以下を導入することにしました

  • 非同期tool call

    • tool callを非同期で複数回せるようにし、終わったら音声AIエージェントが返答する形
    • tool callの待ち時間問題を解消
  • 音声向け出力とテキスト向け出力をstructured outputで分離

    • tool callの結果は基本的にテキスト向け出力としてユーザーに表示。音声向け出力はそれを要約して発話
    • 長過ぎる音声の出力を防ぐ
    • こちらは他にも良い方法がある気がしてます
LLM出力
├── voice_output: "検索結果は3件です。1つ目は..."(短く簡潔)
└── text_output: "【詳細結果】\n1. タイトル: ...\n   URL: ..."(詳細情報)

出てきた問題: 音声による非同期tool callは簡単ではない

ここで目指すアーキテクチャは大体固まりましたが、ここからも大変でした。非同期tool call自体の実装も大変ですが、関連して以下のような修正が必要でした。音声で非同期tool callをするのは大変でないと感じます

  • 割り込み時の対応

    • 音声は間違えやすいので基本的に割り込み対応(Barge In)を入れます。AIが発話中にユーザーが発話した時はAIの発話をキャンセルします
    • tool callが複数あるため、どれをキャンセルするか決める仕組みが必要に
  • tool call完了後の音声の取り出し方法

    • 上述の通り、非同期でのtool call完了後に突然音声を再開するとユーザーが戸惑うリスクが高いです
    • どのtool call結果を再生・表示するか決める仕組みが必要に

2度目のアーキテクチャ変更→ 現在も開発中

OpenAIのE2Eモデルで作り直し

結局、音声AIでの非同期tool callは実装し切れずでした。バグが出る→AIに修正指示→バグが残っているので再度修正指示、の繰り返し。普通の音声会話すらできなくなってしまいました

そんな中、以下の記事でE2E型のOpenAI Realtime APIで非同期function callingができることを知ります

https://zenn.dev/dxclab/articles/e7e2e44a6a824d

ここで上記の構成を目指して作り変える事にしました。OpenAI Realtime APIはWebRTC、websocket、ユーザーの発話検知(VAD)など、音声AIで必要な技術をカバーしており、それを使うことでコードをかなり減らすことができます。これなら早くプロトタイプを作れるのでは、と思いレポジトリごと作り直しました

その後出来上がったのが一番最初に共有したプロトタイプです。このプロトタイプの開発にも結構な苦労があったのですが、それは別途まとめたいと思います

音声AIは大変だが面白い

以上、音声AIエージェントとテキストAIエージェントの大きな違いを述べました

音声AIについては事前に一定勉強した(DeepLearning.aiの動画などを見た)上で開発しましたが、そこで紹介されていない問題が多く出ました。まだまだ世の中に知見が溜まっていないようにも感じます

しかし音声AIは面白い。普段の生活の中で、この作業音声でできんかなというのをよく考えるようになりました。色々と試行錯誤していこうと思います

Discussion