YouMind
登入

生成式 UI 是前端開發的新趨勢

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

TL;DR

深入探討生成式 UI 的三種架構模式:受控式 (Controlled)、宣告式 (Declarative) 與開放式 (Open-ended),並學習如何利用 AG-UI 和 CopilotKit 實作可擴展的 AI 驅動應用程式。

前端過去是固定的東西。設計師畫好它,工程師建構它,使用者拿到出貨版本。

這已經過時了。

2026 年出貨的介面,有一部分是由 Agent 本身即時繪製而成,來自使用者實際要求的內容。要表格就給表格,而不是一段描述表格的文字。

生成式 UI 是讓 Agent 不再只用描述的層,而是直接展示。目前浮現了三種建構模式,而它們之間的差異比多數團隊意識到的更重要。

但建構這種東西的方法不只一種,而是三種。而且多數團隊在選擇時,並不知道自己做了選擇。

協定堆疊

三種協定,各司其職。

MCP 連接 Agent 與工具。A2A 連接 Agent 之間。AG-UI 連接 Agent 與使用者。

AG-UI 是串流層,承載你下面會看到的一切:工具呼叫、A2UI 結構、MCP App 事件、狀態差異。透過 SSE 運行。狀態在同一個串流中雙向流動。使用者編輯,Agent 看到。Agent 變更,使用者看到。

A2UI 是 Google 的規格,讓 Agent 以結構形式發出 UI。它建立在 AG-UI 之上。CopilotKit 在生產環境中出貨它。

你不需要為這些東西寫解析器。CopilotKit 是 AG-UI 用戶端,它會為你解碼串流。

多數團隊混淆的三種模式

問十個開發者什麼是生成式 UI,你會得到十種答案。大部分描述的只是他們當前框架出貨的那種模式。

其實只有三種。這條光譜從更多控制到更多彈性。

  • 受控模式: 你預先建好元件,Agent 選擇要渲染哪一個。
  • 宣告式模式: Agent 發出一個結構,你的應用程式將它對應到元件。
  • 開放式模式: Agent 寫出原始 HTML,你的應用程式在沙盒中渲染它。
Shubham Saboo - inline image

2026 年的每個 Gen UI 框架都在這條線上的某個位置。差異是架構性的,不是表面裝飾。每種模式在大規模使用時,會以不同方式破壞你的應用程式。

我試過不同的堆疊。大多數只擅長覆蓋一種模式。最後選擇 CopilotKit,因為它在同一個運行時上支援全部三種模式,且建立在 AG-UI 之上。以下是這個堆疊上運行的所有內容。

模式 1:受控模式,前端擁有 UI

Shubham Saboo - inline image

這是多數團隊的起點,也是多數團隊卡住的地方。

你預先建好一個 React 元件,將它綁定到一個工具名稱。Agent 選擇那個工具,元件就會以 Agent 的引數作為 props 直接在對話中渲染。

一個前端 hook,零 Agent 程式碼。就這樣。

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}

這個 hook 將工具註冊到 CopilotKit 的運行時。運行時透過 AG-UI 將它告知 Agent。當 Agent 呼叫它時,引數會串流進來,你的元件直接渲染。不需要寫 Python 工具,不需要接線結構,不需要新增 API 路由。

你的設計系統繼續掌控一切。

那個支出圖表不是模擬圖。AI Financial Coach Agent 正是用類似的卡片來渲染真實的預算、儲蓄計畫和債務償還。

Shubham Saboo - inline image

想要最基礎的 hook?它就在 Generative UI Starter Project 的 'use-generative-ui-examples.tsx' 中。

Token 稅

你註冊的每個元件,在使用者還沒說任何話之前,就已經佔據了 Agent 的上下文視窗。一個典型的工具描述加上它的 JSON 結構大約耗費 400 個 token。25 個元件就是每次輪轉 10,000 個 token。每個請求你都要支付這筆稅。

Agent 也可能選錯元件。太多元件看起來相似。圓餅圖和甜甜圈圖都「顯示比例」。它只能猜測。

何時加入 Agent 端狀態

共享狀態是唯一值得寫 Python 工具的情況。Agent 寫入會話狀態,UI 的其他部分訂閱後無需第二次 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 """Pin a metric to the user's dashboard."""
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 的共享狀態 hook 讀取固定的指標。對話元件仍然直接渲染,因為同一個工具名稱已透過前端 hook 接線。

