0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(AWS版AIハヤト)AIが「適性」を診断するWebアプリを作ってAWSに公開してみた

0
Posted at

はじめに

TBS日曜劇場『VIVANT』に登場するAI「ハヤト」に興味を持ち、同じような体験を自分で作れるか試しました。

イベント「VIVANT IMMERSIVE MISSION」では、来場者がミッションをこなしたあと、最終的な適性数値をAIハヤトが算出します。
声は高山みなみさんが担当しています。

この流れを参考に、4つのミッションの結果から「適性値」を算出し、AIが講評を音声で読み上げるWebアプリを作りました。
ハヤトそのものは再現せず、AIキャラクターは別の設定で作り、声も変えています。
本記事は個人の検証で、作品やイベントの制作側とは関係ありません。

この記事では、次の順で書きます。

  • AIハヤトの場面で使われている Google の AI
  • 作ったものの概要
  • ローカル版の実装(Amazon Bedrock、VOICEVOX)
  • Amazon S3 + Amazon CloudFront + AWS Lambda への公開
  • 公開でハマった点(同時実行数の予約、OAC 経由の POST、AWS WAF)
  • デモ動画の撮影
  • AIキャラクターの名前の決め方
  • 動作確認と、声が違う理由

ここで2つの問いを提示しておきます。
1つは、ローカル版と公開版で声が違う理由です。
もう1つは、AIキャラクターの名前に「-07」が付いている理由です。
どちらも最後に答えます。

AIハヤトの場面で使われている Google の AI

作る前に、TBS がハヤトの場面をどう作っているかを調べました。
確認できた範囲を整理します。

  • 使われたツール: Google の動画生成AI「Veo 3」と、静止画生成の「Nano Banana」です。
    細部を指定した静止画を Nano Banana で作り、それを Veo 3 に動かさせる2段階の方法が取られたとされています。
  • 使われた場面: 病院の屋上機械室の爆発映像と、AIハヤトが説明する場面でモニターに映るボイラー室の爆発映像です。
  • 苦労した点: 同じ指示でも毎回違う映像になる「生成AIガチャ」があり、満足のいく映像が出るまで試行を重ねたそうです。
  • 位置づけ: 続編で Veo 3 を放送本編に使うのは、TBS のドラマとして初めてです。

TBS は、この制作の話を Google のイベントでも紹介しています。
2026年7月30日と31日に東京ビッグサイトで開かれた「Google Cloud Next Tokyo 26」に出展し、ブースのテーマは「TBSテレビが見せる、日曜劇場『VIVANT』制作と生成AI活用のいま」でした。

実写や絵コンテを入力に Nano Banana と Veo 3 で映像の下地を作り、専門家が確認して実写と合成し、放送用の映像に仕上げる流れが紹介されています。
来場者が『VIVANT』風のキャラクターに変身できる体験もありました。

確認できなかったことも書いておきます。AIハヤトそのものの声や動きの実装に Google が使われているかは、見つけた資料に記載がありませんでした。
そのため、「TBS が Google で実装したものを AWS で再現した」とは書けません。
調査情報デジタルの記事は、今回は本文を開けず、記事の要約で確認した範囲です。

つまり、ハヤトの場面の映像づくりは Google の AI が担っています。
今回作ったのは、ミッションと診断、講評、声、公開の部分で、こちらは AWS のサービスで組みました。
今回のアプリに、映像を生成する機能はありません。

作ったもの

画面の流れ

画面には、AIキャラクター「カナメ-07」が表示されます。
受験者は4つのミッションを順にこなし、最後にAIが適性値を算出して講評を読み上げます。

ミッションは毎回ランダムに選ばれます。

ミッション 内容 バリエーション
記憶 表示された文字列を覚えて入力 数字6桁、6桁を逆順で入力、英字5文字、数字8桁
反応 緑になった瞬間にタップ 1回、2回連続(平均で評価)、赤の間は押してはいけないフェイント付き
判断 状況に対して最も適切な行動を選ぶ 4つの状況、選択肢の順番は毎回シャッフル
解読 暗号を解いて英単語を入力 3つずらし、ずらし幅が可変、逆順、番号置換(A=1)

各ミッションは100点満点で、適性値は4つの平均です。反応の点数は、反応時間を使って 100 - (ms - 200) / 6 で計算しています。

ローカル版の構成

ブラウザ(HTML / JavaScript)
   └─ Python サーバー
        ├─ Amazon Bedrock ... 講評の生成
        ├─ VOICEVOX ......... 音声(青山龍星)
        └─ Amazon Polly ..... VOICEVOX が使えないときの代替

サーバーに繋がらないときは、テンプレートの講評とブラウザ標準の音声で動きます。

ローカル版の実装

人格をプロンプトで固定

