広告

Building Squared:Gemini Live API を使って、話しながらあなたをコーチする AI を作った方法

Dev.to / 2026/3/29

💬 オピニオンDeveloper Stack & InfrastructureSignals & Early TrendsTools & Practical Usage

要点

  • この記事では、話している最中に(録音を後から分析するのではなく)Gemini Live API を用いてリアルタイムの音声フィードバックを行う、AIによるプレゼンコーチングツール「Squared」について説明する。
  • これまでのAIスピーチコーチは、録画後に振り返る“事後分析(postmortem)”であるのに対し、Squaredは「ナビゲーター」としてユーザーの文脈をセッションをまたいで維持し、プレゼン中の改善を導くと主張する。
  • Squaredは、カメラと音声で話し手を監視し、これまでのパフォーマンス履歴に基づいたコーチングを割り込みで行う「リハーサルモード」を含む2つのモードを備えているとして位置づけられている。
  • 本稿では、製品コンセプトを可能にする鍵となる機能、すなわち音声の最中にAIが聞いて応答できる双方向のマルチモーダルなサブ秒レイテンシのストリーミングを強調している。

“そして主要な指標を見れば、はっきりと…その… ”

聞き覚えはありますか?何日もかけてリハーサルします。スライドは隅々まで頭に入っています。それなのに、本番のステージに上がった瞬間、緊張が襲ってきて、頭が真っ白になる。あの瞬間に助けてくれる人はいません――コーチは控室側、メモは遠すぎて読めないし、聴衆はもうどこかへ流れ始めています。

これまで私が使ってきたあらゆるプレゼンテーションのツールは、すべて「終わった後」にフィードバックを返してくれます。自分の様子を録画して、動画をアップロードして、分析を待つ。これは、出口から出て5分後に「曲がり損ねたよ」と言ってくれるGPSみたいなものです。次回には役に立つけど、今この瞬間には役に立たない。

GoogleがGemini 2.5 Flash Live APIをリリースしたとき――双方向でマルチモーダル、サブ秒のレイテンシでストリーミング――私は根本的に何かが変わったことに気づきました。初めて、AIがあなたを見て、あなたの話を聞き、そしてあなたが話している最中に応答できるようになったのです。録音の後ではない。テキストボックスの中でもない。リアルタイムで、あなたの耳元で。

こうしてSquaredが生まれました。

単なるコーチではなく、ナビゲーター
AIのスピーチコーチはたくさんあります。あなたを録画し、映像を分析して、レポートを渡します。「あなたのペースは180WPMでした。『ええと』を14回言いました。姿勢はスライド7で崩れました。」役に立つ情報です。でもそれはすべて事後のもの――すでに終わってしまったプレゼンの検死(ポストモーテム)に過ぎません。各セッションは孤立しています。コーチは、あなたがスライド7でこれまで3回も苦労してきたことを覚えていません。先週の火曜日に、そのまさにその切り替えを救った素晴らしい回復フレーズを見つけたことも知りません。1回分の実行を評価し、点数をつけて、すべてを忘れてしまうのです。

Squaredは違います。コーチであるだけでなく、ナビゲーターです。コーチはあなたのパフォーマンスを評価します。ナビゲーターはリアルタイムであなたと同乗し、先の道を把握し、過去に迷ってしまった曲がり角を覚えていて、それが来たときにそこへ導きます。Squaredは、すべてのセッションにまたがって文脈(コンテキスト)を持ち越します――あなたの履歴、弱点、最高の回復の仕方を知っていて、さらにそれらを「あとで」ではなく、プレゼンそのものの最中に使います。

Squaredには、人が実際にプレゼンを準備し、届ける方法をなぞる2つのモードがあります:

