AIが作り、AIが配信し、AIが落ちる — 誰も見ていないシステムの品質保証
AI アバターが YouTube と Twitch に無人で配信し続けるシステムを作りました。番組を登録しておけば、時間になると配信枠が作られ、GPU が起動し、視聴者のコメントに音声で応答し、尺が来たら締めて終わります。当日、人間は何もしません。
作っている間、ずっと引っかかっていたのが「これ、何をテストすれば品質を保証したことになるんだ」でした。出力は毎回違う。正しさは主観。そして一番厄介なことに、失敗しても止まらない。誰も見ていないので、誰も気づかない。
この記事は、その状況で「保証する対象を組み替えた」記録と、その一部として避けて通れなかった「長時間でしか出ないバグ」への向き合い方をまとめたものです。前半は QA の設計思想、後半はソークテストの実務です。開発者向けに、つまずき → 原因 → 解決の順で書きます。
従来のテストが前提にしていた3つのこと
書いていて気づいたのは、これまでのテストが暗黙に置いていた前提が、3 つとも成り立たないということでした。
| 前提 | 無人 AI 配信では |
|---|---|
| 入力が決まっている | 視聴者が何を書くか分からない |
| 出力が判定できる | LLM の出力は毎回違う。正解の文字列がない |
| 失敗すると止まる | **失敗しても止まらない。**無言のまま配信が続く |
3 つ目が一番厄介でした。普通のシステムは、壊れると例外が飛び、リクエストが失敗し、誰かが気づきます。**配信は壊れても流れ続けます。**映像は出ている。プロセスも生きている。ヘルスチェックも緑。ただアバターが一言もしゃべらない。
これは実際に起きた障害です。対話サーバだけが再起動したとき、途中の層が切断を下流に伝えていなかったため、**視聴者から見ると「AI が黙り込んだ配信」**になっていました。どの監視項目にも出ません。この「生きているのに機能していない」状態は、この記事を通して何度も出てきます。無人運用における最悪の状態だからです。
保証する対象を組み替える
そこで、保証の対象を出力から振る舞いへ移しました。「アバターが良いことを言うか」は保証できません。保証できるのは、システムが期待どおりに振る舞うかです。
具体的には 4 つに絞りました。
1. 止まるべきときに止まる
自動化されたシステムで怖いのは、失敗ではなく成功の継続です。誰も見ていないので、動き続けている限り誰も気づきません。そして GPU の課金は続きます。
止まる条件を明示的に実装しました。
- 番組の尺が来たら、締めの挨拶をして終了する
- 一定時間ライブにならなければ、失敗として片付けて Pod を破棄する
- レンダラーが短時間に復帰を繰り返したら、自分でプロセスを終了する
3 つ目は「自分から死ぬ」実装です。直感に反しますが、異常を上位に伝える最も確実な手段でした。
2. 壊れたら気づける
「生きているのに機能していない」状態を、最悪の状態として定義しました。
そのうえで、各層に「上流が死んだらどう振る舞うか」を明示しました。既定の振る舞いは、たいてい「何もしない」です。何もしないと、切断が途中の層で吸収されて消えます。冒頭の「黙り込んだ配信」は、まさにこれが原因でした。
- 上流の切断を検知したら、自分の下流の接続も切る
- 切られた側は再読み込みし、再接続する
- 再接続が失敗しても、上流が戻るまでリトライする
**死を伝播させる。**可用性のために生き延びようとする層が、全体の可用性を下げていました。
3. 壊れたら戻る(そして戻ることを実測する)
復帰処理は、書いただけでは動きません。実際に壊して測りました。
| 壊し方 | 復帰 |
|---|---|
| 配信中に GPU の Pod を手で削除 | 32 秒で検知、別ホストで再開 |
| 対話サーバをローリング再起動 | 41 秒で完全復帰(状態の復元込み) |
障害注入は、それぞれ数分の作業です。**やらずに書いた復帰コードは、たいてい一箇所で止まります。**我々も、実際に壊してみて初めて「再接続のリトライがないと、再起動中に当たったとき止まる」ことに気づきました。前節の「リトライを入れる」は、机上ではなくこの実測から出た対策です。
4. 出してはいけないものを出さない
出力そのものは判定できませんが、出力の境界は判定できます。
- チャットの入口で NG ワードと連投を落とす(汚れたものは入口で止める)
- AI 生成コンテンツであることをプラットフォームに申告する
- 検証中は限定公開に固定し、公開に切り替えるのは人間の判断にする
「何を言うか」ではなく「何を入れるか、どう出すか」を制御する、という発想の切り替えです。
それでも人間が見る場所を残す
すべてを機械で判定できるわけではありません。実際、性能指標が全部正常なまま映像だけが壊れる不具合がありました。キャラクターの顔が明滅しているのに、fps もエラーも GPU 使用率も正常。
見つけたのは人間の目です。以降、描画や音声に関わる変更は、実物を見聞きしてから確定する運用にしました。
自動化できる検査を増やすほど、人間が見る場所を意識的に残す必要がある、というのが逆説的な学びでした。全部緑のダッシュボードは、全部正常であることを意味しません。
長時間でしか出ないものがある
ここまでの検査を短時間で全部通しても、まだ足りませんでした。2 時間回すと壊れるものがあるからです。10 分のテストは全部通っていたのに、本番相当の長さで回すと別の顔が出てきます。
実際に長時間稼働で見つかった不具合を並べたら、きれいに 4 つに分かれました。**どれも短時間のテストでは原理的に検出できません。**回す時間そのものが検査項目だった、ということです。
| 類型 | 例 | なぜ短時間で出ないか |
|---|---|---|
| 累積 | タイマーの遅れが積もってリップシンクが漂流 | 1 回の誤差が小さい。時間に比例して増える |
| 増加 | 履歴・メモリ・状態が際限なく増える | 上限に当たるまで時間がかかる |
| 周回 | 同じ話題を繰り返し始める | バッファが一周するまで起きない |
| 再起動 | 更新されたトークンが再起動で失われる | プロセスが生きている間は症状が出ない |
以下、4 類型を一つずつ、原因と検出のコツで見ていきます。
類型1: 累積 — 1回の誤差 × 回数
配信映像のフレームを一定間隔で書き出す処理を setInterval で書いていました。タイマーの発火は遅れることがあり、遅れた分は取り戻されません。
1 回あたり 1ms の遅れでも、30fps なら 1 分で 1.8 秒ぶんのフレームが欠けます。音声は実時間で流れているので、映像だけが遅れていく。開始時は合っているのに、時間が経つほどズレるという症状でした。
検出のコツ: 「時間が経つほど悪化するか」を明示的に確認する。開始直後と 1 時間後で同じ測定をして比較します。「調整しても再発する」と感じたら、たいてい累積です。
類型2: 増加 — 上限があるかを実測する
配信の状態(会話履歴、視聴者ごとの記憶など)を保持していました。設計上は直近のぶんだけ持つつもりでしたが、設計の意図と実装が一致しているかは別問題です。
2 時間の稼働で確認した数字がこれです。
| 項目 | 結果 |
|---|---|
| 状態スナップショットのサイズ | 6.2KB で頭打ち |
| プロセスのメモリ | 増加なし |
「頭打ち」を確認できたことが重要でした。増え続けるなら、いずれ必ず落ちます。上限があるという設計を、実測で裏付ける。
検出のコツ: サイズやメモリを定期的に記録して、グラフが寝るか、伸び続けるかを見る。10 分では両者の区別がつきません。
類型3: 周回 — バッファが一周する
コメントが来ないときに自分で話題を出す機能を入れていました。直近の会話履歴を見て、同じ話題を避けるようになっています。
問題は、履歴の保持量がそのまま「話題が一周する周期」になることです。履歴から消えた話題は、また新鮮な話題として選ばれます。視聴者から見ると「さっきも同じ話をしていた」になります。
これは 10 分のテストでは絶対に出ません。履歴が一周していないので。
検出のコツ: 「N 件保持」「N 分保持」という設計値があったら、その N を超える長さで回す。リングバッファ、キャッシュ、重複排除、レート制限。同じ構図はいろいろな場所にあります。
類型4: 再起動 — プロセスが生きている間は正常
外部サービスのトークンを更新するとき、新しいリフレッシュトークンが返ってくることがあります。それをプロセスのメモリに持っているだけだと、再起動した瞬間に古い値に戻ります。
古い値がまだ有効なら動きますが、無効化されていれば認証が通らなくなります。プロセスが動き続けている間は完全に正常なので、ソークテストですら見つかりません。連続稼働と再起動耐性は、別のテストなのです。
検出のコツ: ソークテストの途中でわざと再起動する。
終わり際と、後片付け
長時間テストでしか確認できないものが、4 類型のほかにもう 2 つあります。
終わり際の処理。番組の締めの挨拶、終了処理、リソースの解放。これらは最後の 1 分にしか動きません。途中で止めたテストでは一度も実行されていないコードです。「止まるべきときに止まる」を実装しても、その締め処理を最後まで走らせて確かめなければ、動く保証はありません。
後片付け。我々は毎回、終了後に GPU の Pod が本当に 0 台になっているかを確認しています。止まらないと課金が続くので、ここは目視の確認項目にしました。「終わったはず」を信じないことにしています。
ソークで見つかった最悪のもの
2 時間のソークで見つかった中でいちばん重要だったのは、冒頭で触れた対話サーバだけが死んだとき、配信が無音のまま続くという挙動でした。
- 映像は出ている
- プロセスは生きている
- どのヘルスチェックも緑
- アバターだけが一切しゃべらない
これは「壊れ方が観測されない」という点で、4 類型よりさらに危険です。長時間回していなければ、本番で初めて遭遇していました。「壊れたら気づける」という設計目標そのものが、この一件を長時間回して観測できたからこそ具体化できた、とも言えます。
ソークテストの観測項目
我々が実際に見ているものを挙げておきます。長時間テストの持ち物リストとして使えます。
- メモリ / 状態サイズの推移(寝るか、伸びるか)
- 時刻のズレ(開始時と終了間際で同じ測定をする)
- 出力の実尺 vs 実時間(脱落していないか)
- 同じ挙動の繰り返し(話題、応答パターン)
- 途中での再起動と、そこからの復帰時間
- 終了処理が実際に走ること
- 後片付け(残っているリソースがゼロか)
まとめ
QA の設計として:
- 無人で動く AI システムでは、テストの前提(入力が既知・出力が判定可能・失敗すると止まる)が3 つとも崩れる
- 保証の対象を出力の正しさから、システムの振る舞いへ移す
- 具体的には 4 つ: 止まるべきときに止まる / 壊れたら気づける / 壊れたら戻る / 出してはいけないものを出さない
- 「生きているのに機能していない」を最悪の状態として定義する
- 復帰は実際に壊して計測する(Pod 削除で 32 秒、対話サーバ再起動で 41 秒復帰)。書いただけの復帰コードは動かない
- 自動検査を増やすほど、人間が見る場所を意識的に残す
ソークテストの実務として:
- 長時間でしか出ないバグは 累積・増加・周回・再起動 の 4 類型に整理できる
- 累積は「時間が経つほど悪化するか」で見つける
- 増加は「上限があること」を実測で裏付ける(状態は 6.2KB で頭打ちを確認)
- 周回は設計値(N 件保持)を超える長さで回して初めて出る
- 再起動はソーク中にわざと再起動しないと出ない
- 終わり際の処理と後片付けは、最後まで回したテストでしか実行されない
- 「10 分動いたから大丈夫」は、4 つの類型のどれ 1 つも検査していないという意味
「AI の出力の品質をどう保証するか」に正面から答えるのは難しいですが、**「AI が変なことを言っても、システムとしては壊れない」**を保証するのは可能でした。無人運用では、後者のほうがずっと重要でした。そして回す時間を伸ばすだけで見つかるバグが、これだけあります。