24時間、誰も操作しないのに配信し続けるAIアバターを作った — 人間に残ったのは「本人確認」と「目と耳」だけ
3D アバターが自動でライブ配信するシステムを作りました。人間がやるのは番組を登録することだけです。当日は誰も操作しません。
番組の時間になる
→ 配信枠を自動生成(YouTube)
→ クラウド GPU の Pod を起動
→ アバターがページ上で描画され、RTMP で送出開始
→ ライブへ遷移
→ 視聴者のコメントに音声で応答する
→ 投げ銭に金額帯に応じて反応する
→ コメントが途切れたら、番組テーマから自分で話題を出す
→ 尺が来たら締めの挨拶
→ 配信終了、Pod 破棄、アーカイブが残る
このシステムは、AI エージェントと一緒に、数週間かけて設計から実装、デプロイ、障害注入、長時間の検証まで進めました。この記事では前半で全体アーキテクチャとなぜその分割にしたのかを書き、後半で開発を通じて見えた人間にしか残らなかった役割を書きます。無人化を突き詰めると、最後まで機械に渡せない部分がどこなのかが逆にくっきりします。
全体像
┌──────────────┐
│ 番組管理 UI │ 番組の登録(キャラ・テーマ・尺・配信先)
└──────┬───────┘
│
┌──────▼───────┐ Pod の作成 / 死活監視 / 破棄
│ スケジューラ │──────────────────────┐
│(配信の一生) │ 配信枠の作成 / 遷移 / 終了 │
└──────┬───────┘ │
│ run(配信の実行単位) │
┌──────▼───────┐ ┌──────▼────────┐
│ 対話サーバ │◀── Redis ─────│ チャット収集 │
│(発話の生成) │ Stream │(YT / Twitch) │
└──────┬───────┘ └───────────────┘
│ WebSocket(表示専用)
┌──────▼───────┐
│ レンダラー Pod │ ヘッドレス Chromium + ffmpeg
│ (GPU) │──▶ tee ──▶ YouTube / Twitch
└──────────────┘
なぜこう分けたか
1. 「配信の一生」を管理する層を独立させた
いちばん最初に決めたのがこれです。配信は始まり、続き、終わるという寿命を持ちます。そして終わらないと課金が続きます。
この寿命管理を対話ロジックの中に入れると、対話のバグが課金の暴走に直結します。分けたことで、スケジューラは「配信が生きているか、終わったか、Pod は消えたか」だけを見ればよくなりました。
スケジューラが持っている責務はこれだけです。
- 時間が来たら配信の実行単位(run)を作る
- 配信枠を作り、ライブへ遷移させる
- GPU Pod を起動し、死活を監視し、死んだら別ホストで取り直す
- 尺が来たら締めに入り、終了させ、Pod を破棄する
- 一定時間でライブにならなければ失敗として片付ける
最後の 2 つが無いと、映像が出ないまま GPU の請求だけが積み上がります。
2. チャットの収集を、対話から切り離した
YouTube のコメントは Data API のポーリング、Twitch は IRC、投げ銭やサブスクは EventSub。取得方法がバラバラです。
ここを対話サーバに直接つなぐと、プラットフォームが増えるたびに対話ロジックが汚れます。収集専用のコンポーネントを置いて、すべてを内部の共通形式に正規化してから Redis Stream に流す構成にしました。
YouTube チャット ┐
Twitch IRC ├→ 正規化 → Redis Stream → 対話サーバ
Twitch EventSub ┘
対話サーバから見ると、入力は「イベントが流れてくるストリーム」1 本だけです。プラットフォームの知識をここに持ち込まずに済みます。
モデレーション(NG ワード、同一ユーザーの連投抑制)も収集側に置きました。汚れたものは入口で落とすほうが、あとの層が単純になります。
3. 発話は 1 本の直列ループにした
配信の対話は、Web のリクエスト処理とは性質が違います。同時に 2 つしゃべれません。
複数のコメントが同時に来ても、アバターの口は 1 つです。なので発話は完全に直列化し、優先度付きのキューで順番を決める構造にしました。
- 投げ銭やサブスクなどのイベント → 優先度高
- 通常のコメント → 優先度中
- 沈黙が続いたときの自発的な話題 → 優先度低
- 番組終了時の締めの挨拶 → 最優先
コメントが大量に来たときは、複数件をまとめて 1 回の発話にする処理も入れています。1 件ずつ返していると、視聴者から見て「さっきのコメントの話をまだしている」状態になるためです。
4. 状態を Redis に置いて、ページを使い捨てにした
レンダラーは落ちます。GPU ホストが不安定だったり、ブラウザがクラッシュしたり、ffmpeg が死んだりします。
そこで、配信の状態(何を話したか、誰と話したか)をページ側に持たせない設計にしました。状態は Redis のスナップショットに置き、ページは接続して現在の状態を受け取るだけの表示専用クライアントです。
これにより、ページが落ちても読み込み直して再接続すれば、続きから復帰できます。再接続のたびに挨拶をやり直すという間抜けな挙動も、スナップショットに「挨拶済み」が入っているので起きません。
5. 映像の生成を、対話から物理的に切り離した
レンダラーは別の Pod、別のクラウド、別の GPU で動きます。やることは「ページを開いて、画面と音を撮って、RTMP に流す」だけです。
この分離のおかげで、
- レンダラーは使い捨てにできる(不良ホストなら引き直す)
- 対話サーバは自社の推論基盤の近くに置ける(LLM と TTS はここにある)
- 相乗り(1 つの GPU に複数の配信)が自然にできる
という性質が出ました。壊れやすい部分を、壊れても困らない位置に置く。
実測値
参考までに、検証で取れた実測値をいくつか。
| 項目 | 値 |
|---|---|
| Pod 作成からライブ遷移まで | 95 秒 |
| GPU 1 枚あたりの同時配信数 | 4 体(720p30) |
| 1 体あたりの費用 | 月 ¥7,600(24 時間)/ 月 ¥2,500(1 日 8 時間) |
| コメントへの応答遅延 | 25〜45 秒(うち 15〜30 秒はプラットフォームの視聴遅延) |
| 障害からの自動復帰 | 41 秒(サーバ再起動からの完全復帰) |
| 連続稼働の検証 | 2 時間(メモリ増加なし、53 ターン全計測) |
障害からの復帰は、実際に Pod を消したりサーバを再起動したりして計測しました。「実際に壊して直ることを確かめる」ところまで含めて検証しています。
いちばん難しかったのは「確実に終わらせる」こと
技術的に難しかったのは、実は対話でも描画でもなく、**「確実に終わらせる」**ところでした。
始まらない障害はすぐ気づきます。誰も見られないので。終わらない障害は、請求書か、翌朝の「まだ配信してる」で気づきます。自動化の怖さは、失敗より成功の継続にあるというのが、いちばん実感した部分です。
各コンポーネントに「自分が異常だと判断したら止まる」条件を入れて回ることになりました。
ここまでがアーキテクチャの話です。ここからは開発の進め方の話に移ります。この一連の作業は AI エージェントと一緒に進めたのですが、途中で気づいたのは、人間が呼ばれる場面がはっきり 3 種類に固まっていたことです。それ以外はほぼ全部、エージェント側で完結していました。
エージェント側で完結したこと
先に、人間が関与しなかった作業を挙げます。
- 設計と仕様の文書化
- 実装(バックエンド、スケジューラ、レンダラー、管理画面)
- コンテナのビルドとデプロイ
- 障害注入(Pod を消す、サーバを再起動する)と復帰時間の計測
- 実測(GPU 1 枚に何体、コスト、遅延の内訳)
- ログの調査と原因の特定
- 記録と引き継ぎ
前半で挙げた実測値や 41 秒の復帰時間も、この工程で出たものです。手が動く範囲は、想像よりずっと広い。
人間にしかできなかったこと1: 本人性を要求される操作
いちばん多かったのがこれです。
- 配信プラットフォームのアカウント作成
- 電話番号による本人確認(そしてライブ配信が有効になるまでの 24 時間待ち)
- 2 要素認証の設定
- OAuth の認可画面でボタンを押す
- クラウドサービスの支払い方法の登録とチャージ
- 開発者アプリの登録
共通しているのは、「あなたが本人であること」を要求される操作です。ここは技術的な難しさではなく、そもそも代行してはいけない領域です。エージェントが代行できたとしたら、そのサービスの本人確認が壊れているということになります。
実務的に重要なのは、これらが「待ち」を生むことです。電話確認から有効化まで 24 時間、というのはコードでは短縮できません。
開発中は、人間の宿題リストを常に持っていました。「今エージェント側で進められること」と「人間がやらないと先に進めないこと」を分けて、宿題を先に片付けてもらう。これをやらないと、実装が完成してから 24 時間待つことになります。実際、宿題を後回しにして「認可待ち」で 1 日止めたことが何度かありました。
人間にしかできなかったこと2: 知覚が必要な品質判断
2 つ目は、見る・聞くことでしか判断できないものです。
| 判断 | なぜ機械で決まらないか |
|---|---|
| リップシンクの遅延を 0.10 秒にする | 「合って見える」は知覚の問題。数値に正解がない |
| 描画のチラつきが許容できるか | fps・エラー・GPU 使用率のどれにも出ない |
| 声が自然に聞こえるか | 波形の指標と主観が一致しない |
| 配信全体の「見られる品質」 | 総合判断 |
特に象徴的だったのが描画のチラつきでした。性能指標が全部正常のまま、キャラクターの顔だけが明滅するという不具合です。fps もエラーも GPU 使用率も何ひとつ異常を示していませんでした。グラフィックスの設定を変えたら直りましたが、そもそも「壊れている」と気づけたのは人間が映像を見たからでした。
ここから運用を変えて、描画や音声に関わる変更は、必ず人間が実物を見聞きしてから確定することにしました。これは AI の能力の問題というより、判断の基準が人間の知覚の中にしかないという性質の問題です。前半で「レンダラーは使い捨て」と書きましたが、その品質を最終的に合格にする工程だけは人間の目と耳に残りました。
人間にしかできなかったこと3: 公開の意思決定
3 つ目は、外に出す判断です。
- 配信を限定公開のままにするか、公開するか
- AI 生成コンテンツであることをどう開示するか
- キャラクターに何をしゃべらせてよいか
技術的には全部いつでも実行できます。実行してよいかどうかが別問題です。責任の所在が人間にある判断は、能力ではなく権限の話として人間に残ります。
「限定公開で検証し、公開に切り替えるのは人間の判断」という線を最初から引いていました。エージェント側もその線を前提に動きます。
分界を先に決めておくと、開発が止まらない
まとめると、人間に残ったのはこの 3 つでした。
- 本人性を要求される操作(アカウント、認可、支払い)
- 知覚が必要な品質判断(見る、聞く)
- 公開の意思決定(責任を伴う判断)
逆に言うと、それ以外はエージェント側で回るということでもあります。設計・実装・デプロイに加えて、障害注入・計測・原因特定まで含めてです。
実務でいちばん効いたのは、この分界をプロジェクトの最初に洗い出したことでした。「人間の宿題」を早めに渡しておくと、実装が終わったときに待ちが発生しません。
もうひとつ気づいたのは、**この 3 つはどれも「代わりにやれるが、やってはいけない」**という性質を持っていることです。技術的な限界ではありません。だからこそ、能力が上がっても分界はあまり動かないだろうと思っています。
まとめ
アーキテクチャ側:
- **配信の寿命管理を独立させる。**対話のバグが課金の暴走にならないように
- **プラットフォーム差はイベント収集層に閉じ込める。**対話は正規化されたストリーム 1 本だけを見る
- **発話は直列 + 優先度キュー。**口は 1 つしかない
- **状態は外(Redis)に置き、ページは使い捨てにする。**落ちても復帰できる
- 壊れやすい部分(レンダラー)を、壊れても困らない位置に置く
- 自動配信でいちばん難しいのは、確実に終わること
進め方の側:
- 設計・実装・デプロイ・障害注入・計測・原因特定まではエージェント側で完結した
- 人間に残ったのは 3 つ: 本人性を要求される操作 / 知覚が必要な品質判断 / 公開の意思決定
- 本人確認や認可は待ち時間を生む。宿題として早めに渡さないと開発が止まる
- 描画や音声の品質は、性能指標が全部正常でも壊れていることがある。実物を見聞きする工程を残す
- この 3 つは能力の限界ではなく、代わりにやるべきでない領域。だから今後も残る
無人配信は、ほとんどの工程を機械に渡せます。GPU の選定、ブラウザのメディア API、プラットフォーム API の癖、耐障害設計、どれも単独で 1 記事ぶんの罠がありましたが、そこはエージェント側で片付きました。そのうえで最後まで残ったのは、「本人であること」「見て聞いて判断すること」「公開の責任を負うこと」でした。「AI に何ができないか」を探すより、「何を人間がやるべきか」を先に決めるほうが、実務としては進みが速いというのが結論です。