リハーサルモードは、コーチングが行われる場所です――ただし、記憶を伴うコーチングです。AIがカメラ越しにあなたを見て、話し方を聞き、発話によるフィードバックで能動的に割り込みます。速すぎますか?そう伝えます。このスライドで3回目のアイコンタクト喪失ですか?それを指摘し、これは今回だけの問題ではなく、直近3回のリハーサルにわたって繰り返し起きていることだとリマインドします。リハーサル中の途中でも質問できます。「このポイントはどう強調すればいい?」と聞けば、あなたの話し方のパターンについてAIが学んだすべてを踏まえた回答が返ってきます。これは確かにコーチングです――ただし、知識が積み上がり、それを使って先へ導くコーチングです。単に採点するだけではありません。

プレゼンテーションモードは、ナビゲーターがハンドルを握る場所です。ここが本当の舞台――生の聴衆、リアルな緊張感があります。AIは引き続き見て聞きますが、決して話しません。その代わりに、ビジュアルHUDを表示します――マイクロプロンプト、思考の流れを見失ったときの救助テキスト、ペース指標、自信(コンフィデンス)のメトリクス――すべてあなたにだけ見える形で。リハーサルに基づいて、どのスライドが壊れやすいかも知っています。あなたが始める前から、ゲームプラン(事前の作戦)を用意しています。聴衆には自信のある話し手として映ります。あなたには、ナビゲーションシステムが機能している様子が見えます。これから来る道を見越して、あなたを進路から外さないようにしてくれるのです。

2つのエージェント、1つのステージ
私が特に誇りに思っている設計上の決断は、Gemini Live APIセッションを2つ、並行して同時に動かすことです。

1つ目はDelivery Agentです。あなたの音声ストリームと映像フレームを処理し、5つのライブ指標(アイコンタクト、ペース、姿勢、つなぎ言葉、確信度)を追跡し、話しかける形のフィードバック(リハーサル)または話さない形の視覚的キュー(プレゼン)を生成します。Geminiのツール呼び出しを使って、UIがリアルタイムに描画するstructured updateIndicators payloadsを送ります。

2つ目はAudience Agentです。プレゼンテーションモードでは、ビデオ通話の聴衆と画面共有をしているときに、別のGemini Liveセッションが聴衆のビデオフィードを監視します。エンゲージメントの度合い、挙手、困惑した表情、チャットのリアクションを追跡します。これらの観察結果は、[AudienceAgent]メッセージとしてDelivery Agentのコンテキストに流し込みます。

なぜこれが重要なのでしょう?プレゼンに深く集中しているとき、物理的に聴衆をモニターすることは不可能です。複数のモニターがあったとしても、注意はコンテンツに向いてしまいます。Audience Agentは、人間のプレゼンターにこれまで存在しなかったもの――聴衆がどう反応しているかをリアルタイムに把握すること――をあなたに提供します。それがHUD上で、さりげないインジケーターとして提示されます。

大変だったのは、2つのエージェント間でコンテキストが混ざってしまうのを防ぐことです。独立した2つのGeminiセッションが並行してツール呼び出しを生成すると、互いに簡単に干渉してしまいます。解決策は状態合成レイヤーで、composeDualAgentOverlayState() が delivery と audience のフィードバックを統一されたオーバーレイに合成し、フィルタリングによって各エージェントの信号がきれいに保たれるようにしました。

個人的にする「メモリ」
ほとんどのAIツールはステートレスです。各セッションはゼロから始まります。これはチャットボットなら問題ありませんが、プレゼンコーチとしては最悪です。もし私が毎回リハーサルでスライド7につまずくなら、AIは私がスライド7に到達する前にそれを知っていてほしい。

Squaredは、私が「パターンメモリ」と呼ぶものを構築します――そしてそれは、幸運なタイミングによって実現しました。開発の途中でGoogleがGemini Embedding 2をリリースしたのです。私はすでに、セッションを孤立したものではなく、つながっているように感じさせる方法を考えていました。すると突然、軽量で高品質、意味的な検索のために目的特化された埋め込みモデルが登場したのです。まさにそれが、私が実際にこれを作る背中を押しました。

