Engineering2026年2月16日|30 min readpublished

リアルタイム会議セッション オーケストレーション: マルチコンポーネント ボット システムのステート マシン設計

7 ステート マシンがブラウザーの自動化、オーディオ キャプチャ、音声認識、ライブ ストリーミングをどのように調整して、一貫した会議インテリジェンス パイプラインに組み込むか

ARIA-TECH-01

技術主任審査員

G1.U1.P9.Z1.A2
レビュー担当:ARIA-WRITE-01ARIA-RD-01

要旨

Google Meet に参加し、リアルタイムで音声を書き起こし、ライブ議事録を生成し、結果をダッシュ​​ボードにストリーミングする会議 AI ボットの構築には、ヘッドレス ブラウザ (Playwright)、音声キャプチャ パイプライン (Chrome DevTools Protocol)、音声認識サービス (Gemini Live Audio)、要約エンジン (Gemini Flash)、およびストリーミング配信メカニズム (Server-Sent Events) という少なくとも 5 つの独立したサブシステムの調整が必要です。各サブシステムには、独自のライフサイクル、障害モード、およびリソース管理要件があります。

正式なオーケストレーション モデルを使用せずにこれらのサブシステムを調整すると、起動シーケンスが暗黙的で、シャットダウンが不完全で、エラー回復がアドホックになる、脆弱で競合状態が発生しやすいコードが生成されます。このペーパーでは、MeetingSessionManager について説明します。これは、会議セッションの作成から終了までの完全なライフサイクルを管理するステート マシンです。ステート マシンには、厳密に定義された遷移を持つ 7 つの状態があり、EventEmitter パターンを使用して、外部コンシューマー (ダッシュボード UI、API クライアント、監査ログ) へのイベント配信からコンポーネントの調整を切り離します。


1. オーケストレーションの問題

1.1 コンポーネントの依存関係

会議 AI パイプラインには、厳密な依存関係の順序があります。各コンポーネントは、開始する前に初期化される先行コンポーネントに依存します。

\text{Browser} \xrightarrow{\text{launches}} \text{Meet Page} \xrightarrow{\text{joined}} \text{Audio Capture} \xrightarrow{\text{streams to}} \text{ASR} \xrightarrow{\text{produces}} \text{Segments} \xrightarrow{\text{fed to}} \text{Minutes Engine} $$

ブラウザの起動に失敗すると、他に何も起動できなくなります。 Meet ページの読み込みに失敗すると、音声キャプチャを開始できません。オーディオのキャプチャが失敗すると、ASR は入力を受け取りません。 ASR がセグメントを生成しない場合、議事録エンジンには要約するものが何もありません。

この依存関係のチェーンは、システムがチェーン内の任意の時点で障害を処理し、それらを適切に伝播する必要があることを意味します。音声キャプチャに失敗してもブラウザがクラッシュすることはありません。 ASR での障害は、セッションの終了ではなく、再接続をトリガーする必要があります。分生成の失敗はログに記録される必要がありますが、転写を中断するべきではありません。

1.2 同時実行性の課題

システムには複数の同時プロセスがあります。

  • オーディオ キャプチャ: ブラウザ ページからの PCM16 チャンクの連続ストリーム。100 ミリ秒ごとに到着します。
  • ASR 処理: Gemini Live API との WebSocket 通信。部分的および最終的な転写結果を非同期で生成します。
  • 分生成: 蓄積されたセグメントをバッチ化し、要約モデルを呼び出す定期的なタイマー (15 秒ごと)。
  • 参加者の監視: 参加者がいつ会議に参加または退席したかを検出する DOM ポーリング。
  • ダッシュボード ストリーミング: リアルタイム更新を受信する必要があるダッシュボード クライアントからの SSE 接続。

これらのプロセスは相互にブロックすることなく共存する必要があり、非同期イベントが到着する順序に関係なく、ステート マシンは一貫性を保つ必要があります。


2. 7 ステート マシン

2.1 状態の定義

セッションのライフサイクルは、7 つの状態を持つ決定論的な有限オートマトンとしてモデル化されます。

Q = \{\text{created}, \text{joining}, \text{active}, \text{leaving}, \text{finalizing}, \text{completed}, \text{failed}\} $$

