YouMind
ログイン

Generative UI が新しいフロントエンドの形に

@Saboo_Shubham_
英語2026年6月03日
1.1M
1.2K
148
49
3.7K

TL;DR

Generative UI の 3 つのアーキテクチャパターン(Controlled、Declarative、Open-ended)を解説します。また、AG-UI と CopilotKit を使用して、スケーラブルな AI 駆動型アプリケーションを実装する方法を学びましょう。

フロントエンドはかつて固定されたものでした。デザイナーが描き、エンジニアが構築し、ユーザーはそのまま受け取っていました。

しかし、それは過去の話です。

2026 年にリリースされるインターフェースは、ユーザーが実際にリクエストした内容に基づいて、エージェント自身がリアルタイムに部分的に描き出します。テーブルを求めればテーブルが表示されます。それを説明する段落ではありません。

Generative UI は、エージェントが説明するのをやめて、実際に見せることができるようにするレイヤーです。これを構築する方法として、3 つのパターンが登場しており、その違いはほとんどのチームが認識している以上に重要です。

しかし、これを構築する方法は 1 つではありません。3 つあります。そして、ほとんどのチームは自分たちが選択したことに気づかずに、1 つを選んでいます。

プロトコルスタック

3 つのプロトコル。それぞれが 1 つの役割を果たします。

MCP はエージェントをツールに接続します。A2A はエージェント同士を接続します。AG-UI はエージェントをユーザーに接続します。

AG-UI は、以下で説明するすべてのもの(ツールコール、A2UI スキーマ、MCP App イベント、状態差分)を運ぶストリーミングレイヤーです。SSE 上で動作します。状態は同じストリーム上で双方向に流れます。ユーザーが編集すればエージェントが認識し、エージェントが変更すればユーザーが認識します。

A2UI は、エージェントが UI をスキーマとして出力するための Google の仕様です。これは AG-UI 上で動作します。CopilotKit はこれを本番環境で提供しています。

これらのいずれかを解析するパーサーを自分で書く必要はありません。CopilotKit は AG-UI クライアントであり、ストリームをデコードしてくれます。

ほとんどのチームが混同している 3 つのパターン

10 人の開発者に Generative UI とは何か尋ねてみてください。10 通りの答えが返ってきます。そのほとんどは、現在使用しているフレームワークが提供するパターンを説明しているだけです。

実際には 3 つしかありません。そのスペクトルは、制御の度合いが高いものから柔軟性が高いものへと広がっています。

  • Controlled (制御型): コンポーネントを事前に構築します。エージェントがどのコンポーネントをレンダリングするかを選択します。
  • Declarative (宣言型): エージェントがスキーマを出力します。アプリがそれをコンポーネントにマッピングします。
  • Open-ended (自由型): エージェントが生の HTML を書き込みます。アプリはそれをサンドボックス内でレンダリングします。
Shubham Saboo - inline image

2026 年のすべての Gen UI フレームワークは、この線上のどこかに位置します。その違いはアーキテクチャ上のものであり、表面的なものではありません。各パターンは、スケールするにつれて、アプリをそれぞれ異なる方法で破綻させます。

私はさまざまなスタックを試しました。ほとんどのものは 1 つのパターンをうまくカバーしています。CopilotKit に落ち着いたのは、同じランタイム上で AG-UI を利用して 3 つすべてをサポートしているからです。以下のすべての説明は、このスタック上で動作しています。

パターン 1: Controlled (制御型)、フロントエンドが UI を所有する

Shubham Saboo - inline image

これは、ほとんどのチームが始めるパターンです。そして、ほとんどのチームが行き詰まるパターンでもあります。

React コンポーネントを事前に構築します。それをツール名にバインドします。エージェントがそのツールを選択し、コンポーネントはエージェントの引数を props としてチャット内にインラインでレンダリングされます。

1 つのフロントエンドフック。エージェントコードはゼロ。これだけです。