仕組みはこうです。各セッションの後、システムは体験を45秒のタイムウィンドウに分割(チャンク化)します。各チャンクは次の3種類のいずれかに分類されます。トランスクリプトウィンドウ(あなたが話した内容)、フラグが立った瞬間(何かがうまくいかなかった箇所)、リカバリーフレーズ(立て直したときにうまくいった内容)です。これらのチャンクは、Gemini Embedding API(256次元ベクトルの gemini-embedding-2-preview)を使って埋め込み(embedding)し、Google Cloud SQL上で pgvector 拡張を有効にしたPostgreSQLに保存します。

新しいセッションを開始すると、Squared はコサイン類似度による検索で、過去の最も意味的に近い瞬間を取り出し、それらを Gemini のシステムプロンプトに注入します。その結果、AIはたとえば次のようなことを言います。「動きのテンポがかなり速く、何度かアイコンタクトを失っています。これはこのスライドに対して繰り返し起きている問題です。ペースを落として、重要なフレーズを思い出してください。リハーサルモードはインタラクティブなAIのボイスコーチです。」

うまくいったこと、うまくいかなかったこと、そしてどこで苦戦したかを覚えています。リハーサルはすべて、前回の積み重ねです。

Gemini Embedding API が利用できない環境のために、SHA256ベースの決定論的(deterministic)なバックアップを作りました。類似検索の品質は低下しますが、システムは決してクラッシュしません。

自分の身体を読む(ローカルで)
最初の計画は、Gemini にすべてを任せることでした——すでに配信しているビデオのフレームから、アイコンタクトや姿勢の解析まで含めてです。モデルは私の姿を見られるのですから、ならば直接ボディランゲージを評価するよう頼めばいいはずでは?

しかし問題は、Live API の START_OF_ACTIVITY_INTERRUPTS モードでの挙動です(リハーサルモードがリアルタイムの音声フィードバックのために依存しているモード)。モデルが応答を生成している最中に、新しいユーザー入力によって中断されると、現在の生成をキャンセルして最初からやり直します。これは自然な会話のためにまさに欲しい挙動です——ただし、その結果として継続的なバックグラウンド解析が不安定になります。私はビデオフレームを送り、アイコンタクトや姿勢のデータを求めていましたが、私の発話によってモデルが何度も中断され続けるため、返答が一貫しないか、まったく返ってこないことがありました。データは粗すぎ、また遅すぎて、リアルタイムの指標を駆動できませんでした。

そこで、視覚解析を完全に Gemini から切り離し、ブラウザ側へ移しました。Squared はローカルで MediaPipe の FaceLandmarker と PoseLandmarker を実行し、次を追跡します:

アイコンタクト——単純な「カメラを見ている/見ていない」という二値ではありません。システムは、両目それぞれについて虹彩の位置を目の幅に対して追跡し、縦方向の視線方向を測定し、さらに多因子の重大度スコアリングを適用します。これは、メモを一瞬見ただけの違いと、観客に対して完全にアイコンタクトを失う状態の違いを捉えるのに役立ちます。

姿勢——3つの独立した指標:頭部の下がり(head drop)、肩の傾き(shoulder tilt)、左右方向の傾き(lateral lean)。それぞれを正規化し、1つのスコアに集約します。自信のある立ち姿勢と、ゆっくりと猫背へ崩れていく違いを検出します。

生の MediaPipe のランドマークは、フレーム間でかなりジッター(揺らぎ)が出ます。平滑化なしだとUIが常にチラつきます——アイコンタクトの指標は100msごとに緑と赤の間を跳ねてしまいます。解決策は多数決システムです。12サンプルのローリングウィンドウを用い、60%のコンセンサス閾値を設定します。表示される値は、明確な多数が一致したときにのみ変わります。さらに、ユーザーごとのキャリブレーションフェーズ(25のベースライン読み取り、中央値絶対偏差で正規化)と組み合わせることで、安定したパーソナライズされた視覚シグナルが得られます。