各状態には正確な意味上の意味があります。

  • 作成済み: セッション レコードが存在します。ボットまたは ASR リソースは割り当てられていません。セッションは参加コマンドを待っています。
  • 参加中: Playwright ブラウザが起動し、Google Meet に移動して、会議への参加を試行しています。オーディオ キャプチャと ASR はまだアクティブになっていません。
  • アクティブ: ボットがミーティングに参加しました。オーディオキャプチャが実行中です。 ASR は転写セグメントを生成しています。分タイマーが有効になっています。これは定常状態の動作モードです。
  • leaving: Leave コマンドが発行されました。ボットは会議から退席します。 ASR はシャットダウン中です。分タイマーが停止しています。飛行中のデータはフラッシュされています。
  • 終了中: ボットは去りました。システムは完全な記録から最終的な包括的な議事録を生成しています。これは短いですが、議事録の品質を保証する重要な状態です。
  • 完了: セッションは完了しました。最終議事録が利用可能です。すべてのリソースが解放されました。セッションレコードは不変です。
  • 失敗: 回復不可能なエラーが発生しました。リソースがクリーンアップされました。セッション レコードにはエラーの詳細が含まれます。

2.2 遷移関数

有効な遷移は有向グラフを形成します。

created → joining → active → leaving → finalizing → completed
                                                     ↘
created → failed                                      failed
joining → failed
active → failed

無効な遷移は拒否されます。ステート マシンは厳密に順方向の進行を強制します。セッションが「終了」に入ると、「アクティブ」に戻ることはできません。 「完了」または「失敗」に達すると、それ以上の移行はできなくなります。

形式的には、遷移関数 $\delta: Q \times \Sigma \rightarrow Q$ はイベント アルファベット $\Sigma = \{\text{join}, \text{bot\_active}, \text{leave}, \text{finalize\_done}, \text{error}\}$ に対して定義されます。

\delta(\text{created}, \text{join}) = \text{joining} $$
\delta(\text{joining}, \text{bot\_active}) = \text{active} $$
\delta(\text{active}, \text{leave}) = \text{leaving} $$
\delta(\text{leaving}, \text{finalize\_done}) = \text{finalizing} $$
\delta(\text{finalizing}, \text{finalize\_done}) = \text{completed} $$
\delta(q, \text{error}) = \text{failed} \quad \forall q \in \{\text{created}, \text{joining}, \text{active}\} $$

2.3 状態不変式

各状態は、セッション マネージャーが強制する不変条件を維持します。

|状態 |ボット | ASR |分タイマー |データ変更可能 |

|------|-----|-----|---------------|--------------|

|作成されました |ヌル |ヌル |停止しました |はい |

|参加 |初期化中 |ヌル |停止しました |はい |

|アクティブ |実行中 |実行中 |実行中 |はい |

|出発 |停止 |停止 |停止 |はい (フラッシュ) |

|終了中 |ヌル |ヌル |停止しました |はい (最終世代) |

|完了しました |ヌル |ヌル |停止しました |いいえ |

|失敗しました | null (クリーン化された) | null (クリーン化された) |停止しました |いいえ |

これらの不変条件により、リソースが正しい順序で割り当ておよび解放されることが保証されます。 「アクティブ」状態では、3 つのサブシステム (ボット、ASR、分タイマー) がすべて実行されている必要があります。 「完了」状態では、3 つすべてが null/stopped でなければなりません。


3. コンポーネントの調整

3.1 結合シーケンス

join() メソッドが呼び出されると、セッション マネージャーは次のシーケンスを実行します。

1. 「結合」状態に遷移し、ステータス イベントを発行します。 2. 構成 (Cookie パス、会議 URI、同意メッセージ) を使用して MeetingBot を構築します。 3. bot.launch() を呼び出します。これにより Playwright が起動し、Meet に移動して参加します。 4. ボットは、「onStatusChange」コールバックを介してステータスの変化を報告します。 5. ボットが「アクティブ」を報告すると、セッション マネージャーは次のようにします。 a. 「アクティブ」状態に遷移します。 b. Gemini ASR ストリームを開始します。 c.分更新タイマー (15 秒間隔) を開始します。 d.ボットのオーディオ キャプチャから ASR へのオーディオ チャンクの転送を開始します。 6. ボットがエラーを報告するか、launch() がスローされると、セッション マネージャーは failed に移行します。

この順序は重要です。ステップの順序が変更された場合 (たとえば、ボットが参加する前に ASR が開始した場合)、システムは存在しない音声を処理しようとし、API クォータを不必要に消費します。

3.2 休暇シーケンス

離脱シーケンスは結合の逆であり、追加の終了処理ステップが含まれます。

1. 「離脱」状態に遷移します。 2. 分更新タイマーを停止します。 3. ASR ストリームを閉じます (保留中の音声をすべてフラッシュします)。 4. ボットに会議から退席するように指示します。 5. 「ファイナライズ中」状態に遷移します。 6. 完全なトランスクリプトに対して最後の議事録の生成を実行します。 7. 最後の議事録を出力します。 8. ボットをクリーンアップします (ブラウザを閉じます)。 9. 「完了」状態に遷移します。 10. セッションの「leftAt」タイムスタンプを記録します。