typescript
1"use client";
2import { z } from "zod";
3import { useComponent } from "@copilotkit/react-core/v2";
4
5const expenseChartSchema = z.object({
6 title: z.string(),
7 data: z.array(z.object({ label: z.string(), value: z.number() })),
8});
9
10function ExpenseChart({ title, data }: z.infer<typeof expenseChartSchema>) {
11 return (
12 <section className="rounded-xl border p-4">
13 <h3 className="text-sm font-medium">{title}</h3>
14 <ul className="mt-2 grid gap-1">
15 {data.map((d) => (
16 <li key={d.label} className="flex justify-between text-sm">
17 <span>{d.label}</span>
18 <span>${d.value}</span>
19 </li>
20 ))}
21 </ul>
22 </section>
23 );
24}
25
26export function ExpensesCopilot() {
27 useComponent({
28 name: "showExpenseChart",
29 description: "Render a breakdown of expenses by category.",
30 parameters: expenseChartSchema,
31 render: ExpenseChart,
32 });
33
34 return null;
35}

このフックは、ツールを CopilotKit のランタイムに登録します。ランタイムは、AG-UI を介してそのツールをエージェントに通知します。エージェントがそれを呼び出すと、引数がストリーミングされ、コンポーネントがインラインでレンダリングされます。Python ツールを書いたり、スキーマを配線したり、API ルートを追加したりする必要はありません。

デザインシステムが主導権を握り続けます。

この支出グラフはモックアップではありません。AI Financial Coach Agent は、実際の予算、貯蓄計画、債務返済のために、これとまったく同じカードをレンダリングします。

Shubham Saboo - inline image

まずは生のフックをお求めですか?それは、Generative UI Starter Project の 'use-generative-ui-examples.tsx' です。

トークン税

登録するすべてのコンポーネントは、ユーザーが何かを言う前にエージェントのコンテキストウィンドウに配置されます。典型的なツールの説明とその JSON スキーマは、約 400 トークンになります。25 のコンポーネントでは、毎ターン 10,000 トークンになります。この税をリクエストごとに支払うことになります。

エージェントは誤ったコンポーネントを選択することもあります。あまりにも多くのコンポーネントが類似しているためです。円グラフとドーナツグラフはどちらも「割合を表示する」ものです。エージェントは推測することになります。

いつエージェント側の状態を追加するか

共有状態は、Python ツールを書く価値がある唯一のケースです。エージェントはセッション状態に書き込みます。UI の他の部分はそれを購読し、2 回目の LLM 呼び出しなしで再レンダリングします。メトリクスをピン留めすればダッシュボードが更新され、行を追加すればテーブルが再描画されます。

python
1from google.adk.agents import LlmAgent
2from google.adk.tools import ToolContext
3
4def pin_metric(tool_context: ToolContext, label: str, value: float) -> dict:
5 """メトリクスをユーザーのダッシュボードにピン留めします。"""
6 pinned = tool_context.state.get("pinnedMetrics", [])
7 tool_context.state["pinnedMetrics"] = pinned + [{"label": label, "value": value}]
8 return {"status": "pinned"}
9
10agent = LlmAgent(name="dashboard_agent", model="gemini-3.5-flash", tools=[pin_metric])

フロントエンドは、CopilotKit の共有状態フックを通じてピン留めされたメトリクスを読み取ります。チャットコンポーネントは、同じツール名がフロントエンドフックで配線されているため、引き続きインラインでレンダリングされます。

チャットでメトリクスをピン留めします。パネルは 2 回目のモデル呼び出しなしで再描画されます。それが AI Dashboard Canvas Agent です。

Shubham Saboo - inline image

AI Deep Research Agent はさらにそれを推し進めています。計画、各検索、各ファイル書き込み、そのすべてがライブカードとしてストリーミングされます。その他すべてについては、フロントエンドフックがすべてです。

Shubham Saboo - inline image

いつ Controlled を採用すべきか: 10 以下の高価値フロー。デザインの精度が重要。必要な UI が正確にわかっている場合。

採用すべきでない場合: コードベースがユースケースに比例して線形に増加する場合。25 のコンポーネントは、エージェントの毎ターンに 25 のツール定義が存在することを意味します。