在對話中固定一個指標,面板無需第二次模型呼叫即可重繪。這就是 AI Dashboard Canvas Agent。

Shubham Saboo - inline image

AI Deep Research Agent 更進一步。計畫、每次搜尋、每個檔案寫入,全部以即時卡片串流呈現。對於其他所有情況,前端 hook 就是全部的故事。

Shubham Saboo - inline image

何時使用受控模式: 十個或更少的高價值流程。設計精確度很重要。你知道你需要的確切 UI。

何時不該使用: 你的程式碼庫會隨著使用案例線性增長。25 個元件意味著每次 Agent 輪轉都有 25 個工具定義。

可能出錯的地方: Agent 選錯元件。兩個工具描述在語義上重疊。超過 15 個工具後,其中兩個大概讀起來都像「顯示資料」。修正方式:重寫描述,命名使用者意圖而非視覺效果。「當使用者要求比較整體的比例時使用」勝過「渲染圓餅圖」。

模式 2:宣告式(A2UI),Agent 發出結構

Shubham Saboo - inline image

這是大多數生產級 Agent 應用程式最終需要的模式。

Agent 發出描述 UI 的 JSON 結構。你的應用程式有一個元件目錄,將結構節點對應到 React(或 Svelte、Flutter 等)。一個工具,多種 UI。

A2UI 是標準規格。CopilotKit 出貨運行時。ADK 運行 Agent。AG-UI 是傳輸線。

Agent 工具依序回傳三個操作:建立一個表面、推送元件樹、推送資料。

python
1def search_flights(flights: list[Flight]) -> dict[str, Any]:
2 """Search flights and display them as rich cards."""
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 容器,並將表面轉發到前端。加入飯店?新增結構檔案。用不同的 surface ID 再加一個函式。前端完全不需要額外工作。

固定結構 vs 動態結構

上面的元件樹存在 flights.json 中。是你寫的。Agent 只填入資料。這就是固定結構。

動態結構則相反:由一個次要 LLM 根據對話上下文在每次輪轉中寫出元件樹。結尾仍然是同一個 a2ui_operations 容器。Google ADK 展示兩者都有。

目錄就是合約

定義列出 Agent 允許發出的元件,並使用 Zod 結構來定義 props。渲染器負責填入 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

按鈕和其他互動元件在結構中帶有 action。基本目錄將它綁定到 onClick。點擊會透過 AG-UI 將事件發回 Agent。Agent 決定下一步要渲染什麼。零個點擊處理器。

Token 計算

50 種卡片類型或 500 種,Agent 只看到一個函式。隨著你的元件庫增長,每次輪轉的 token 數保持平穩。

可擴展到任何渲染框架,因為它只是 JSON。任何已經能講 AG-UI 的 Agent 都可以在第一天驅動 A2UI。你不需要碰 Agent 程式碼來接線。

取捨: LLM 擁有佈局。輸出在同一個目錄內每次運行可能不同。如果你要出貨法律聲明、行銷頁面,或任何需要精確像素位置的事物,這不是你的選擇。

宣告式是為長尾效應而建的模式。儀表板、結果、表單、卡片、小工具。

何時使用宣告式: 你的使用案例多到你沒時間預先建置。你在原型階段之後仍在乎 token 經濟。

可能出錯的地方: 你建了一個自訂 FlightCard。每個航班都渲染成基本目錄的通用卡片。主控台沒有錯誤。Agent 上的 CATALOG_ID 與前端 createCatalog 中的 catalogId 不匹配。前端無法辨識 Agent 目標的目錄,會退回基本目錄。請確保兩邊的字串完全吻合。

模式 3:開放式,無目錄,無規則

Shubham Saboo - inline image

第三種模式是另一個極端。沒有目錄,沒有結構。只是一塊空白畫布。

這個桶子裡有兩個子模式。

MCP Apps

MCP 伺服器暴露 UI 表面,由 Agent 驅動。Excalidraw 是我印象最深的例子。Agent 完全控制畫布。根據你的上下文繪製圖表。擁有專案上的每個像素。

Shubham Saboo - inline image

從頭實現用戶端協定很痛苦,所以 CopilotKit 出貨了 MCPAppsMiddleware。將它附加到你的 Agent,並指向任何 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 讓 Agent 將一個全新的應用程式寫入 E2B 沙盒,然後即時渲染。

