🦭

災害時の「迷い」を減らすためのAI社会シミュレーション

に公開2件

1. はじめに

私は今回、「第4回 Agentic AI Hackathon with Google Cloud」に出場するにあたって、災害時の「迷い」を減らすためのAI社会シミュレーション 「Agent Town」 を作成しました。

住人をAIエージェントで構築し、災害が起きた時にどのような行動を取るのか、シミュレーションするゲームになります。


 
「Agent Town」 は、災害時に起きる次の4つを確認できるシミュレーションです。

  • 噂や誤情報がどう広がるか
  • 公式情報がどこまで届くか
  • 住民がいつ避難を始めるか
  • 高齢者・要介助者など要支援者に支援が届くか

このプロジェクトの目的は、次の3つです。

  1. 災害時の初動で「何を先にやるか」を迷わないようにする
  2. パニックを減らし、必要な人へ支援を早く届ける
  3. 訓練の振り返りを「なんとなく」ではなく、数字で比較できるようにする

この目的を、シミュレーションゲームを通じて学ぶことができます。
最終的には、本番で使える手順にまで落とし込むことを狙っています。

デモ動画

https://youtu.be/nk9Q-Gn5e2k

2. なぜこの課題に取り組んだか

2.1 災害現場で起きやすい3つの詰まり

実際の災害対応では、次の3つが重なると何をしたらいいのかの判断が難しくなります。

  • 情報の時差: 正しい情報が届く前に、断片情報や噂が先に広がる
  • 受け取り方の差: 同じ情報でも、言語・信頼度・ストレスで解釈が変わる
  • 行動開始の差: 要支援者ほど避難の出遅れが起きやすい

その結果、現場では「何を先に打つべきか」の優先順位が毎回ぶれやすくなります。

2.2 既存訓練の限界

紙の計画や単発訓練だけだと、次の比較がしにくいという課題があります。

  • 順番の違い: 先にファクトチェックを打つか、先に要支援者支援を打つか
  • タイミングの違い: 同じ施策でも早く打つか遅く打つかで結果が変わる
  • リソース制約: 現実には施策を全部同時に実行できない

AgentTown は、この「順番」「タイミング」「予算制約」を同条件で比較できます。
そのため、施策の良し悪しを繰り返し検証できます。

3. 推しポイント

3.1 災害時の課題発見

  • 技術デモではなく、自治体・防災訓練で使う意思決定の課題に直結させる
  • 特に取り残されやすい層(高齢者、要介助者、非日本語話者)を重要指標で追跡する

3.2 課題解決策の有効性

  • 介入カードで施策の順番とタイミングをA/B比較できる
  • 10種類の介入カードに加え、施策のコンボで連携効果を検証できる
  • 結果を履歴として保存し、再実行して比較できる

3.3 技術ポイント

  • Three.jsを使用した、かわいいデザインで楽しく学べる
  • LLM・Embedding・Vector Search を使い、過去の出来事を参照して判断するAI住民を実装
  • 外部AIが失敗してもシミュレーション全体は止まらないフォールバック設計(代替経路あり)
  • Cloud SQL でログを永続化し、後から分析できる

3.4 Google周り使用技術

  1. Google Cloud アプリケーション実行プロダクト
  • Cloud Run を使用
  • agenttown-web(Next.js Webアプリ)
  • agenttown-ws(WebSocketシミュレーションサーバー)
  • cloudbuild.yaml で gcloud run deploy により2サービスをデプロイ
  1. Google Cloud AI 技術
  • Vertex AI を使用
  • Gemini(Vertex AI 経由)で住民の行動決定・理由生成
  • Embeddings(gemini-embedding-001)で記憶をベクトル化
  • Vertex AI Vector Search で近傍検索し、過去記憶を再利用
  1. その他
  • Cloud SQL (MySQL) にイベント・メトリクス・住民メモリを保存
  • Cloud Build で Web/WS のビルドと Cloud Run デプロイを自動化

4. 実装説明