講評は Amazon Bedrock の Claude Haiku 4.5 で生成します。
システムプロンプトでは、次の点を指定しました。

  • 冷静で簡潔に話し、数値と根拠で語る
  • 一人称は「当機」、受験者は「あなた」と呼ぶ
  • 講評は3〜4文、200字以内で、最後に配属先を述べる
  • 与えたスコア以外の事実は作らない
  • 音声で読み上げるため、見出しや記号、Markdown は使わず平文だけで出力する

最後の指定は、最初の実行で必要になりました。
指定なしだと、講評に「講評」のようなMarkdown記号が混ざり、画面表示にも読み上げにも出てしまいました。

呼び出しは Converse API を使っています。

res = bedrock.converse(
    modelId="us.anthropic.claude-haiku-4-5-20251001-v1:0",
    system=[{"text": PERSONA}],
    messages=[{"role": "user", "content": [{"text": prompt}]}],
    inferenceConfig={"maxTokens": 400, "temperature": 0.7},
)

声は VOICEVOX の青山龍星

音声は VOICEVOX エンジンに合成させます。

AIハヤトの声を担当する高山みなみさんの声は、そのままでは使えません。
そのため、ローカル版では VOICEVOX の青山龍星、公開版では Amazon Polly の Takumi と、声を変えました(クレジットは末尾に記載しています)。
手元のエンジンは、既定の 50021 ではなく 50032 番ポートで動いていました。
話者一覧を取得すると、青山龍星のノーマルは ID 13 でした。

curl -s localhost:50032/speakers

合成は、audio_query で読み上げ用のデータを作り、speedScale を設定してから synthesis に渡す2段階です。

q = json.loads(urlopen(Request(
    f"{URL}/audio_query?speaker=13&text={quote(text)}", method="POST")).read())
q["speedScale"] = 1.0
wav = urlopen(Request(f"{URL}/synthesis?speaker=13",
    data=json.dumps(q).encode(),
    headers={"Content-Type": "application/json"}, method="POST")).read()

VOICEVOX に繋がらないときは、Amazon Polly の Takumi に切り替えます。
Polly 側は SSML の prosody で速さを指定できます。

課題も見つかりました。長い講評を合成すると、手元の環境では十数秒かかります。
講評が画面に出てから声が始まるまで、待ち時間が生じます。

AWSへの公開

構成