何が破綻するか: エージェントが誤ったコンポーネントを選択する。2 つのツールの説明が意味的に重複する。15 を超えるツールがあると、そのうちの 2 つはおそらく「データを表示する」のように読めます。修正方法: 説明を書き換えて、視覚的なものではなくユーザーの意図を名前にします。「ユーザーが全体の割合を比較したい場合に使用」は「円グラフをレンダリング」よりも優れています。

パターン 2: Declarative (宣言型、A2UI)、エージェントがスキーマを出力する

Shubham Saboo - inline image

これは、ほとんどの本番エージェントアプリが最終的に必要とするパターンです。

エージェントは UI を記述する JSON スキーマを出力します。アプリには、スキーマノードを React(または Svelte、Flutter、など)にマッピングするコンポーネントのカタログがあります。1 つのツールで、多くの UI を実現します。

A2UI は標準仕様です。CopilotKit がランタイムを提供します。ADK がエージェントを実行します。AG-UI が通信路です。

エージェントツールは、3 つの操作を順番に返します。サーフェスの作成、コンポーネントツリーのプッシュ、データのプッシュです。

python
1def search_flights(flights: list[Flight]) -> dict[str, Any]:
2 """フライトを検索し、リッチカードとして表示します。"""
3 return {
4 "a2ui_operations": [
5 {"type": "create_surface", "surfaceId": SURFACE_ID, "catalogId": CATALOG_ID},
6 {"type": "update_components", "surfaceId": SURFACE_ID, "components": FLIGHT_SCHEMA},
7 {"type": "update_data_model", "surfaceId": SURFACE_ID, "data": {"flights": flights}},
8 ]
9 }

これは疑似コードではなく、実際の関数です。ランタイムミドルウェアはツールの結果内の a2ui_operations コンテナを認識し、サーフェスをフロントエンドに転送します。ホテルを追加しますか?新しいスキーマファイルです。異なる surfaceId を持つ別の関数を 1 つ追加するだけです。フロントエンドの作業はゼロです。

固定スキーマ vs 動的スキーマ

上記のコンポーネントツリーは flights.json にあります。あなたが書いたものです。エージェントはデータのみを入力します。これが固定スキーマです。

動的スキーマはそれを反転させます。セカンダリ LLM が会話のコンテキストから毎ターンコンポーネントツリーを書き込みます。最終的には同じ a2ui_operations コンテナになります。Google ADK のショーケースは両方を提供しています。

カタログが契約である

定義リストは、エージェントが出力を許可されているコンポーネントを、props の Zod スキーマとともに示します。レンダラーは React で実装します。タイプミスは、真っ白な画面ではなく、ビルドエラーになります。

typescript
1const renderers: CatalogRenderers<TravelDefinitions> = {
2 FlightCard: ({ props }) => (
3 <article className="rounded-xl border p-4">
4 <header className="flex justify-between">
5 <span>{(props as any).airline}</span>
6 <span>{(props as any).price}</span>
7 </header>
8 <div className="text-sm text-muted-foreground">
9 {(props as any).origin} → {(props as any).destination} · {(props as any).departureTime}
10 </div>
11 </article>
12 ),
13};
14
15export const travelCatalog = createCatalog(travelDefinitions, renderers, {
16 catalogId: "copilotkit://travel-catalog",
17 includeBasicCatalog: true,
18});

両方の半分は Generative UI Starter Project にあり、配線されマッチングされています。search_flights は 'a2ui_fixed_schema.py' に、FlightCard カタログは 'renderers.tsx' にあります。フライトをリクエストしてください。カードがチャットにストリーミングされるのを見てください。

Shubham Saboo - inline image

ボタンやその他のインタラクティブなコンポーネントは、スキーマ内にアクションを持ちます。基本カタログはそれを onClick に配線します。クリックすると、AG-UI を介してイベントがエージェントに送り返されます。エージェントは次に何をレンダリングするかを決定します。クリックハンドラーはゼロです。

トークン計算

50 のカードタイプでも 500 でも、エージェントは 1 つの関数しか認識しません。コンポーネントライブラリが成長しても、1 ターンあたりのトークン数は一定です。