まず前提として、このシステムは住民一人ひとりを小さな意思決定主体として扱います。
この意思決定主体を、本稿では「エージェント」と呼びます。

  • エージェント: 住民1人分の判断主体
  • メトリクス: 状況を数値で見るための指標
  • 介入カード: 運用側が打つ施策(コストとクールダウンあり)

4.1 システム構成

  • フロント: Next.js + React Three Fiber(画面表示と操作)
  • シミュレーション: server/index.ts(サーバー側で世界状態を管理)
  • 実行基盤: Cloud Run(agenttown-web / agenttown-ws)
  • 保存: Cloud SQL (MySQL)
  • AI: Vertex AI (推論), Embeddings, Vector Search

全体構成図(詳細)は次のとおりです。

この図は、入力(介入)から判断、保存、可視化までのデータの流れを1枚で示しています。

4.2 住民モデル

各住民には、次の状態・属性があります。

  • 属性: 年齢層、移動制約、言語、聴覚、世帯、役割
  • 脆弱性タグ: 高齢者、要介助、非日本語話者、独居など
  • 心理: mood, stress, energy
  • 情報状態: NONE / RUMOR / OFFICIAL
  • 避難状態: STAY / EVACUATING / SHELTERED / HELPING

4.3 メトリクス(中核)

このシミュレーションでは、次の観点をまとめて見ます。

  • 街全体の混乱が増えているか、落ち着いているか
  • 公式情報がどこまで届いているか
  • 要支援者に支援が届いているか
  • 誤情報の影響が広がっていないか
  • 限られたリソースを適切に使えているか

stabilityScore(安定スコア) は、これらをまとめて全体の安定度を見るための総合指標です。
どれか1つだけ良くても不十分なので、複数指標をセットで判断します。

4.4 介入カード(10種 + コンボ)

全カードにはコストがあるため、同じ施策を連続で打つことはできません。
この制約によって、現実に近い条件で訓練できます。

さらに、一定の秒数内に特定の2枚を順番に打つとコンボが発動します(このコンボはカードの色が変わるのでわかりやすくなっています)

4.5 AI 意思決定ループ

AI 住民は次の流れで判断します。

  1. 近くで起きた出来事や会話、過去メモリを取得
  2. 内省(いま何を優先すべきかを整理)
  3. 計画(次に何をするかを決める)
  4. 行動(移動・会話・避難支援など)

さらに、行動ログをメモリとして保存します。
必要に応じて埋め込み化・ベクトル検索し、次の意思決定に再利用します。

実際の実装コードは次です。

1) どの住民をAI化するか(ハイブリッド運用)

環境変数 SIM_AI_AGENT_RATIO(既定 0.1 = 10%)で AI 住民の比率を決めています。
役割が偏りすぎないように、ロール単位でも選ぶ処理を入れています。

// mocks/mockWorld.ts
const aiRatio = resolveNumber(process.env.SIM_AI_AGENT_RATIO ?? undefined, 0.1);
assignAiAgents(agents, aiRatio);

const assignAiAgents = (agents: Record<string, Agent>, ratio: number) => {
  const list = Object.values(agents);
  const targetCount = Math.max(1, Math.round(list.length * Math.max(0, Math.min(1, ratio))));
  const selected = new Set<string>();
  // role ごとに偏らないように選定
  // ...
  list.forEach((agent) => {
    agent.isAI = selected.has(agent.id);
  });
};

2) AI住民が毎ティックで何を見て判断するか

AI 住民だけを対象に、近傍会話・近傍住民・直近イベント・関連メモリを集めます。
それらを generateAgentDecision に渡して判断させます。

// server/index.ts
const candidates = Object.values(world.agents).filter(
  (agent) => agent.isAI && !decisionInFlight.has(agent.id)
);

const memories = await getRelevantMemories({
  agent,
  recentEvents: recent,
  limit: 6,
});

return generateAgentDecision({
  agent,
  tick: world.tick,
  metrics,
  recentEvents: recent,
  moveOptions,
  disaster: currentConfig?.disaster ?? "EARTHQUAKE",
  nearbyChatter,
  nearbyAgents,
  memories,
  timeOfDay,
});

3) Geminiで行動を決める部分

