# 策展 · X (Twitter) 🔥🔥🔥

> 📖 本站完整內容索引（documentation index）：[llms.txt](/llms.txt)

> 作者：Shubham Saboo (@Saboo_Shubham_) · 平台：X (Twitter) · 日期：2026-06-04

> 原始來源：https://x.com/Saboo_Shubham_/status/2062220865643982875

## 中文摘要

# Generative UI 是全新的前端

過去的前端是固定的。設計師繪製它，工程師建構它，使用者則接收最終交付的成果。

那個時代已經結束了。

2026 年交付的介面，部分是由 Agent 本身即時繪製的，且完全基於使用者的實際需求。要求一個表格，就給你一個表格，而不是一段描述表格的文字。

Generative UI 是讓 Agent 從「描述」轉向「展示」的層級。目前已經出現了三種建構模式，而這些模式之間的差異，比大多數團隊意識到的還要重要。

然而，建構這類介面並沒有單一標準，總共有三種方式。大多數團隊在選擇時，甚至沒意識到自己做了選擇。

## 協議堆疊 (The protocol stack)

三種協議，各司其職。

MCP 將 Agent 連接到工具；A2A 將 Agent 彼此連接；AG-UI 則將 Agent 連接到使用者。

AG-UI 是串流層，承載了你下方將看到的一切：工具呼叫、A2UI 結構描述 (schema)、MCP App 事件以及狀態差異 (state deltas)。它基於 SSE 運行，狀態在同一個串流中雙向流動：使用者編輯，Agent 看得見；Agent 修改，使用者也看得到。

A2UI 是 Google 用於 Agent 輸出 UI 結構描述的規範，它運行在 AG-UI 之上。CopilotKit 已將其應用於生產環境。

你不需要為這些協議編寫解析器，CopilotKit 本身就是一個 AG-UI 客戶端，會為你解碼串流。

## 大多數團隊容易混淆的三種模式

問十個開發者什麼是 Generative UI，你會得到十種答案。大多數人描述的，其實只是他們當前框架所採用的模式。

實際上只有三種。這是一個從「更多控制」到「更多靈活性」的頻譜：

- **Controlled（受控模式）**：你預先建構好元件，由 Agent 選擇要渲染哪一個。
- **Declarative（宣告式模式）**：Agent 輸出結構描述，你的應用程式將其對應到元件。
- **Open-ended（開放式模式）**：Agent 編寫原始 HTML，你的應用程式在沙盒 (Sandbox) 中渲染它。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396792-iaHI0u2OsbMAAPV5Vjpg.jpg)

2026 年的每一個 Gen UI 框架都位於這條線上的某個位置。這些差異是架構上的，而非表面上的。每一種模式在規模擴大時，都會以不同的方式導致你的應用程式出錯。

我嘗試過不同的堆疊，大多數只能很好地涵蓋其中一種模式。最終我選擇了 CopilotKit，因為它在同一個執行時期 (runtime) 上支援這三種模式，並運行在 AG-UI 之上。這就是下方所有範例所使用的堆疊。

## 模式 1：Controlled（受控模式），前端擁有 UI 控制權

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396090-diaHI24iaKagAAnd8jpg.jpg)

這是大多數團隊開始的地方，也是大多數團隊卡住的地方。

你預先建構一個 React 元件，並將其綁定到一個工具名稱。Agent 選擇該工具時，元件就會在對話中內嵌渲染，並將 Agent 的參數作為 props 傳入。

只需要一個前端 hook，完全不需要編寫 Agent 端的程式碼。就這麼簡單。

```typescript
"use client";
import { z } from "zod";
import { useComponent } from "@copilotkit/react-core/v2";

const expenseChartSchema = z.object({
  title: z.string(),
  data: z.array(z.object({ label: z.string(), value: z.number() })),
});

function ExpenseChart({ title, data }: z.infer<typeof expenseChartSchema>) {
  return (
    <section className="rounded-xl border p-4">
      <h3 className="text-sm font-medium">{title}</h3>
      <ul className="mt-2 grid gap-1">
        {data.map((d) => (
          <li key={d.label} className="flex justify-between text-sm">
            <span>{d.label}</span>
            <span>${d.value}</span>
          </li>
        ))}
      </ul>
    </section>
  );
}

export function ExpensesCopilot() {
  useComponent({
    name: "showExpenseChart",
    description: "Render a breakdown of expenses by category.",
    parameters: expenseChartSchema,
    render: ExpenseChart,
  });

  return null;
}
```