公開版は、次の構成で AWS CDK(Python)から作りました。
スタックは us-east-1 に作っています。
CloudFront 用の AWS WAF の Web ACL は、us-east-1 に作る必要があります。

  • 画面: Amazon S3(非公開)を Amazon CloudFront の OAC で配信
  • API: AWS Lambda(Python 3.13)の Function URL を IAM 認証にし、CloudFront の /api/* から OAC で呼ぶ
  • 権限: Lambda に bedrock:InvokeModel(Haiku 4.5 の基盤モデルと推論プロファイル)と polly:SynthesizeSpeech だけを付与
  • 制限: AWS WAF のレート制限

Lambda の中身は、ローカルのサーバーとほぼ同じです。
音声は Polly(Takumi)だけを使います。

architecture.png

同時実行数の予約が通らない

公開したエンドポイントからは、誰でも Bedrock と Polly を呼べます。
課金の上限を絞るため、最初は Lambda に予約済み同時実行数(5)を設定しました。
デプロイは次のエラーで失敗しました。

Resource of type 'AWS::Lambda::Function' ... is not updatable with parameters provided.
(HandlerErrorCode: NotUpdatable)

原因を調べるため、アカウントの設定を確認しました。

aws lambda get-account-settings --region us-east-1 --query AccountLimit

同時実行数の上限が10で、未予約分も10でした。
予約すると、未予約分が Lambda の下限を割るために設定できないと判断しました。
これは私の推測で、公式の説明までは確認していません。
予約の設定を外すと、デプロイは通りました。
上限の絞り込みは、AWS WAF に任せています。

OAC 経由の POST には SHA-256 ヘッダーが必要

CloudFront から OAC で AWS Lambda の Function URL に POST する場合は、クライアントがリクエストボディの SHA-256 ハッシュを x-amz-content-sha256 ヘッダーに付ける必要があります。
公式ドキュメントの記述に従ったもので、ヘッダーなしの挙動は今回検証していません。

画面側は、Web Crypto API でハッシュを計算して付けています。

async function post(path, body) {
  const data = JSON.stringify(body);
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data));
  const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
  return fetch(path, {
    method: "POST",
    headers: { "Content-Type": "application/json", "x-amz-content-sha256": hex },
    body: data,
  });
}

crypto.subtle は HTTPS か localhost でしか使えないため、ローカルでも公開版でも動きます。

Function URL には InvokeFunction の権限も追加

2025年10月以降に作る Function URL には、lambda:InvokeFunctionUrl に加えて lambda:InvokeFunction の権限も必要です。
新規の URL でこれが足りないと 403 になると把握していたため、CDK のスタックには、CloudFront のディストリビューションを条件にした InvokeFunction の許可を明示的に追加しました。
この権限がなくても動くかどうかは、検証していません。

AWS WAF のレート制限

予約済み同時実行数が使えないため、CloudFront に AWS WAF のレート制限を付けました。

  • 対象: /api/ で始まるパスだけ。画面本体は制限しません
  • 条件: 同じ IP から5分間に100リクエストを超えたらブロック(403)
  • 根拠: 1回のプレイで API を10回前後(デモ動画の撮影時は音声8回と講評1回の計9回)呼ぶため、通常の利用には影響しません

実際に効果があるか、存在しないパスに160回リクエストして確かめました。
Bedrock や Polly を呼ばないパスです。

  • 結果: 404 が157回、403 が3回
  • 45秒待ってから再度送ると、すべて 403

100回を超えたあともしばらくは通っていたため、制限は即時には反映されません。
また、この検証で自分の IP もブロックされました。
解除までにかかる時間は、今回は確認していません。

なお、WAF は課金の上限を保証するものではありません。
Web ACL とルールには固定費もかかります。
金額は AWS の料金ページで確認してください。

デモ動画の撮影

デモ動画は、ローカル版と公開版の2本を、Playwright でブラウザ操作を録画して作りました。画面の録画には音声が入らないため、読み上げ音声は別に集めて合成します。

  • 画面: Playwright の動画録画(webm)。ミッションの回答は、画面の内容を読み取って自動で入力
  • 音声: ページ側で fetch を包み、/api/speak の応答を保存
  • 合成: ffmpeg で、音声を再生された時刻に合わせて動画に重ね、mp4 に変換

ローカル版

公開版

AIキャラクターの名前

最初の名前は「カガリ」でした。その後、次の順に変えています。

  • カナメ: 同名のアプリやサービスが複数ありました。業務ソフトの「KANAME簡単レポート」、予算申請のアプリ「KANAME Hub」、AI開発の手法「Kaname」などです。
  • サトリ: 同名のアプリ「SATORI」があるため、候補から外しました。
  • 造語の5候補(Clavis、Noema、Miras、Solva、Verita): 検索したところ、すべてに同名の AI アプリやサービスがありました。

今回検索した範囲では、発音しやすい短い名前はいずれも既存の AI 系のものと重なりました。名前だけで被りを避けるのは難しいと判断し、コードネーム型にしました。
「カナメ-07」の「-07」は、その結果です。
数字自体に意味はなく、型番の形にするために付けました。

ここまでで、冒頭の問いの1つに答えられます。
名前に「-07」が付いているのは、名前の被りを型番で区別するためです。
商用化や商標登録を考える場合は、特許庁の J-PlatPat で別途確認する必要があります。

声が違う理由と動作確認

もう1つの問いです。
ローカル版と公開版で声が違うのは、VOICEVOX のエンジンが手元のパソコンで動いているためです。
Lambda からは呼べません。公開版でも青山龍星にするには、VOICEVOX エンジンをクラウド上に立てる必要があり、
今回はそこまでやっていません。公開版の音声は、Amazon Polly の Takumi です。

動作確認の結果です。

  • 公開版のページ、音声(MP3)、講評が返ることを、curl とブラウザの両方で確認しました。
  • デモ動画は、ローカル版が約93秒、公開版が約78秒です。
  • 確認していないのは、音声の実際の聞こえ方、OAC の POST でヘッダーがない場合の挙動、InvokeFunction の権限の要否です。

不要になったときは、次のコマンドで作成したリソースを削除できます。

cd infra && npx aws-cdk@latest destroy

ハヤトの体験から変えたところ

最後に、冒頭で触れた『VIVANT』のAIハヤトの体験と、今回作ったものを比べます。

  • 同じにしたところ: ミッションをこなし、最後にAIが適性を数値で告げる流れ
  • 変えたところ:
    • キャラクター: ハヤトではなく、オリジナルの「カナメ-07」
    • 声: 高山みなみさんの声はそのまま使えないため、ローカル版は VOICEVOX の青山龍星、公開版は Amazon Polly の Takumi
    • 名前: 既存のものと被らないよう、コードネーム型
  • 作らなかったところ: ハヤトの場面の映像づくり(TBS は Veo 3 と Nano Banana を使用)。今回のアプリに映像生成はありません
  • 自分で考えたところ: イベントのミッションの中身は確認しておらず、今回の4つのミッションと講評の指示は自分で考えたものです

AIハヤトの体験のうち、参考にしたのは流れだけです。
TBS が Google の AI で作っている映像づくりの部分とは別に、診断と声の部分を、Amazon Bedrock、Amazon Polly、Amazon CloudFront、AWS Lambda で動くところまで作りました。
キャラクターと声は別のものにしています。

最後まで読んでいただきありがとうございました。

参考とクレジット

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?