ADK を使っています。モデルは gemini-3-flash-preview です。

// lib/ai/decision.ts
const getDecisionModelName = () =>
  process.env.VERTEX_AI_MODEL_DECISION ||
  process.env.VERTEX_AI_MODEL ||
  "gemini-3-flash-preview";

if (process.env.SIM_ADK_ENABLED === "true") {
  try {
    return await generateAgentDecisionWithAdk(input);
  } catch {
    // fall back to Vertex AI
  }
}

const modelName = getDecisionModelName();
const vertex = getVertexClient(modelName);
const model = vertex.getGenerativeModel({ model: modelName });
const response = await model.generateContent({
  contents: [{ role: "user", parts: [{ text: prompt }] }],
});

4) Geminiで「なぜこの行動か」を生成する部分

住民を選択したときに AGENT_REASONING を返すため、理由文(why)と参照メモリ(memoryRefs)を生成・保存します。
この部分は gemini-3-pro-preview を使います。

// lib/gcp/vertex.ts
const getReasoningModelName = () =>
  process.env.VERTEX_AI_MODEL_REASONING ||
  process.env.VERTEX_AI_MODEL ||
  "gemini-3-pro-preview";

const modelName = getReasoningModelName();
const vertex = getVertexClient(modelName);
const model = vertex.getGenerativeModel({ model: modelName });
const response = await model.generateContent({
  contents: [{ role: "user", parts: [{ text: prompt }] }],
});
// server/index.ts
const reasoning = await generateAndStoreReasoning({
  agent,
  tick: world.tick,
  recentEvents: getRecentEvents(agentId),
});
sendTo(client, { type: "AGENT_REASONING", payload: reasoning });

5) Embeddingでメモリを「保存・検索」する部分

このプロジェクトでは、イベントや内省ログを Embedding 化して Vector Search に保存します。
次の判断時には「今の状況に近い過去メモリ」を引いて使います。