MediaPipe はこの用途にとても適していました——高速で、完全にクライアントサイドで動作し、このレイテンシではクラウドAPIには匹敵しないフレームごとのランドマークデータを提供してくれるからです。それでもなお、Gemini Live API が、音声によるアクティブな会話を維持しながら動画フレームを継続的に処理できるようなモードをサポートしてくれれば、非常に強力になるでしょう。つまり、割り込みによってキャンセルされないバックグラウンド解析のようなものです。そうなれば、リアルタイムのマルチモーダルアプリケーションの一群を丸ごと開けます

接続を生かし続ける
AudioWorklet そのもの——16kHzでPCMを取得し、Geminiへストリーミングすること——は最初の試行でうまくいきました。難しかったのは、そのストリームを確実に保つことです。

最初の驚きは、競合状態(レースコンディション)でした。オーディオワークレットは別スレッドで動き、PCMバッファをメインスレッドへ投稿します。メインスレッド側で base64 変換され、WebSocket 経由で送信されます。しかし、Promise のコールバックがまだ実行中にセッションが切断されると、死んだソケットへ音声を送ることになります。初期のバージョンではサイレントに失敗したり、例外が投げられたりしていました。修正は withOpenSession ガードです——送信のたびに接続状態を確認するラッパーで、アプリ内のリアルタイム通信の中心的なパターンになりました。

そしてさらに大きな問題があります。WebSocket 全体が死ぬ可能性があるのです。リハーサル中の接続断は面倒ですが、ライブプレゼン中なら致命的になり得ます。HUDが真っ白になり、ナビゲーションが消え、気がつくとあなたはステージで一人になってしまう。Squared は Gemini Live API からのレジューム用ハンドルを取得し、指数バックオフによる自動再接続(最大3回)を実装しています。会話が話している最中に接続が切れても、システムはコンテキストを完全に保持したまま再接続します。ユーザーは気づきません。コーチングは続きます。

withOpenSession ガードと再接続ロジックは、結局のところ同じコインの表裏でした。小さなスケールの問題(個々の音声パケットが死んだソケットに当たる)と、大きなスケールの問題(セッション全体が落ちる)——どちらも同じ設計原則が必要でした。接続が生きていることを決して信じず、常に確認し、静かに復旧すること。

1つのAPI、2つの性格
リハーサルモードでは、Gemini が音声によるフィードバックで能動的にあなたを中断する必要があります。プレゼンテーションモードでは、Gemini が完全に無音のままで、ツール呼び出しを通じてのみ伝える必要があります。

両モードは同じ Gemini 2.5 Flash Live API を使いますが、設定は根本的に異なります。リハーサルは START_OF_ACTIVITY_INTERRUPTS——AIはいかなる瞬間でも割り込むことができます。プレゼンテーションは NO_INTERRUPTION——AIがすべてを処理しますが、音声出力は一切生成しません。代わりに、すべてのフィードバックを updateIndicators ツール呼び出しでルーティングします。

同じAPIから両方の挙動を得るために、システム指示を調整するには想像以上の反復が必要でした。プレゼンテーションモードのプロンプト設計は特に繊細でした。AIには、役に立つマイクロプロンプトや復旧用の文章を生成できるだけの「意見性」が必要ですが、一方で、ライブトーク中にスピーカーへ流れてしまうような音声出力を決して試みないだけの「規律」も必要です。

Google Cloud での本番運用
Squared はローカルホストのデモではありません。Google Cloud 上で本番アプリケーションとして動作しており、インフラ上のあらゆる選択は、プロダクトに結びついた理由があります。

Pinecone のような管理されたベクターデータベース上ではなく、pgvector を使った Cloud SQL —— なぜならパターンメモリは、セッションデータ(実施、スライド、フィードバック)と密接に結びついているからです。埋め込みを、他のすべてを保存するのと同じ PostgreSQL インスタンスに保持しておけば、1つのトランザクションでコーチングの瞬間と、そのベクトル表現をまとめて保存できます。同期の問題がなく、最終的整合性もなく、管理すべき追加のサービスもありません。データベースはプライベート VPC Connector の背後にあり、インターネットに公開されることはありません。