這個 hook 會向 CopilotKit 的執行時期註冊該工具，執行時期再透過 AG-UI 將其宣告給 Agent。當 Agent 呼叫它時，參數會串流進來，你的元件就會內嵌渲染。不需要編寫 Python 工具、不需要定義複雜的結構描述，也不需要新增 API 路由。

你的設計系統始終保持主導地位。

那個費用圖表並不是模型，AI 金融教練 Agent 正是透過這種方式，為真實的預算、儲蓄計畫和債務償還渲染卡片。

想要最原始的 hook 範例嗎？它就在 Generative UI Starter Project 中的 `use-generative-ui-examples.tsx`。

**token 的代價**

你註冊的每一個元件，在使用者開口之前，就已經佔用了 Agent 的 context window。一個典型的工具描述及其 JSON 結構描述大約會消耗 400 個 token。如果有 25 個元件，每次對話轉折 (turn) 就會消耗 10,000 個 token。你必須為每次請求支付這筆「稅」。

而且 Agent 還會選錯元件。因為太多元件看起來很像，例如圓餅圖和甜甜圈圖都用於「顯示比例」，Agent 只能猜測。

**何時加入 Agent 端狀態**

當需要共享狀態時，編寫 Python 工具是值得的。Agent 將資料寫入 session 狀態，UI 的其他部分會訂閱並重新渲染，無需再次呼叫 LLM。釘選一個指標，儀表板就會更新；新增一行資料，表格就會重繪。

```python
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext

def pin_metric(tool_context: ToolContext, label: str, value: float) -> dict:
    """Pin a metric to the user's dashboard."""
    pinned = tool_context.state.get("pinnedMetrics", [])
    tool_context.state["pinnedMetrics"] = pinned + [{"label": label, "value": value}]
    return {"status": "pinned"}

agent = LlmAgent(name="dashboard_agent", model="gemini-3.5-flash", tools=[pin_metric])
```

前端透過 CopilotKit 的共享狀態 hook 讀取釘選的指標。對話元件依然內嵌渲染，因為同一個工具名稱已經與前端 hook 綁定。

在對話中釘選一個指標，面板就會重繪，無需再次呼叫模型。這就是 AI 儀表板畫布 Agent 的運作方式。

AI 深度研究 Agent 將此發揮得更淋漓盡致：計畫、每一次搜尋、每一個檔案寫入，全部都以即時卡片的形式串流呈現。除此之外，前端 hook 幾乎能解決所有問題。

**何時採用 Controlled 模式**：當你有 10 個或更少的高價值流程，且設計精確度至關重要，且你明確知道需要哪些 UI 時。

**何時不該採用**：當你的程式庫隨使用案例線性增長時。25 個元件意味著每次 Agent 對話轉折都要處理 25 個工具定義。

**會出什麼問題**：Agent 選錯元件。當兩個工具描述在語意上重疊時，超過 15 個工具後，其中兩個讀起來可能都很像「顯示資料」。解決方法：重寫描述以強調「使用者意圖」而非「視覺呈現」。例如「當使用者要求比較整體比例時使用」遠比「渲染一個圓餅圖」有效。

## 模式 2：Declarative（宣告式模式，A2UI），Agent 輸出結構描述

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396809-iaHI24mX9agAEAzhajpg.jpg)

這是大多數生產環境 Agent 應用程式最終需要的模式。

Agent 輸出描述 UI 的 JSON 結構描述，你的應用程式則擁有一個元件目錄，將結構描述節點對應到 React（或 Svelte、Flutter 等）。一個工具，多種 UI。

A2UI 是標準規範，CopilotKit 提供執行時期，ADK 運行 Agent，AG-UI 則是傳輸管道。

Agent 工具會依序回傳三個操作：建立介面 (surface)、推送元件樹、推送資料。

```python
def search_flights(flights: list[Flight]) -> dict[str, Any]:
    """Search flights and display them as rich cards."""
    return {
        "a2ui_operations": [
            {"type": "create_surface", "surfaceId": SURFACE_ID, "catalogId": CATALOG_ID},
            {"type": "update_components", "surfaceId": SURFACE_ID, "components": FLIGHT_SCHEMA},
            {"type": "update_data_model", "surfaceId": SURFACE_ID, "data": {"flights": flights}},
        ]
    }
```

這不是虛擬碼，而是實際的函式。執行時期中介軟體會偵測工具結果中的 `a2ui_operations` 容器，並將介面轉發給前端。想新增飯店功能？只需一個新的結構描述檔案，以及另一個帶有不同介面 ID 的函式，完全不需要額外的前端工作。