// lib/gcp/embeddings.ts
const model = process.env.VERTEX_EMBED_MODEL || "gemini-embedding-001";
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${model}:predict`;

const body = {
  instances: [{ content: text }],
  parameters: { autoTruncate: true },
};

const vector =
  data.predictions?.[0]?.embeddings?.values ??
  data.embedding?.values ??
  data.embeddings?.[0]?.values;
// lib/ai/memoryPipeline.ts
await saveMemory({ id: memoryId, agentId: agent.id, content, sourceType: "event" });

if (process.env.MEMORY_PIPELINE_ENABLED !== "true") return;

const vector = await embedText(content);
if (!vector) return;

await upsertVector({
  id: memoryId,
  vector,
  metadata: { agentId: agent.id, type: event.type },
});
// lib/ai/memoryRetrieval.ts
const query = buildQueryText(input.agent, recentEvents);
const vector = await embedText(query);
if (!vector) return fallback;

const neighbors = await findNeighbors({ vector, neighborCount: limit * 2 });
const memories = await getMemoriesByIds(neighbors.map((n) => n.id));
const filtered = memories.filter((memory) => memory.agentId === input.agent.id);
return dedupeById([...filtered, ...fallback]).slice(0, limit);

Embedding 失敗時やレート制限時は recent memories にフォールバックします。
そのため、AI 機能が落ちてもシミュレーション自体は止まらない設計です。

4.6 WebSocket プロトコル

クライアントは描画と操作に集中し、ワールドの正しい状態はサーバーが持ちます。

4.7 設定画面の全項目ガイド

設定画面は「この街がどれくらい混乱しやすいか」を作る場所です。
全項目は段階式(プリセット)なので、難しい数式なしで調整できます。

  • ミッションガイド: 初回は3ページのイントロ(ゲーム説明 -> クリア条件 -> 学習ループ)を読んでから設定に進みます。
  • 難易度(easy / middle / high): 入力した設定から自動で決まる目安です。
  • 高度な設定: 押すと詳細パラメータが開きます。閉じたままでも開始できます。

設定項目(全13項目)

  1. 町のサイズ(小 / 中 / 大)
    マップの広さです。大きいほど移動が増え、情報の行き渡りに差が出やすくなります。

  2. 人数(50 / 200 / 600 人)
    住民の数です。人数が多いほど噂も公式情報も広がる先が増えます。

  3. 地形(海沿い / 山 / 都市)
    街の形です。避難のしやすさや、どの災害が成立するかに影響します。

  4. 災害シナリオ(津波 / 地震 / 洪水 / 隕石)
    何が起きる世界かを選びます。津波 は 海沿い のときだけ選べます。

  5. 建物数(140 / 260 / 400 棟)
    施設・住宅の総量です。多いほど人が分散し、動き方が複雑になります。

  6. 公式警報遅延(5 / 15 / 30 分)
    公式アラートが出るまでの遅れです。遅いほど初動が噂に引っ張られやすくなります。

  7. 情報の曖昧さ(20 / 50 / 80 %)
    情報の分かりやすさです。高いほど「はっきりしない情報」が増えます。

  8. デマ強度(20 / 50 / 80 %)
    誤情報の勢いです。高いほど訂正する前に拡散しやすくなります。

  9. 多言語対応(30 / 60 / 90 %)
    翻訳がどれだけ届くかです。高いほど言語の壁で取り残される人を減らせます。

  10. 検証速度(30 / 60 / 90 %)
    ファクトチェックの速さです。高いほど誤情報の訂正を早く出せます。

  11. 介入ポイント(80 / 120 / 160 / 200 pt)
    介入カードに使える予算です。高いほど打てる手が増えます。

  12. 住民の気分(温かめ / 中立 / 冷ため)
    住民の反応傾向です。温かめ は協力的、冷ため は警戒強めの挙動になります。

  13. 年齢層(若年層多め / バランス / 高齢層多め)
    人口構成です。高齢層が多い設定ほど、要支援者への到達設計が重要になります。

4.8 介入カードの制約(コスト/回復/回数)

運用の制約は、現在次の4つです。

  • 介入ポイント: 各カードの cost を支払って実行
  • クールダウン: カードごとに再使用待機(tick単位、UI上は秒表示)
  • 使用回数上限: 1ゲームあたり 10回
  • ポイント回復: 5tickごとに +10pt(上限は初期設定値まで)

この制約により、単純な連打ではなく「どの順で、どのタイミングで打つか」の設計が必要になります。

4.9 結果画面の見方

結果画面は5つのタブで構成されています。
ここでは「どの項目が何を意味するか」を、画面の順番で説明します。

ヘッダー(画面上部)

  • 終了ティック: シミュレーション終了時点の tick(進行ステップ)
  • 実時間: 実際に回していた時間(分:秒)
  • 仮想時間: シミュレーション内で経過した時間(分)
  • 現実換算: 仮想時間を現実時間へ換算(仮想1分 = 現実1時間)
  • Town Resilience(街の安定度スコア): 0〜100点の総合点(高いほど良い)
  • ランク: S(85以上) / A(70以上) / B(55以上) / C(54以下)

タブ1: 概要

ミッションの結果や各設定値などの概要が確認できます。

タブ2: 主要指標

このタブには8つのメトリクスが出ます。
各カードの Peak は「その指標が最も高かった瞬間の値」です。

  • 混乱度 (confusion): 高いほど混乱している(低いほど良い)
  • 噂拡散 (rumorSpread): 高いほど噂が広がっている(低いほど良い)
  • 公式到達 (officialReach): 公式情報が届いた割合(高いほど良い)
  • 要支援到達 (vulnerableReach): 要支援者へ支援・情報が届いた割合(高いほど良い)
  • パニック (panicIndex): パニック傾向の強さ(低いほど良い)
  • 公式信頼 (trustIndex): 公式情報への信頼度(高いほど良い)
  • 誤情報信念 (misinfoBelief): 誤情報を信じている度合い(低いほど良い)
  • 誤配分 (resourceMisallocation): 支援リソースの無駄配分(低いほど良い)

タブ3: スコア内訳

Town Resilience の点数が、どの要素から作られているかを表示します。
value × weight の寄与で足し合わせる方式です(重み合計100%)。

タブ4: 人口/イベント

  • 警報認知: 住民が 公式 / 噂 / 未到達 のどれで認知しているか
  • 避難状態: 避難中 / 避難所 / 支援中 / 待機 の人数比
  • イベント集計: 発生イベントの総数
  • 警報 / 公式情報 / 噂 / 避難 / 支援 / 安否 / 会話 / 移動 / 生活 / 介入

タブ5: AI分析(Vector Insights)

  • ステータス: pending / ready / disabled / unavailable / error
  • 会話ライン: 抽出された会話スレッド数、発話ターン数、参加者数
  • 噂の汚染度: 近傍会話における非公式情報比率(%)
  • 鎮静化率: 会話ムードが STABILIZING のスレッド割合
  • プレイヤー向け要約: 「どんな会話があったか / 不安ポイント / 解決の進展」
  • 次の一手: 指標と会話状況から自動生成される運用ヒント
  • 技術詳細(折りたたみ): クラスタ要約、パイプライン診断、会話反転tick(噂優勢 -> 公式優勢)

5. 今後の拡張

  • 実データ連携: 気象・避難所・交通 API 同化・実際のマップベースにシミュレーション
  • 多人数運用: 他プレイヤーとのマルチプレイ機能
  • レポート強化: さらなるレポートの詳細化から振り返りがしやすいように
  • 介入カードの追加機能: 好きなオリジナル介入カードを追加することができる

6. まとめ

AgentTown の最終目的は、実際の災害でパニックを減らすことです。
そのうえで、要支援者への支援を早く確実に届けることを目指します。

そのために、施策の順番・タイミング・予算を変えながら、

  • 混乱をどれだけ抑えられるか
  • 誰が取り残されるか
  • どの手順なら巻き返せるか

を事前に検証し、本番で迷わない運用手順にしていくことを目指していきます。

7. 感想

今回も「Agentic AI Hackathon with Google Cloud」に参加させていただきました。
前回と比べ、より複雑な UI / UX 設計や AI Agent の制御を行う必要があり、設計・実装の両面で頭を悩ませる場面が多くありました。

しかし、シミュレーションを“ゲームのように体験できる形”まで落とし込んだことで、仮説 → 実行 → 観察 → 改善を何度も高速に繰り返せるようになり、災害が起きた時にどのような行動を起こせばいいのか学びやすくなりました。

追加機能として考えていることも多いので、引き続き開発を進めてより説得力のあるシミュレーションへと進化させていこうと思います。

お読みいただきありがとうございました!

参考文献・資料

Discussion

いしはるいしはる

私も、個々のagentをルールベースではなく自然言語でマクロなアウトプットを期待する取り組み自体大きな魅力を感じています。

ただ、LLMやagent間のコミュニケーションの仕組みが「理性的すぎる」というか、本当にそれが現実の人間社会を正しく反映しているかの妥当性の検証がポイントになってきそうだと思います。

LLMやシステムの持つバイアスが、シミュレーション結果を特定の方向に歪めてしまう可能性についてご意見お聞きしたいです!

shunshun

いしはるさん。コメントありがとうございます!

おっしゃる通りで、LLMベースのagentシミュレーションは面白い一方で、「どこまで現実を反映しているのか」という妥当性や、LLMやシステムのバイアスが結果を特定の方向に歪めてしまう可能性は重要なポイントだと思っています。

LLMは比較的整合的で協調的に振る舞う傾向があるので、実際の人間社会にある非合理さや情報の偏りが弱く出る可能性はあるんじゃないかと思っています。

特に災害時などの極限状況では、人の意思決定は「どちらを選ぶか」という形に収束することが多いと思っていて、例えば、どちらの方向に逃げるのか、誰を優先して助けるのか、何を持って逃げるのか、どこに避難するのか、といった判断です。

そのため、agentにどのような情報を持たせるか(知っている情報・立場など)や、意思決定をどういうプロンプトで行わせるかは、シミュレーション結果にかなり影響するポイントだと思っています。

このあたりはまだ研究途上の領域だと思うので、色々試して検証していきたいテーマだと思っています!

1