こんにちは!
KDDIアイレットの取り組みとして6月22日〜7月3日の期間で開催中の「Google Cloud Next '26 / Google I/O やってみた系ブログリレー」、最終日の投稿です。
今回は「Gemini Live Avatar」を対象に、実際に検証してみた様子をお届けします!
前回の記事はこちらです。
はじめに
「AIと話せるWebアプリを作りたいけど、映像付きのアバターまで動かすのは難しそう…」と思っていませんか?
Google が提供する Gemini Live Avatar を使えば、音声と映像でリアルタイムに応答するAIアバターをWebアプリに組み込めます。この記事では、Google Cloud の設定から FastAPI サーバー・Next.js フロントエンドの実装まで、実際に動くコードをもとに解説します。
この記事を読むと、以下のことがわかります。
- Gemini Live Avatar とは何か・何ができるか
- Google Cloud(Vertex AI)で必要な設定手順
- ADK + FastAPI で WebSocket BIDI サーバーを作る方法
- Next.js でアバター映像をリアルタイム再生する方法
Gemini Live Avatar とは?何ができるのか
Gemini Live Avatar は、Google の Gemini Live API に 映像生成機能を追加した拡張機能です。通常の Gemini Live が音声のみで応答するのに対し、Avatar モードでは 口の動きや表情が同期した3Dアバターの映像をリアルタイムにストリーミング出力します。
主な特徴
| 項目 | 内容 |
|---|---|
| 対応モデル |
gemini-3.1-flash-live-preview-04-2026(Private Preview) |
| 出力形式 | MP4(H.264)フラグメントをチャンク単位でストリーミング |
| アバター種別 | 組み込みアバター(Ben など)またはカスタムアバター画像(PNG) |
| 音声 | カスタム音声(Puck など)で話者の個性を表現 |
| 対話形式 | テキスト・音声の双方向(BIDI)ストリーミング |
2026年7月時点では Private Preview のため、利用には Google Cloud プロジェクトのアローリスト登録が必要です(後述)。
デモ:プレゼン練習AIを作ってみた
今回は Gemini Live Avatar を使って「AIが厳しいお客さん役になってプレゼン練習を手伝ってくれる」デモを実装しました。
プレゼン内容を話すと、AIが適切なタイミングで鋭い質問を投げかけ、良い回答には「それは説得力がありますね」と褒め、最後には5観点での採点フィードバックをくれます。
- 【論理構成】
- 【数値・根拠】
- 【競合比較
- 【リスク説明】
- 【総合評価】
Google Cloud で必要な設定
1. Vertex AI API の有効化
Google Cloud Console にアクセスし、以下の手順で Vertex AI API を有効化します。
- 左メニューから「APIとサービス」→「ライブラリ」を開く
- 「Vertex AI API」を検索して有効化
2. Gemini Live Avatar のアローリスト申請
- Gemini Live Avatar(VIDEO モード)は現在 Private Preview のため、Google のアカウント担当者にプロジェクト ID とユースケースを伝えて申請が必要です。
3. Application Default Credentials の設定
gcloud auth application-default login
4. 環境変数の設定
1. envファイルの設定
- .env
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=<your-project-id>
GOOGLE_CLOUD_LOCATION=us-central1
2. ディレクトリ構成
gemini-live-avatar/
├── .env # Vertex AI接続設定
├── requirements.txt # Python依存パッケージ
│
├── presentation_agent/
│ └── agent.py # 田中部長キャラクター定義・give_feedbackツール
│
├── server/
│ └── main.py # FastAPI WebSocketサーバー(BIDI)
│
└── frontend/
├── app/
│ └── page.tsx # メインUI(アバター表示・テキスト入力)
├── components/
│ └── AvatarDisplay.tsx # アバター映像の<video>ラッパー
└── hooks/
├── useWebSocket.ts # WebSocket接続・送受信
├── useAvatarVideo.ts # MediaSource APIによる映像再生
└── useAudio.ts # マイク入力→PCMチャンク変換
3. バックエンドの実装
- バックエンド実装:FastAPI + ADK で WebSocket BIDI サーバー
- agent.py
- ADK(Google Agent Development Kit)でエージェントを定義します。Live 専用モデルを指定し、ツールを渡します。
from google.adk.agents import Agent
_MODEL = "gemini-3.1-flash-live-preview-04-2026"
def give_feedback() -> str:
"""フィードバックを求められたときに呼び出す"""
return (
"以下の観点でフィードバックしてください:\n"
"1. 【論理構成】2. 【数値・根拠】3. 【競合比較】\n"
"4. 【リスク説明】5. 【総合評価】10点満点で採点"
)
root_agent = Agent(
name="strict_customer",
model=_MODEL,
description="プレゼン練習用の厳しいお客さんエージェント",
instruction=(
"あなたは大手企業の情報システム部長です。"
"数字と根拠がなければ納得しません。"
"曖昧な表現には即座に「具体的にどのくらいですか?」と聞いてください。"
),
tools=[give_feedback],
)
- server/main.py
- RunConfig に response_modalities=["VIDEO"] と avatar_config を渡すだけで Avatar モードが有効になります。
from google.adk.agents.run_config import RunConfig, StreamingMode
from google.genai import types
def _build_avatar_config() -> types.AvatarConfig:
avatar_image = Path("assets/avatar.png")
if avatar_image.exists():
# カスタムアバター画像(PNG, 704×1280px, 9:16)
return types.AvatarConfig(
customized_avatar=types.CustomizedAvatar(
image_data=avatar_image.read_bytes(),
image_mime_type="png",
)
)
# 画像がなければ組み込みアバター(Ben)にフォールバック
return types.AvatarConfig(avatar_name="Ben")
run_config = RunConfig(
streaming_mode=StreamingMode.BIDI,
response_modalities=["VIDEO"], # ← Avatar モードのキー設定
avatar_config=_build_avatar_config(),
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Puck")
)
),
input_audio_transcription=types.AudioTranscriptionConfig(),
session_resumption=types.SessionResumptionConfig(),
)
▎ ⚠️ 注意:output_audio_transcription は VIDEO モードで指定しない
▎
▎ ADK は response_modalities=["AUDIO"] のときのみ自動設定します。
▎ VIDEO モードで明示的に設定すると APIError 1007 が発生します。
- WebSocket エンドポイント:BIDI ストリーミング
- クライアントからのメッセージ受信(upstream)と Gemini からのイベント送信(downstream)を asyncio.create_task で並列実行します。
@app.websocket("/ws/{user_id}/{session_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str, session_id: str):
await websocket.accept()
live_request_queue = LiveRequestQueue()
async def upstream_task():
"""クライアント → Gemini: テキストまたは音声PCMを転送"""
while True:
message = await websocket.receive()
if message.get("text"):
data = json.loads(message["text"])
content = types.Content(parts=[types.Part(text=data["text"])])
live_request_queue.send_content(content)
elif message.get("bytes"):
# 音声: PCM 16kHz/16bit/mono
live_request_queue.send_realtime(
types.Blob(data=message["bytes"], mime_type="audio/pcm;rate=16000")
)
async def downstream_task():
"""Gemini → クライアント: イベントをJSONで転送"""
async for event in runner.run_live(
user_id=user_id,
session_id=session_id,
live_request_queue=live_request_queue,
run_config=run_config,
):
# content.parts[].inlineData.data に base64 MP4 チャンクが入る
await websocket.send_text(
event.model_dump_json(exclude_none=True, by_alias=True)
)
tasks = [
asyncio.create_task(upstream_task()),
asyncio.create_task(downstream_task()),
]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for task in pending:
task.cancel()
live_request_queue.close()
4. フロントエンド実装:Next.js でアバター映像をリアルタイム再生
- hooks/useWebSocket.ts
- WebSocket フック
export function useWebSocket({ userId, sessionId, onEvent }) {
const [connected, setConnected] = useState(false);
useEffect(() => {
const ws = new WebSocket(`ws://localhost:8080/ws/${userId}/${sessionId}`);
ws.onopen = () => setConnected(true);
ws.onclose = () => setConnected(false);
ws.onmessage = (e) => onEvent(JSON.parse(e.data));
return () => ws.close();
}, [userId, sessionId]);
return {
connected,
sendText: (text: string) => ws.send(text),
sendAudio: (pcm: ArrayBuffer) => ws.send(pcm),
};
}
- hooks/useAvatarVideo.ts(抜粋)
- MediaSource API で映像チャンクをリアルタイム再生
- Gemini が返す映像は MP4 フラグメントを base64 エンコードしたチャンクです。これを MediaSource API で 要素へ流し込みます。
const processVideoChunk = useCallback((base64: string) => {
// URLセーフ base64 → 標準 base64 に変換
const std = base64.replace(/-/g, "+").replace(/_/g, "/");
const bytes = Uint8Array.from(atob(std), c => c.charCodeAt(0));
if (sourceBuffer && !sourceBuffer.updating) {
sourceBuffer.appendBuffer(bytes.buffer);
} else {
videoQueue.push(bytes.buffer); // 処理中は待機キューに積む
}
}, []);
- バッファ遅延を防ぐ再生速度の動的調整
- 映像チャンクが積み上がるとリアルタイム感が失われます。バッファ残量を監視して再生速度を動的に調整します。
// hooks/useAvatarVideo.ts
video.preservesPitch = false; // 音割れ防止
if (bufferAhead > 3.0) {
video.playbackRate = 1.1;
} else if (bufferAhead > 1.0) {
video.playbackRate = 1.05;
} else {
video.playbackRate = 1.0;
}
ADK 2.2.0 × Vertex AI × VIDEO モードの組み合わせで実際に遭遇したエラー
公式ドキュメントに記載のある制約ではなく、試行錯誤の中で踏んだものです(2026年7月時点)。
| 試したこと | 結果 | エラーの内容 |
|---|---|---|
LoopAgent を使う |
❌ クラッシュ |
_run_live_impl が未実装 |
transfer_to_agent(Vertex AI) |
❌ クラッシュ |
history_config 非対応 |
output_audio_transcription(VIDEO モード) |
❌ APIError 1007 | VIDEO モードでは設定不可 |
単一 Agent に全ロジックを集約 |
✅ 安定動作 | — |
Vertex AI + Live BIDI + VIDEO モードの組み合わせでは、単一 Agent が唯一の安定構成でした。
まとめ
- Gemini Live Avatar は音声+映像でリアルタイム応答するAIアバターをWebアプリに組み込める
- 利用には Vertex AI の有効化 と アローリスト申請(Private Preview)が必要
- RunConfig に response_modalities=["VIDEO"] と avatar_config を渡すだけで Avatar モードが有効になる
- フロントエンドでは MediaSource API で MP4 チャンクをリアルタイム再生する
- Vertex AI 環境では 単一 Agent に全ロジックを集約するのが安定
Avatarを組み換えたりプロンプトを変更したりすることで今回紹介した教育に使用できたり、映画の世界みたいにAIが会話ベースでも相棒として一緒に仕事をしたりするような世界がかなり近づいてきているのだと感じました。ぜひ皆さんもオリジナルのAvatarを作成してみてください!最後までお読みいただきありがとうございました。