このブログは、Gemini Live Agent Challengeハッカソンで私のプロジェクトについて詳述するために作成しました。 #GeminiLiveAgentChallenge
問題点: 歴史は至る所にあるが、埋もれている
地図上のどこにでもピンを落とせば、そこに物語がある。
決定的な戦いが行われた場所。運動が始まった街角。文明全体が神話を築いた山。五百年間、二つの世界を結びつけた港湾都市。
ほとんどの人はこれらの場所を通り過ぎる――あるいは地図上でスクロールして見過ごす――そこで何が起こったのかまったく分かりません。そして好奇心が湧くときには壁にぶつかります。Wikipediaは事実のリストを示します。検索は十個の青いリンクを提示します。どれも物語のようには感じられません。起こった出来事の重さを感じさせるものは何もありません。
私たちはそれを解決するためにTapestryを作りました。
Tapestryは、位置情報ベースの歴史的リサーチエージェントです。地球儀のどこをクリックしても、Tapestryはその場所についてのリッチで多層の歴史的語りを生成します。AI生成の画像、ナレーション付きの音声トラック、年代順のタイムライン、そして物語を体験する四つの独立した方法を備えています。検索エンジンではなく、物語を語る者です。
コアとなる洞察: 歴史には形がある
最初の1行のコードを書く前に、優れた歴史的物語を作る要素は何かを考える時間を惜しみませんでした。Wikipediaの記事ではなく、物語です。
優れた歴史的ストーリーテリングには一貫した形がある:
- 開幕 — シーンを設定し、場所を確立し、読者を現在にいると感じさせる
- 起源と発見 — この場所はどこから来たのか?誰が最初にここにいたのか?
- 転換点 — すべてを変えた出来事
- 人間の層 — 単なる出来事だけでなく、ここで生きた人々。生きられた人生。
- 現代 — 何が残っているのか?何が失われたのか?今、それは何を意味するのか?
- 結び — 物語を締めくくる。読者に持ち帰るべき何かを残す。
この六幕構成は、Tapestryの研究スキーマの基盤となりました。AIが生成するすべてのコンテンツは、これらの段階のいずれかに対応します。その結果は、リズムとアークを持つ物語であり、単なる歴史的事実の羅列ではありません。
アーキテクチャ: 作家のように考える研究パイプライン
Tapestryのバックエンドは、複数段階のパイプラインで、各段階が前の段階を引き継いで構築されます:
ステージ1: 深層リサーチ
ユーザーが場所を選択すると、Tavily(ウェブ検索)と Gemini(統合・合成)を使って、並行リサーチを開始します。Tavilyは現在の、信頼できるウェブソースを引き出します。Geminiはそれらを六幕スキーマに整理された構造化リサーチノートへ統合します。
この二段階アプローチは意図的です。Geminiに歴史を創作させるのではなく、実際のソースから統合させます。すべての主張はウェブから取得した内容に基づいています。その結果はResearchOutputオブジェクトで、セクション、タイムラインエントリ、位置座標、出典引用を含む型付きスキーマです。
// The research schema that drives everything
The image generation uses Google Imagen via Vertex AI. Each prompt is crafted by Gemini to match the tone and period of the narrative — a battle scene gets dramatic lighting, a founding moment gets a sense of dawn and possibility, a human story gets intimacy and warmth.
Stage 3: Audio Narration (Streaming TTS)
The text narrative doesn't just sit on the page — it speaks.
Every research output can be narrated using Google Cloud Text-to-Speech with the en-US-Journey-D voice — a natural, documentary-style voice tuned with a slightly deeper pitch (-1.0 semitones) and a measured speaking rate (0.95x) that fits long-form historical content. This is a deliberate choice: the voice sounds like a documentary narrator, not a virtual assistant.
デフォルトモード。内容は、上部と下部に木製ローラーを備えた古代の巻物のように展開します。褪せた紙の質感、SVGディスプレースメントフィルターによる波打つ裂け目エフェクト、そしてスクロールに合わせて動くパララックス染みのオーバーレイ。冒頭段落の頭字大文字。セクション間の装飾的な紋様。中世の写本にふさわしいと感じられる ❧ 区切り線。
このモードは、長文で没入感があり、急がず読むことを想定して設計されています。
2. 石板 — タイムライン・エクスプローラー
石に刻まれた垂直方向の年代順タイムライン。背景は濃い花崗岩色(#2a2218)、彫られた文字には textShadow の落とし影とハイライト、年ラベルにはローマ数字が添えられ、そして私たちが最も時間をかけたディテール――本当に粗く彫られた縁――が特徴です。
縁には数十個の不規則な頂点を持つ多層 clip-path ポリゴン、上部に適用された SVG ディスプレースメントフィルター、折れ目にはひび割れのハイライトラインを組み合わせています。結果は、誰かが岩から本当に削り出したかのように見え、CSS の border-radius のようには見えません。
イベントは、スクロールすると whileInView でビューに現れ、デスクトップでは左寄せと右寄せが交互に配置されます。
3. セルロイド・フィルム — 視覚ストーリー
ビンテージ風のフィルムストリップとして描かれる交互の物語。暖かなアンバー系のブラウン地色(#2a1f0e)、半透明のスプロケット穴と内側の影、セピア調の文字、そして全体の上に重ねられたフィルム粒子の SVG フィルター。
テキストの段落は、場面番号付きの脚本のイントロ・タイポグラフィとしてスタイル設定されています。画像は、ライトリーク、ビネット、わずかなデサチュレーションを備えた投影スライドのようにデザインされています――古いドキュメンタリーのフィルムフレームのように。パララックスの粒状オーバーレイは、スクロール時に内容とは異なる速度で動きます。
このモードは、ビジュアルストーリーが生成されたときにのみ有効になります。生成されていない場合は、滑らかに要約テキスト表示へフォールバックします。
4. ハードカバーの本 — フリップブック
最も技術的に複雑なモード。内容は、タイトルページ、セクションごとに1ページ、タイムラインページ、ソースページのようにページに分割され、開いたハードカバーの本として表示されます。
左側は金の装飾を施した革の背表紙で、タイトルは writing-mode: vertical-rl で縦書きされています。
右側は紙の粒状感のあるクリーム色のページで、ランニングヘッダーと隅のページ番号が配置されています。
ページめくりのアニメーションは、二つの重ね合わせレイヤーを使用します。宛先ページが下にあり、現在のページは右端(後方ナビゲーションの場合は左端)から rotateY を使って折りたたまれ、下の次のページを現します。折りたたまれたページの裏面はやや濃い紙で、葉の裏側を示します。角に点滅する三角形が、ページがめくれることを示唆します。
交互出力: テキスト + 画像 + 音声を一体として
ハッカソンの Creative Storyteller 部門は、「創造ディレクターのように思考・制作し、テキスト、画像、音声、動画を1つの流れる出力ストリームに滑らかに織り込む」エージェントを求めています。
Tapestry の答えは、3つのモダリティが一体となって連携すること――順番通りではなく、独立してではなく、統合された体験として。
テキスト + 画像: 一体で設計
要点は、Gemini がテキストと画像を別々に生成しないことです。統一されたクリエイティブブリーフ――各ビートが散文の一節であるか、それを描く画像であるかの連なり――を生成します。画像のプロンプトは周囲のテキストを前提として作成されます。散文は、後に画像が続くことを想定して書かれます。いっしょに設計されています。
プロンプト構造の簡易版は次のとおりです:
You are a documentary filmmaker and writer. Generate an interleaved narrative
about [location] that alternates between:
- Narration passages (2-3 sentences, cinematic, present-tense)
- Image descriptions (vivid, specific, painterly — describe what the camera sees)
The narrative should follow this arc: [six-act structure]
Each image should visually complement the passage before it.
Output as a JSON array of { type: "text"|"image", content: string } objects.
結果は、都市が創設された瞬間の一節を読み、朝焼けのAI生成画像を見て、それから最初の商人の到着を読み、港がどのように見えたかを視覚化します。テキストと画像が織り交ざり、互いをより強くします。
オーディオ: 第三の柱
ビジュアルストーリーは、読む・見せるだけでなく、話します。
ナレーションボタンを押すと、研究出力全体が Google Cloud Text-to-Speech の en-US-Journey-D ボイスを使用して音声へ変換されます――長編のナレーション向けに、やや低めの音域と、落ち着いたテンポを持つ、自然なドキュメンタリースタイルの声です。これはロボットのスクリーンリーダーではありません。BBC のドキュメンタリーのように聞こえます。
音声は書かれた語りと同期しています。開幕の文章を読み進めると、画面に同じ本文が表示されます。重要な瞬間のセクションへ移ると、そのセクションの映像が表示されます。三つのモダリティ――散文、画像、声――はすべて同じ物語を同時に伝え、それぞれが互いを強化します。
重要なのは、音声が完全なナレーションを合成するのを待つのではなく、チャンク単位でストリーミングされる点です。本文は文の境界で約50語のセグメントに分割され、それぞれ独立して合成され、Server-Sent Eventとしてクライアントへ送信されます。ボタンをクリックしてから数秒で再生が開始します。最初のチャンクが再生され、残りはまだ生成中です。これが、ファイルダウンロードのように感じさせず、ライブのように感じさせる理由です。
User clicks "Listen" →
Chunk 1 synthesized → sent → starts playing immediately
Chunk 2 synthesized → queued → plays when chunk 1 ends
Chunk 3 synthesized → queued → ...
[audio plays continuously, seamlessly, while generation is still running]
結果は、実際に没入型の体験です。読んで、画像を見て、同時に語りを聴くことができます。テキスト、画像、音声が交互に組み合わさっているのではなく、三つの感覚を横断して語られる一本の統一されたストーリーです。
Google Cloud Services: 完全スタック
| サービス | 役割 |
|---|---|
| Gemini 2.0 Flash | リサーチの統合、物語生成、インターリーブストーリーテリング |
| Google Imagen (Vertex AI) | ビジュアルストーリー モード向けのAI画像生成 |
| Google Cloud Text-to-Speech | ストリーミング音声ナレーション(en-US-Journey-D) |
| Google Cloud Translate | 15言語以上への完全な研究翻訳 |
| Google Maps Embed API | コンテンツブロックに埋め込まれたインタラクティブな地図 |
| Google Cloud Storage | ヒーロー画像と生成画像のホスティング |
| MongoDB Atlas | リサーチの永続性、履歴、共有トークン |
| Next.js on Cloud Run | フロントエンドとAPIルート、サーバーサイドレンダリング |
パイプラインは完全に Google Cloud ネイティブです。リサーチ、統合、画像生成、音声、翻訳――すべての AI 機能が Google のサービスを介して動作します。これは OpenAI のラッパーに Google Maps のウィジェットを追加したものではありません。ゼロから Google AI スタックの上に構築しています。
主要な技術的決定
すべてをストリーミング
テイプストリーは、あらゆる層でストリーミングします。研究パイプラインは、各段階が完了するごとに Server-Sent Events を送信します――研究、ストーリーテリング、画像生成が順に進行します。TTS のナレーションはチャンク単位でストリーミングされるため、音声は数秒で再生を開始します。ユーザーは、すべてが完了するのを待つためのローディングスピナーを決して見つめることはありません。
型付き研究スキーマ
「ResearchOutput」スキーマは、AIとUIの間の契約です。すべての表示モードは、同じ型付き構造から読み取ります。これにより、研究パイプラインに触れることなく新しい表示モードを追加でき、表示モードを触れることなく研究パイプラインを改善できます。スキーマは、すべてのセクションに stage、すべてのブロックに type、すべてのソースに url があることを強制します。
根拠に基づく研究 — 設計上の幻覚ゼロ
これは私たちが下した最も重要な信頼性の決定であり、審査基準に直接対処します:「エージェントは幻覚を避けていますか? 根拠づけの証拠はありますか?」
答えは構造的であり、単なるプロンプト指示ではありません。
ステップ1:生成前に取得する。 Gemini が一文も書く前に、Tavily はその場所に関する実在のウェブソースを取得します — Wikipediaの記事、歴史協会のページ、学術的参照、ニュースアーカイブ。これらはそのまま Gemini のプロンプトへソース材料として渡されます。
ステップ2:統合する。創作はしない。 研究プロンプトは、提供されたソースから統合するように Gemini に明示的に指示しており、学習データだけに頼ることはありません。取得したソースで裏付けられていない主張は、記述には現れてはいけません。
ステップ3:すべてを引用する。 Tavily によって取得されたすべてのソースは、ResearchOutput に保存され、grounded: boolean フラグが付与されます。ウェブから直接取得されたソースには grounded: true と表示されます。これらの出典は Flipbook の Sources ページおよび Scroll の Sources セクションに表示され、ユーザーに見える状態で、メタデータには隠されていません。
ステップ4:構造化出力が一貫性を保証する。 Gemini は厳密な JSON スキーマに従って出力します。出力が準拠していない場合――例えば、間違ったステージ名、必須フィールドの欠落、ブロックの形式不良――パイプラインはそれを拒否して再試行します。スキーマがガードレールです。スキーマに存在しない幻の「stage」が出力に含まれることは、最終出力には決してありません。
結果として、Tapestry の物語は実在のソースに基づき、透明性のある引用がなされ、ユーザーに届く前に構造的に検証されます。完全無謬ではありません — どんな LLM システムでもそうではありません — しかし、このアーキテクチャは「この場所について Gemini に尋ねる」という素朴な方法よりも幻覚をかなり難しくします。
全体を通じたダークモード
すべての表示モードには、注意深く設計されたダークバージョンがあります。羊皮紙の巻物は、ダーク背景に深い琥珀色になります。石の板は暗いままですが、刻まれた文字が明るくなります。フィルムストリップは暖かい琥珀色のアクセントを添えて、ほとんど黒に近い色調へ深まります。フリップブックのページは暖かなほぼ黒(#1e1c18)へ。ダークモードは後付けではなく、第一級の体験です。
私たちが直面した課題
インタリーブ出力の一貫性: Gemini が信頼性高く、テキストと画像のビートの適切なバランスを持つ、周囲の散文と実際に一致する画像プロンプトを含む、構造化されたインタリーブJSON配列を出力するようにするには、かなりのプロンプトエンジニアリングが必要でした。最終的なプロンプトには、明示的な例、ビートごとの長さ制約、そして画像プロンプトは抽象的な概念ではなく、カメラが捉えるものを説明すべきであるというリマインダーが含まれています。
画像生成の遅延: Imagen は3〜8秒かかります。物語あたり6〜10枚の画像があると、逐次生成だと1分かかります。可能な限り画像を並列生成し、完成次第クライアントにストリーム送信するため、ビジュアルストーリーモードは一度に全てではなく、漸進的に埋められていきます。
ページ折りたたみのアニメーション: フリップブックのページめくりエフェクトは、3回の完全な書き直しを経ました。最初は react-pageflip を使用しました(絶対配置で DOM を占有し、Tailwind の流動的レイアウトを壊します)。2 番目は framer-motion の AnimatePresence と rotateY を使いました(スライドのように見え、折りたたみには見えませんでした)。最終版は 2 つの積み重ねレイヤーを使用します。下に宛先ページ、上に現在のページが折りたたまれていく構造で、両面に backfaceVisibility: hidden を適用して、前面が消え去り、背面が短い間見えるようにします。これが実際のページのめくれの感覚を生み出します。
TTS ストリーミングの状態機械: オーディオプレーヤーには 4 つの状態(待機中、読み込み中、再生中、一時停止)と、再生中に非同期で埋まるチャンクキューがあります。元の実装には、playNextChunk が空の useCallback 依存関係で定義されていたため、キューへの凍結参照をキャプチャするという陳腐化したクロージャのバグがありました。我々は、それをレンダリングごとに更新される参照に関数を格納することで修正しました。これにより、非同期コールバックは常に現在の実装を呼び出します。
石板の縁: タイムラインのエッジを実際に荒々しく見せるには — CSS の clip-path のように見えるのではなく — 外側の欠けと内側の割れの 2 枚の重なる clip-path ポリゴンレイヤー、上部に適用された SVG 変位フィルタ、そして折れ目の箇所に沿う割れのハイライトラインが必要でした。最終的なポリゴンは、エッジごとに 40 以上の頂点を持ちます。
私たちが学んだこと
スキーマこそが成果物です。 ResearchOutput スキーマ — 6 つのステージ、型付きブロック、タイムラインエントリ — が、Tapestry を機能させる要素です。すべての表示モード、すべての AI プロンプト、すべてのデータベースクエリは、それを軸に構築されています。初期の段階でスキーマを正しく設計することは、後で行われた 12 回分のリファクタリングを回避できました。スキーマは、根拠づけを実現可能にする要因でもあります。出力が構造化されていれば、それを検証できます。検証できれば、ユーザーに届く前に、形式が乱れた内容や幻覚的な内容を拒否できます。
真のインタリーブ出力は、「テキストを生成してから画像を追加する」というものとは質的に異なります。 Gemini が同一のパスで本文と画像プロンプトを同時に作成する場合 — 各段落に続く画像があることを知っている — 結果は一貫した視覚的アークとなり、挿絵付きの記事にはなりません。画像は取得したものではなく、指示されたように感じられます。 それが、クリエイティブディレクターと検索エンジンの違いです。
音声が三要素を完成させます。 テキストと画像だけでも豊かな体験です。ナレーションを加えると、それは別のものになり — アクティブに読む必要がなく、目を閉じて聴くことができるものへ。en-US-Journey-D の声は、そのドキュメンタリーな抑揚で、歴史記事をストリーミングプラットフォームにふさわしいと感じさせます。3 つのモダリティを合わせると、それぞれの総和以上のものになります。
根拠づけはプロンプト指示ではなく、アーキテクチャの決定です。 あなたはシステムプロンプトで Gemini に「作り話をしないで」と伝えることができます。 それは役に立ちます。 しかし幻覚を実際に防ぐのは、実在のソースを最初に取得し、文脈として渡し、厳格な出力スキーマを適用し、すべてを引用することです。 プロンプト指示は最後の防御線です。 アーキテクチャが最初の防御線です。
視覚的アイデンティティは没入感に影響します。 私たちは四つの表示モードをすべて同じように見せることもできました — 白い背景のクリーンなカード — しかし、各モードには独自のマテリアル・メタファーがあります:羊皮紙、石、セルロイド、紙。 ユーザーはその違いに気づきます。 石板は古代の雰囲気を感じさせます。 フィルムストリップはドキュメンタリーのように感じられます。 美学は装飾ではなく、物語の一部です。 古代ローマについての話が石板に刻まれているときは、サンセリフのカードで同じ話が語られるのとは受け取り方が異なります。
ストリーミングは体験を変えます。 研究パイプラインが進行状況をリアルタイムでストリーミングするとき — 「ソースを調査中... 語りを生成中... 視覚的ストーリーを作成中... 画像を生成中...」 — ユーザーは何かが作られているのを見ていると感じます。スピナーよりも没入感が高いです。期待感が高まります。そして「Listen」をクリックしてから2秒以内に音声が再生されるとき — 完全な語りがまだ合成されていなくても — それは瞬時に感じられます。待機遅延は、技術的な問題であると同時に UX の問題でもあります。
自分で試してみる
- GitHub: https://github.com/SarthakRawat-1/tapestry
- デモ動画: https://vimeo.com/1174045515?share=copy&fl=sv&fe=ci
Gemini 2.0 Flash、Google Imagen、Google Cloud Text-to-Speech、Google Cloud Translate、Google Maps、Google Cloud Storage、Next.js、Framer Motion、MongoDB、Tailwind CSS — すべて Google Cloud 上で動作。
Gemini Live Agent Challenge のために作成されました。 #GeminiLiveAgentChallenge