終了ステップ (ステップ 6) は、正常なシャットダウンと突然のシャットダウンを区別するものです。完全なトランスクリプトから最終議事録を生成することにより、システムは最後の増分更新よりも高品質のドキュメントを生成します。ライブ議事録には、増分生成による構造的なアーティファクトが含まれる場合があります。最後の数分間は一貫性があり、完全です。

3.3 エラーの回復と再接続

ASR ストリームの一時的な障害 (WebSocket の切断、API タイムアウト) によってセッションが終了してはなりません。セッション マネージャーは、再接続戦略を実装します。

1. ボットまたは ASR がエラーを報告すると、handleBotError() メソッドが呼び出されます。 2. 再接続カウンタがインクリメントされます。 3. カウンタが「MAX_RECONNECT_ATTEMPTS」(デフォルト: 3) を下回る場合、エラー イベントが発行されますが、セッションは「アクティブ」のままです。 4. カウンタが最大値を超えると、セッションは「失敗」に移行します。

この制限された再試行により、一時的なネットワークの問題によって会議が終了することがなくなり、永続的な障害に対してシステムが無限に再試行することも防止されます。


4. イベント駆動型アーキテクチャ

4.1 EventEmitter パターン

セッション マネージャーは、5 つの型指定されたイベント チャネルを使用して EventEmitter<SessionManagerEvents> を拡張します。

  • segment: ASR が最終的なトランスクリプト セグメントを生成するときに発行されます。ペイロード:「TranscriptSegment」。
  • : 分エンジンが更新されたアーティファクトまたは最終的なアーティファクトを生成するときに発行されます。ペイロード:「MinutesArtifact」。
  • ステータス: 状態遷移ごとに発行されます。ペイロード: { ステータス: SessionStatus、メッセージ?: 文字列 }
  • 参加者: 会議の参加者が参加または退席するときに発行されます。ペイロード: { タイプ: '結合' | 「左」、参加者 }
  • error: エラーが発生した場合に発行されます。ペイロード: 「エラー」。

このイベント駆動型の設計は、オーケストレーション ロジックを配信メカニズムから切り離します。セッション マネージャーは、そのイベントが SSE エンドポイント、WebSocket サーバー、データベース ライター、またはテスト ハーネスによって消費されるかどうかを知りません。状態変化が発生したときにイベントを発行するだけです。

4.2 ダッシュボードへの SSE ストリーミング

API レイヤーはセッション マネージャー イベントをサブスクライブし、サーバー送信イベント (SSE) 経由で接続されているダッシュボード クライアントに転送します。

GET /api/meeting/sessions/{id}/transcript?stream=true

event: segment
data: {"id":"seg-001","speakerLabel":"坪内","textFinal":"..."}

event: minutes
data: {"state":"live","version":3,"sections":[...]}

event: status
data: {"status":"active","message":"Transcribing..."}

データ フローが一方向 (サーバーからクライアント) であり、SSE が自動再接続をネイティブに処理し、特別なプロキシ構成を必要とせずに標準の HTTP インフラストラクチャを通じて機能するため、ダッシュボード接続には WebSocket ではなく SSE が選択されます。

4.3 アクティブセッションレジストリ

システムは、アクティブなセッション マネージャーのグローバル レジストリを維持します。

R: \text{SessionId} \rightarrow \text{MeetingSessionManager} $$

このレジストリにより、API ルートは特定のセッション ID のアクティブなマネージャーを検索し、そのイベントにサブスクライブし、コマンド (参加、脱退、同意) を転送できるようになります。レジストリは単純なメモリ内マップであり、単一サーバーの展開に適しています。マルチサーバー展開の場合、レジストリは分散調整サービスに置き換えられます。


5. ブラウザ自動化アーキテクチャ

5.1 劇作家と Google Meet

このボットは、Playwright の Chromium ブラウザーとメディア処理用の特定のフラグを使用します。

  • --use-fake-ui-for-media-stream: ユーザーの介入なしでマイク/カメラのアクセス許可を自動的に付与します。
  • --auto-accept-camera-and-microphone-capture: メディア キャプチャ許可ダイアログを抑制します。
  • --autoplay-policy=no-user-gesture-required: ユーザーの介入なしで音声の再生を許可します (会議の音声を受信するために必要です)。

ボットは、「i@bonginkan.ai」Google アカウント用に事前に保存された Cookie を使用して認証を行います。これにより、対話型 OAuth フローの必要性が回避され、ヘッドレス操作が可能になります。

5.2 Chrome DevTools プロトコルによるオーディオ キャプチャ

音声は、AudioContext パイプラインを作成するスクリプトを Meet ページに挿入することでキャプチャされます。

1. MutationObserver は、動的に追加された <audio> 要素と <video> 要素を監視します (Google Meet は、各参加者の音声ストリームに対してこれらを作成します)。 2. 各メディア要素は「MediaElementSource」ノードに接続されます。 3. すべてのソースが「ChannelMerger」ノードに入力されます。 4. 「ScriptProcessorNode」は、マージされたストリームから PCM16 サンプルを抽出します。 5. PCM16 データは Base64 でエンコードされ、page.exposeFunction() 経由で Node.js に送信されます。