Shubham Saboo - inline image

0:45

沙盒化 HTML

Agent 寫出原始 HTML。你的應用程式在沙盒化的 iframe 中渲染,這樣它無法劫持會話。

運行時註冊一個 HTML 渲染工具,並透過 AG-UI 將它送到 Agent。Agent 用任何想要的標記來呼叫它。Agent 端不需要定義 HTML 工具。運行時會注入它。

Agent 端的指令正在做實際工作:

python
1canvas_agent = LlmAgent(
2 name="canvas_agent",
3 model="gemini-3.5-flash",
4 instruction=(
5 "You are a visualization assistant. When the user asks to see, "
6 "draw, or visualize anything, generate an interactive HTML UI. "
7 "Use Tailwind classes only. No external fonts. Stick to neutral "
8 "colors unless the user names one."
9 ),
10)

如果沒有這些樣式規則,模型就會預設使用訓練資料中那個星期最突出的美學。有了它們,你大部分時候會得到接近你品牌的東西。但並非總是如此。

品牌不一致問題

我試過將開放式作為 Agent 的主要 UI。一週後就撤回了。

星期二「新粗獷主義」,星期三「iOS 4 複製版」。提示中的樣式規則會引導 Agent 靠近你的品牌,但並不保證。品牌一直在變,產品感覺不夠嚴謹。

Shubham Saboo - inline image

開放式並非無用,而是被誤用了。

只適合一件事:一次性互動,使用者不在乎介面長怎樣,也不會再看到第二次。「告訴我電子是怎麼運作的。」「把我最近 10 個查詢做成一個奇怪的長條圖。」「將這個 API 回應視覺化。」就是你在 Google AI 摘要中看到的那種東西。

何時使用開放式: 一次性查詢。一次性視覺化。沙盒實驗。絕對不要作為主要表面。

可能出錯的地方: iframe 渲染了,但按鈕不能點,表單不能送出。沙盒標誌太嚴格,或者太鬆散導致瀏覽器拒絕。將 iframe 沙盒設為允許腳本和允許表單。其他都不要。絕對不要 allow-same-origin。

如何選擇

在寫程式碼之前先做決策樹。

設計師對這個流程有像素完美的 mockup?用受控模式。

要出貨幾十種卡片類型或小工具?用宣告式。

一次性、不會再看到的視覺化?用開放式。

無法決定?預設用宣告式。對前 3 個流程升級到受控模式。永遠不要預設用開放式。

如果你已經在出貨,但不確定自己落在哪個位置,數一下渲染工具。超過 15 個,你就是受控模式,而且瓶頸快到了。本週開始接線 A2UI。

三種模式,三種賭注

受控模式賭在你身上。預先建好的元件,像素完美。超過 25 個就會變貴。

宣告式賭在結構上。結構就是合約。Agent 填入內容。水平擴展。

開放式賭在模型上。沒有目錄,沒有結構,原始 HTML。適合一次性用途。任何會出貨兩次的東西都很脆弱。

錯誤不在於選錯模式,而在於不知道你選了模式。

多數團隊預設用受控模式,因為框架預設就是受控模式。他們在 25 個元件時撞牆,然後轉向開放式,因為它在展示中看起來很有吸引力。這兩者都不是決策,都是漂移。

有意識地選擇。將模式對應到問題。精確的流程用受控模式。長尾用宣告式。一次性的用開放式。

開放原始碼生成式 UI Agent 模板

這三種模式的參考都在新的 Generative UI Agents 區塊中,位於 awesome-llm-apps。複製你需要的,刪掉你不要的。

我會持續發表更多關於在生產環境中出貨 Agent、AG-UI 以及可擴展模式的文章。追蹤我 @Saboo_Shubham_ 以保持關注。

一鍵儲存

使用 YouMind AI 深度閱讀爆款文章

保存原文、追問細節、總結觀點,並在一個 AI 工作空間裡把爆款文章沉澱成可複用筆記。

了解 YouMind
寫給創作者

把你的 Markdown 變成乾淨的 𝕏 文章

圖片上傳、表格、程式碼區塊,往 𝕏 上手動重排太痛苦。YouMind 把整篇 Markdown 一鍵轉成乾淨、可直接發佈的 𝕏 文章草稿。

試試 Markdown 轉 𝕏

更多可拆解樣本

近期爆款文章

探索更多爆款文章