**固定結構描述 vs 動態結構描述**

上述的元件樹存在於 `flights.json` 中，是由你編寫的，Agent 只負責填入資料，這就是「固定結構描述」。

「動態結構描述」則相反：由第二個 LLM 根據對話上下文，在每次轉折時編寫元件樹。最終同樣使用 `a2ui_operations` 容器。Google ADK 展示範例兩者皆有。

**目錄 (Catalog) 就是合約**

定義檔列出了 Agent 被允許輸出的元件，並附帶用於 props 的 Zod 結構描述。渲染器負責填入 React 元件。拼字錯誤會變成建置錯誤，而不是空白畫面。

```typescript
const renderers: CatalogRenderers<TravelDefinitions> = {
  FlightCard: ({ props }) => (
    <article className="rounded-xl border p-4">
      <header className="flex justify-between">
        <span>{(props as any).airline}</span>
        <span>{(props as any).price}</span>
      </header>
      <div className="text-sm text-muted-foreground">
        {(props as any).origin} → {(props as any).destination} · {(props as any).departureTime}
      </div>
    </article>
  ),
};

export const travelCatalog = createCatalog(travelDefinitions, renderers, {
  catalogId: "copilotkit://travel-catalog",
  includeBasicCatalog: true,
});
```

兩者都存在於 Generative UI Starter Project 中，已完成綁定與對應。`search_flights` 在 `a2ui_fixed_schema.py`，`FlightCard` 目錄在 `renderers.tsx`。要求搜尋航班，看著卡片串流進對話中。

按鈕和其他互動式元件會在結構描述中攜帶一個動作。基本目錄會將其綁定到 `onClick`，點擊後會透過 AG-UI 將事件發回給 Agent，由 Agent 決定下一步渲染什麼。完全不需要手動寫點擊處理器。

**token 的計算**

無論是 50 種還是 500 種卡片類型，Agent 看到的都只有一個函式。隨著你的元件庫擴大，每次轉折的 token 消耗量保持平穩。

由於它只是 JSON，因此可擴展到任何渲染框架。任何已經支援 AG-UI 的 Agent 都能在第一天就驅動 A2UI，你不需要為了綁定它而修改 Agent 程式碼。

權衡：LLM 擁有版面配置權。在你的目錄範圍內，輸出結果會隨每次執行而有所不同。如果你要發布法律聲明、行銷頁面，或任何對像素位置有嚴格要求的內容，這不是適合你的選擇。

Declarative 模式是為長尾需求而生的：儀表板、搜尋結果、表單、卡片、小工具。

**何時採用 Declarative 模式**：當你的使用案例多到無法一一預先建構，且你在原型階段後開始在意 token 經濟效益時。

**會出什麼問題**：建構了自訂的 `FlightCard`，但所有航班都渲染成基本目錄的通用卡片，且控制台沒有錯誤訊息。這是因為 Agent 上的 `CATALOG_ID` 與前端 `createCatalog` 中的 `catalogId` 不匹配。前端無法識別 Agent 目標的目錄，因此退回到基本目錄。請確保兩端的字串完全一致。

## 模式 3：Open-ended（開放式模式），無目錄，無規則

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396990-iaHI25lZ8bUAAiX0Ajpg.jpg)

第三種模式是另一個極端：沒有目錄，沒有結構描述，只有一張空白畫布。

此類別包含兩種子模式。

**MCP Apps**

MCP 伺服器公開了由 Agent 驅動的 UI 介面。Excalidraw 是讓我印象最深刻的例子。Agent 獲得了畫布的完全控制權，可以根據你的上下文繪製圖表，擁有畫板上的每一個像素。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396108-iaHI0yNVwbYAACK88jpg.jpg)

從零開始實作客戶端協議非常痛苦，因此 CopilotKit 提供了 `MCPAppsMiddleware`。將其附加到你的 Agent，並指向任何 MCP Apps 伺服器即可。

```typescript
const agent = new BuiltInAgent({
  model: "openai/gpt-5.5",
  prompt: "You are a helpful assistant.",
}).use(
  new MCPAppsMiddleware({
    mcpServers: [{ type: "http", url: "https://mcp.excalidraw.com/mcp", serverId: "my-server" }],
  }),
);
```

啟動 MCP Apps Showcase，你就能在對話視窗中預訂航班和飯店。使用相同的中介軟體，連接真實的 MCP 伺服器，甚至可以做得更多。