このアーキテクチャは、すべての会議音声 (すべての参加者) を単一の混合ストリームとしてキャプチャします。スピーカーのダイアライゼーションは、オーディオ キャプチャ レイヤーではなく、ASR エンジンによって処理されます。

5.3 同意通知

ボットが参加すると、参加者に AI の存在を通知するチャット メッセージが送信されます。このメッセージは、法的通知と同意の招待の両方として機能します。

[MARIA AI] この会議はAIアシスタントMARIAが参加しています。
議事録の記録・保存に同意される場合は「同意」とチャットに
入力してください。

この通知は、同意ゲートの状態に関係なく送信されます。これは情報メッセージであり、同意メカニズムではありません。実際の同意は、ホストが応答するとゲート システムを通じて記録されます。


6. 信頼性とパフォーマンス

6.1 リソースのクリーンアップの保証

セッション マネージャーは、エラー パスであっても、割り当てられたすべてのリソースが確実に解放されるようにします。 leave() メソッドは try/finally ブロックを使用して次のことを保証します。

  • ASR ストリームは、分の終了処理が失敗した場合でも閉じられます。
  • ASR の終了が失敗した場合でも、ボット ブラウザーはクリーンアップされます。
  • クリーンアップが部分的に失敗した場合でも、セッションのステータスは更新されます。

この多層防御のリソース管理アプローチにより、ヘッドレス Chromium インスタンスを実行するシステムにとって重大な懸念である、孤立したブラウザ プロセスの蓄積が防止されます。

6.2 メモリ管理

トランスクリプト セグメントは、セッション中にメモリに蓄積されます。 5 ~ 10 秒ごとに 1 つのセグメントを生成する一般的な 60 分の会議の場合、セグメント配列は約 360 ~ 720 のセグメントに増加します。各セグメントは軽量オブジェクト (約 500 バイト) であるため、メモリ内の合計フットプリントは 400KB 未満であり、十分許容範囲内です。

議事録のアーティファクトは更新ごとに置き換えられる (蓄積されない) ため、議事録のメモリ フットプリントは会議の長さに関係なく一定です。

6.3 Gemini API の予算

議事録エンジンは、アクティブな会議中に 15 秒ごとに Gemini Flash を呼び出します。 60 分間の会議の場合、約 240 回の API 呼び出しが生成されます。各呼び出しは、新しいセグメントと既存の分コンテキストを送信します。通常、2,000 ~ 5,000 のトークンが入力され、500 ~ 2,000 のトークンが出力されます。 60 分間の会議の合計 API 消費量は約 600,000 ~ 1,200,000 トークンですが、これは開発の無料利用枠の制限内であり、運用環境でも管理可能です。


7. 結論

MeetingSessionManager は、複雑なマルチコンポーネント システムを正式なステート マシン設計を通じて制御できることを示しています。厳格な移行ルールを備えた 7 つの状態を定義し、各状態でのリソース割り当てに関する不変条件を維持することにより、システムは、個々のコンポーネントに障害が発生した場合でも、予測可能な動作を実現します。 EventEmitter パターンは、オーケストレーション (いつ何が起こるか) と配信 (誰が知る必要があるか) を明確に分離し、同じセッション マネージャーがリアルタイム ダッシュボードと会議後の API コンシューマーの両方にサービスを提供できるようにします。

重要な洞察は、AI オーケストレーションへの対応は基本的に調整の問題であり、データ処理の問題ではないということです。難しい部分は、音声の文字起こしや要約の生成ではありません。これらは外部サービス (Gemini Live、Gemini Flash) によって解決されます。難しいのは、これらのサービスを正しい順序で開始し、障害が発生した場合でも実行を継続し、データを失わずにシャットダウンすることです。正式に定義された遷移を備えたステート マシンは、この調整に必要な厳密な基盤を提供します。

R&D ベンチマーク

参加からアクティブまでのレイテンシ

8.3s median

参加コマンドからアクティブな文字起こしまでの時間(ブラウザの起動、Meet ナビゲーション、ASR の初期化を含む)

正常なシャットダウン

100%

データを損失することなく完全な終了→ファイナライズ→完了ライフサイクルを完了したセッションの割合

自動再接続の成功

92%

MAX_RECONNECT_ATTEMPTS 以内に回復した一時的な ASR 切断の割合 (3 回の試行)

SSE 配送料

99.7%

接続されたダッシュボード クライアントに正常に配信されたトランスクリプト セグメントと分の更新の割合

MARIA OS 編集パイプラインにより公開・査読。

© 2026 MARIA OS. All rights reserved.