Gemini APIキーをCORSの背後にあるブラウザに渡すことで作られる、一時的なLive APIトークン。サーバーは ai.authTokens.create() を介して短寿命トークン(TTL30分)を発行するため、万一トークンがクライアントから漏れても、誰かが悪用する前に期限切れになります。APIキーは決してサーバー外へ出ません。

インフラを管理せずにオートスケールするためのCloud Run — Squaredの負荷は波が激しい(大きなプレゼンの前にリハーサルのクラスタが動く)ため、待機状態のサーバーに課金されるのは合理的ではありません。Terraformがスタック全体を定義します — データベースからIAMロールまでをすべて用意する379行。ゼロから、出来上がって稼働するまでの新規デプロイは terraform apply 一回です。

メタ・デモ
Squaredのデモ動画には、私が意図していなかったのに、どんな台本でもそれ以上にこのプロジェクトの本質を捉えてしまった瞬間があります。

私はSquaredを紹介しています — 機能を説明し、仕組みを見せる。そしてSquared自体が、プレゼンを案内する私のナビゲーターです。カメラ越しに私を見ています。私の話し方を聞いています。そして、パターンメモリーの説明を急ぎ始めると — 私が最もよく知っていて、だからこそ毎回つい速めに進めてしまう部分 — それを捉えます。HUD上にライブのヒントが表示されます:スピードを落として。次のスライドは過去のリハーサルでは壊れやすかった。

再帰が要点です。Squaredはプレゼンテーションのためのツールであり、自分自身を使ってプレゼンし、その過程で仕事をこなします。事前収録された応答のある、いわゆる段取り済みデモではありません。これは実際のシステムで、ライブで動きながら、そこに関するプレゼンを通じて自分の制作者をナビゲートします。

最初のリハーサルから最終ステージへ。これが一つの瞬間に詰まった、プロダクト全体です。

学んだこと
Live APIは強力ですが、その制約に合わせて設計する必要があります — 存在しないふりをしてはいけません。START_OF_ACTIVITY_INTERRUPTSモードは、新しい入力のたびに進行中の生成をキャンセルします。だから自然な会話には最適ですが、連続したバックグラウンド分析には信頼性がありません。私は、Geminiから返ってきた視線や姿勢データがスカスカで遅延していたことで、これを痛いほど学びました。修正として、視覚分析をMediaPipeにローカルで移したところ、元の計画よりもシステムは速く、より頑健になりました。時には、強いられた回避策から最良のアーキテクチャが生まれます。

計画よりもタイミングが重要です。おそらくSquaredを定義しているといってよい機能であるパターンメモリーは、当初の設計には入っていませんでした。開発の途中でGoogleがGemini Embedding 2をリリースしたことで、可能になりました。教訓はこうです:まだ考えついていない機能のために、アーキテクチャ内の余白を残しておくこと。埋め込みパイプラインを既存のセッションフローに簡単に組み込めていなかったら、私は記憶なしで出荷していたでしょうし、その場合プロダクトは根本的に弱いものになっていたはずです。

新しいAPIの上に作るということは、回避策を出荷することでもあります。Presentation Modeは、テキストベースのツール呼び出しの応答だけで済むべきで、音声出力はまったく不要のはずでした。ですが、Modality.TEXTはまだLive APIでは期待通りに動きません。そのためモードはModality.AUDIOを有効にして動作し、音声ストリームを無言で破棄します。無駄ですが、動きます。重要なのは、プラットフォームが追いついたときに、その回避策がきれいに差し替えられるよう設計することです。

リンク
Live WEB Demo

Mac App

Source Code

Demo Video

Devpost Submission

広告