AI MCP App Builder 允許 Agent 將全新的應用程式寫入 E2B 沙盒中，然後即時渲染出來。

**沙盒 HTML**

Agent 編寫原始 HTML，你的應用程式將其渲染在沙盒 iframe 中，以防止它劫持會話。

執行時期會註冊一個 HTML 渲染工具，並透過 AG-UI 將其傳送給 Agent。Agent 可以使用任何它想要的標記語言來呼叫它。Agent 端不需要定義 HTML 工具，執行時期會自動注入。

Agent 端的指令在此發揮了關鍵作用：

```python
canvas_agent = LlmAgent(
    name="canvas_agent",
    model="gemini-3.5-flash",
    instruction=(
        "You are a visualization assistant. When the user asks to see, "
        "draw, or visualize anything, generate an interactive HTML UI. "
        "Use Tailwind classes only. No external fonts. Stick to neutral "
        "colors unless the user names one."
    ),
)
```

如果沒有這些樣式規則，模型會預設使用其訓練資料中出現頻率最高的審美風格。有了這些規則，大多數時候你能得到接近品牌風格的結果，但並非總是如此。

**品牌不一致的問題**

我曾嘗試將 Open-ended 作為 Agent 的主要 UI，但一週後就撤下了。

週二看起來像「新粗獷主義」，週三又變成「iOS 4 復刻版」。提示詞中的樣式規則可以引導 Agent 靠近你的品牌，但無法保證一致性。品牌風格不斷變更，產品顯得不夠專業。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1780542396311-iaHI0y7Y7agAAzYK9jpg.jpg)

Open-ended 並非毫無用處，只是被誤用了。

它適合一次性的互動，使用者不在乎介面長相，且之後也不會再看到它。例如：「展示電子是如何運作的」、「給我一個關於我過去 10 次查詢的奇怪長條圖」、「視覺化這個 API 回應」。這類內容你在 Google AI 概覽中經常看到。

**何時採用 Open-ended 模式**：一次性查詢、拋棄式視覺化、沙盒實驗。絕對不要作為主要介面。

**會出什麼問題**：iframe 渲染了，但按鈕點不動，表單送不出。這是因為沙盒標記太嚴格，或者太寬鬆導致瀏覽器拒絕執行。請將 iframe 沙盒設定為允許腳本和表單，除此之外不要設定任何權限，絕對不要使用 `allow-same-origin`。

## 如何選擇

在寫程式碼之前，先跑一遍決策樹：

設計師是否為這個流程提供了像素級精確的設計稿？選擇 **Controlled**。

是否有數十種卡片類型或小工具需要發布？選擇 **Declarative**。

是否為使用者永遠不會看第二次的一次性拋棄式視覺化？選擇 **Open-ended**。

無法決定？預設選擇 **Declarative**。針對前 3 大核心流程升級為 **Controlled**。永遠不要將 **Open-ended** 作為預設。

如果你已經在生產環境中運行，但不確定自己處於哪種模式，數數看你的渲染工具數量。超過 15 個，代表你處於 Controlled 模式，且已經快撞牆了。本週就開始導入 A2UI 吧。

## 三種模式，三場賭注

- **Controlled** 賭的是你：預先建構的元件，像素級精確，但超過 25 個後成本過高。
- **Declarative** 賭的是結構描述：結構描述就是合約，Agent 負責填入資料，擴展性極佳。
- **Open-ended** 賭的是模型：沒有目錄，沒有結構描述，原始 HTML。適合拋棄式內容，但對於需要重複使用的功能來說太脆弱。

錯誤不在於選錯模式，而在於根本不知道自己選了哪一種。

大多數團隊預設選擇 Controlled，因為框架預設就是如此。當元件達到 25 個時，他們撞到了牆，因為在演示中看起來很吸引人而轉向 Open-ended。這兩者都不是經過深思熟慮的決策，而是隨波逐流。

請有目的地選擇。將模式與問題匹配：需要精確的流程用 Controlled，長尾需求用 Declarative，拋棄式內容用 Open-ended。

**開源 Generative UI Agent 範本**

這三種模式的參考實作都在 `awesome-llm-apps` 的全新 Generative UI Agents 章節中。複製你需要的部分，移除你不需要的。

---

我將會發布更多關於在生產環境中發布 Agent、AG-UI 以及可擴展模式的文章。追蹤我的 X @Saboo_Shubham_ 以獲取最新資訊。

## 標籤

Agent, AIGC, Web, 產業趨勢, Generative UI