JSON であるため、任意のレンダリングフレームワークに拡張可能です。AG-UI と通信できるエージェントは、初日から A2UI を駆動できます。これを配線するためにエージェントコードに触れる必要はありません。

トレードオフ: LLM がレイアウトを所有します。出力はカタログ内で実行ごとに異なります。法的な開示事項、マーケティングサーフェス、または正確なピクセル配置が重要なものを提供している場合、これは適切なパターンではありません。

Declarative は、ロングテール向けに構築されたパターンです。ダッシュボード、結果、フォーム、カード、ウィジェット。

いつ Declarative を採用すべきか: 事前に構築する時間よりも多くのユースケースがある場合。プロトタイプ段階を超えてトークンの経済性を考慮している場合。

何が破綻するか: カスタム FlightCard を構築したとします。すべてのフライトが基本カタログの汎用カードとしてレンダリングされます。コンソールにエラーはありません。エージェント側の CATALOG_ID とフロントエンドの createCatalog の catalogId が一致していません。フロントエンドがエージェントのターゲットとするカタログを認識できず、基本カタログにフォールバックします。両側で文字列を正確に一致させてください。

パターン 3: Open-ended (自由型)、カタログなし、ルールなし

Shubham Saboo - inline image

3 つ目のパターンは正反対の極端なものです。カタログなし、スキーマなし。ただの空白のキャンバスです。

このバケットには 2 つのサブパターンがあります。

MCP Apps

MCP サーバーは、エージェントが駆動する UI サーフェスを公開します。Excalidraw は私の心に残っている例です。エージェントはキャンバスを完全に制御します。コンテキストから図を描画します。ボード上のすべてのピクセルを所有します。

Shubham Saboo - inline image

クライアントプロトコルをゼロから実装するのは面倒なので、CopilotKit は MCPAppsMiddleware を提供しています。それをエージェントにアタッチし、任意の MCP Apps サーバーを指定します。

typescript
1const agent = new BuiltInAgent({
2 model: "openai/gpt-5.5",
3 prompt: "You are a helpful assistant.",
4}).use(
5 new MCPAppsMiddleware({
6 mcpServers: [{ type: "http", url: "https://mcp.excalidraw.com/mcp", serverId: "my-server" }],
7 }),
8);

MCP Apps Showcase を起動すると、チャットウィンドウ内でフライトを予約したりホテルを予約したりできます。同じミドルウェア、実際の MCP サーバーです。または、さらに進んでみてください。

AI MCP App Builder を使用すると、エージェントが E2B サンドボックスにまったく新しいアプリを書き込み、それをライブでレンダリングできます。

Shubham Saboo - inline image

0:45

サンドボックス化された HTML

エージェントは生の HTML を書き込みます。アプリはそれをサンドボックス化された iframe 内でレンダリングし、セッションを乗っ取れないようにします。

ランタイムは HTML レンダリングツールを登録し、AG-UI を介してエージェントに提供します。エージェントは任意のマークアップでそれを呼び出します。エージェント側で定義する HTML ツールはありません。ランタイムがそれを注入します。

エージェント側の指示は実際に機能しています:

python
1canvas_agent = LlmAgent(
2 name="canvas_agent",
3 model="gemini-3.5-flash",
4 instruction=(
5 "あなたは可視化アシスタントです。ユーザーが何かを見たい、描きたい、"
6 "可視化したいと要求した場合は、インタラクティブな HTML UI を生成してください。"
7 "Tailwind クラスのみを使用してください。外部フォントは使用しないでください。"
8 "ユーザーが特定の色を指定しない限り、ニュートラルな色にしてください。"
9 ),
10)

これらのスタイルルールがないと、モデルはその週のトレーニングデータで最も顕著だった美的感覚をデフォルトで採用します。ルールがあれば、ほとんどの場合、ブランドに近いものが得られます。常にではありません。

ブランドの不一致問題

私はエージェントの主要な UI として Open-ended を採用しようと試みました。1 週間で撤退しました。

火曜日は「ネオ・ブルータリズム」、水曜日は「iOS 4 のクローン」。プロンプトのスタイルルールはエージェントをブランドに向かわせようとします。しかし、それを保証するものではありません。ブランドは変わり続けました。製品は真剣味に欠けるものに感じられました。

Shubham Saboo - inline image

Open-ended は無価値ではありません。誤った適用がされているのです。

1 つのことに適しています。ユーザーがインターフェースの見た目を気にせず、二度と見ることがないような使い捨てのインタラクションです。「電子がどのように動作するか見せて」「過去 10 回のクエリの変な棒グラフを見せて」「この API レスポンスを可視化して」といったものです。Google AI の概要で見られるような種類のものです。

いつ Open-ended を採用すべきか: 1 回限りのクエリ。使い捨ての可視化。サンドボックス化された実験。主要なサーフェスとしては決して使用しないでください。

何が破綻するか: iframe はレンダリングされます。しかし、ボタンがクリックできなかったり、フォームが送信できなかったりします。サンドボックスフラグが厳しすぎるか、ブラウザが拒否する方法で緩すぎるかのいずれかです。iframe のサンドボックスは、allow-scripts と allow-forms のみを許可するように設定してください。それ以外は何も許可しないでください。allow-same-origin は決して許可しないでください。

選択方法

コードを書く前に、決定木を実行してください。

このフロー用にデザイナーがピクセルパーフェクトなモックアップを持っている場合は? Controlled。

出荷するカードタイプやウィジェットが数十種類ある場合は? Declarative。

ユーザーが二度と見ることのない、1 回限りの使い捨ての可視化の場合は? Open-ended。

決められない場合は? デフォルトで Declarative にしてください。上位 3 つのフローは Controlled にアップグレードしてください。デフォルトとして Open-ended を決して使用しないでください。

すでに出荷していて、どのパターンに該当するかわからない場合は、レンダリングツールの数を数えてください。15 を超えている場合は Controlled にいて、壁が近づいています。今週中に A2UI の配線を開始してください。

3 つのパターン。3 つの選択。

Controlled はあなたに賭けます。事前に構築されたコンポーネント、ピクセルパーフェクト。25 を超えるとコストがかかります。

Declarative はスキーマに賭けます。スキーマが契約です。エージェントがそれを埋めます。フラットにスケールします。

Open-ended はモデルに賭けます。カタログなし、スキーマなし、生の HTML。使い捨てには良いです。2 回以上出荷するものにはもろいです。

間違いは、間違ったパターンを選ぶことではありません。自分が 1 つを選んだことに気づいていないことです。

ほとんどのチームは、フレームワークがデフォルトで Controlled であるため、Controlled をデフォルトで選択します。そして、25 のコンポーネントで壁にぶつかり、デモで魅力的に見えるため Open-ended に手を伸ばします。どちらも決定ではなく、漂流でした。

意図的に選択してください。問題にパターンを合わせてください。正確さが必要なフローには Controlled を。ロングテールには Declarative を。使い捨てには Open-ended を。

オープンソース Generative UI エージェントテンプレート

3 つすべてのリファレンスは、新しい awesome-llm-apps の Generative UI Agents セクションにあります。必要なものをクローンし、不要なものを削除してください。

本番環境でのエージェントの提供、AG-UI、そしてスケールするパターンについて、さらに多くの記事を公開していきます。フォロー @Saboo_Shubham_ して、最新情報をチェックしてください。

ワンクリック保存

YouMindでバイラル記事をAI深読み

ソースを保存し、的を絞った質問をし、主張を要約して、バイラル記事を再利用できるノートに変えます。すべてを1つのAIワークスペースで行えます。

YouMindを探索
クリエイターのために

あなたの Markdown をきれいな 𝕏 記事に

自分の長文を投稿するとき、画像・表・コードブロックを 𝕏 向けに整形するのは手間がかかります。YouMind は Markdown 全体を、そのまま投稿できるきれいな 𝕏 記事に変換します。

Markdown → 𝕏 を試す

解読すべきパターンをもっと

最近のバイラル記事

バイラル記事をもっと見る