YouMind
Đăng nhập

Generative UI là tương lai của Frontend

@Saboo_Shubham_
TIẾNG ANH03 thg 6, 2026
1.1M
1.2K
148
49
3.7K

TL;DR

Khám phá ba mô hình kiến trúc của Generative UI — Controlled, Declarative và Open-ended — và tìm hiểu cách triển khai chúng bằng AG-UI và CopilotKit để xây dựng các ứng dụng dựa trên AI có khả năng mở rộng.

Giao diện người dùng từng là thứ cố định. Nhà thiết kế vẽ nó. Kỹ sư xây dựng nó. Người dùng nhận được thứ đã được phát hành.

Điều đó đã kết thúc.

Các giao diện được phát hành vào năm 2026 được vẽ một phần bởi chính tác nhân, trong thời gian thực, dựa trên những gì người dùng thực sự yêu cầu. Yêu cầu một bảng, nhận được một bảng. Không phải một đoạn văn mô tả nó.

Generative UI là lớp cho phép các tác nhân ngừng mô tả và bắt đầu hiển thị. Ba mẫu hình đã xuất hiện cho cách xây dựng nó, và sự khác biệt giữa chúng quan trọng hơn hầu hết các đội ngũ nhận ra.

Nhưng không chỉ có một cách để xây dựng thứ này. Có ba cách. Và hầu hết các đội ngũ chọn một cách mà không biết rằng họ đã chọn.

Chồng giao thức (Protocol stack)

Ba giao thức. Mỗi giao thức làm một việc.

MCP kết nối các tác nhân với công cụ. A2A kết nối các tác nhân với nhau. AG-UI kết nối các tác nhân với người dùng.

AG-UI là lớp truyền tải mang mọi thứ bạn sẽ thấy bên dưới: lệnh gọi công cụ, lược đồ A2UI, sự kiện MCP App, delta trạng thái. Chạy qua SSE. Trạng thái chảy theo cả hai hướng trên cùng một luồng. Người dùng chỉnh sửa, tác nhân thấy. Tác nhân thay đổi, người dùng thấy.

A2UI là đặc tả của Google cho các tác nhân phát ra giao diện người dùng dưới dạng lược đồ. Nó chạy trên AG-UI. CopilotKit vận chuyển nó trong sản phẩm.

Bạn không viết trình phân tích cú pháp cho bất kỳ thứ nào trong số này. CopilotKit là một máy khách AG-UI và giải mã luồng cho bạn.

Ba mẫu hình mà hầu hết các đội ngũ nhầm lẫn

Hãy hỏi mười nhà phát triển Generative UI là gì. Bạn nhận được mười câu trả lời. Hầu hết trong số chúng đang mô tả bất kỳ mẫu hình nào mà framework hiện tại của họ vận chuyển.

Chỉ có ba mẫu hình. Phổ trải dài từ kiểm soát nhiều hơn đến linh hoạt hơn.

  • Có kiểm soát (Controlled): Bạn xây dựng sẵn các component. Tác nhân chọn component nào để hiển thị.
  • Khai báo (Declarative): Tác nhân phát ra một lược đồ. Ứng dụng của bạn ánh xạ nó tới các component.
  • Kết thúc mở (Open-ended): Tác nhân viết HTML thô. Ứng dụng của bạn hiển thị nó trong một sandbox.
Shubham Saboo - inline image

Mọi framework Gen UI trong năm 2026 đều nằm ở đâu đó trên dòng này. Sự khác biệt là về mặt kiến trúc, không phải hình thức. Mỗi mẫu hình phá vỡ ứng dụng của bạn theo một cách khác nhau ở quy mô lớn.

Tôi đã thử các stack khác nhau. Hầu hết đều bao phủ tốt một mẫu hình. Tôi đã chọn CopilotKit vì nó hỗ trợ cả ba trên cùng một runtime, chạy trên AG-UI. Đó là stack mà mọi thứ bên dưới chạy trên.

Mẫu hình 1: Có kiểm soát, frontend sở hữu giao diện người dùng

Shubham Saboo - inline image

Đây là nơi hầu hết các đội ngũ bắt đầu. Đây cũng là nơi hầu hết các đội ngũ bị mắc kẹt.

Bạn xây dựng sẵn một component React. Bạn liên kết nó với một tên công cụ. Tác nhân chọn công cụ đó và component hiển thị nội tuyến trong chat với các đối số của tác nhân dưới dạng props.

Một hook frontend. Không cần code tác nhân. Chỉ có vậy.

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 đăng ký công cụ với runtime của CopilotKit. Runtime quảng cáo nó tới tác nhân qua AG-UI. Khi tác nhân gọi nó, các đối số được truyền vào và component của bạn hiển thị nội tuyến. Không cần viết công cụ Python, không cần kết nối lược đồ, không cần thêm route API.

Hệ thống thiết kế của bạn vẫn nắm quyền kiểm soát.

Biểu đồ chi tiêu đó không phải là một bản mockup. AI Financial Coach Agent hiển thị các thẻ tương tự cho ngân sách thực, kế hoạch tiết kiệm và trả nợ.

Shubham Saboo - inline image

Muốn hook cơ bản trước? Đó là 'use-generative-ui-examples.tsx' trong Generative UI Starter Project.

Thuế token (The token tax)

Mọi component bạn đăng ký đều nằm trong cửa sổ ngữ cảnh của tác nhân trước khi người dùng nói bất cứ điều gì. Một mô tả công cụ điển hình với lược đồ JSON của nó chạy khoảng 400 token. 25 component là 10.000 token cho mỗi lượt. Bạn trả khoản thuế đó cho mỗi yêu cầu.

Tác nhân cũng chọn sai component. Quá nhiều cái trông giống nhau. Biểu đồ hình tròn và biểu đồ donut đều "hiển thị tỷ lệ". Nó đoán mò.

Khi nào nên thêm trạng thái phía tác nhân (agent-side state)

Trạng thái dùng chung là trường hợp duy nhất đáng để viết một công cụ Python. Tác nhân ghi vào trạng thái phiên. Các phần khác của giao diện người dùng đăng ký và hiển thị lại mà không cần lời gọi LLM thứ hai. Ghim một chỉ số, dashboard cập nhật. Thêm một hàng, bảng vẽ lại.

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])

Frontend đọc các chỉ số đã ghim thông qua hook trạng thái dùng chung của CopilotKit. Component chat vẫn hiển thị nội tuyến vì cùng tên công cụ được kết nối với hook frontend.

Ghim một chỉ số trong chat. Bảng điều khiển vẽ lại mà không cần lời gọi mô hình thứ hai. Đó là AI Dashboard Canvas Agent.

Shubham Saboo - inline image

AI Deep Research Agent còn đi xa hơn. Kế hoạch, mọi tìm kiếm, mỗi lần ghi tệp, tất cả đều được truyền dưới dạng thẻ trực tiếp. Đối với mọi thứ khác, hook frontend là toàn bộ câu chuyện.

Shubham Saboo - inline image

Khi nào nên phát hành Có kiểm soát: Mười hoặc ít hơn các luồng có giá trị cao. Độ chính xác thiết kế là quan trọng. Bạn biết chính xác các giao diện người dùng mình cần.

Khi nào không nên: Cơ sở code của bạn phát triển tuyến tính với các trường hợp sử dụng. 25 component có nghĩa là 25 định nghĩa công cụ nằm trong mỗi lượt của tác nhân.

Điều gì bị hỏng: Tác nhân chọn sai component. Hai mô tả công cụ trùng lặp về mặt ngữ nghĩa. Trên 15 công cụ, có lẽ hai trong số chúng có nội dung như "hiển thị dữ liệu." Cách khắc phục: Viết lại mô tả để nêu rõ ý định của người dùng, không phải hình ảnh trực quan. "Sử dụng khi người dùng yêu cầu so sánh tỷ lệ của một tổng thể" tốt hơn "hiển thị biểu đồ hình tròn."

Mẫu hình 2: Khai báo (A2UI), tác nhân phát ra lược đồ

Shubham Saboo - inline image

Đây là mẫu hình mà hầu hết các ứng dụng tác nhân sản xuất cuối cùng đều cần.

Tác nhân phát ra một lược đồ JSON mô tả giao diện người dùng. Ứng dụng của bạn có một danh mục các component ánh xạ các nút lược đồ tới React (hoặc Svelte, Flutter, bất cứ thứ gì). Một công cụ. Nhiều giao diện người dùng.

A2UI là đặc tả tiêu chuẩn. CopilotKit vận chuyển runtime. ADK chạy tác nhân. AG-UI là lớp kết nối.

Công cụ tác nhân trả về ba thao tác theo thứ tự: tạo một bề mặt, đẩy cây component, đẩy dữ liệu.

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 }

Đây là hàm thực tế, không phải mã giả. Middleware runtime nhìn thấy vùng chứa a2ui_operations trong kết quả công cụ và chuyển tiếp các bề mặt tới frontend. Thêm khách sạn? Tệp lược đồ mới. Một hàm nữa với một surface ID khác. Không cần thêm công việc frontend.

Lược đồ cố định (Fixed schema) so với lược đồ động (Dynamic schema)

Cây component trên tồn tại trong flights.json. Bạn đã viết nó. Tác nhân chỉ điền dữ liệu. Đó là một lược đồ cố định.

Lược đồ động đảo ngược nó: một LLM thứ cấp viết cây component mỗi lượt từ ngữ cảnh hội thoại. Cùng một vùng chứa a2ui_operations ở cuối. Google ADK showcase vận chuyển cả hai.

Danh mục (Catalog) là hợp đồng (Contract)

Các định nghĩa liệt kê các component mà tác nhân được phép phát ra, với lược đồ Zod cho các props. Các bộ kết xuất (Renderers) điền vào React. Lỗi chính tả trở thành lỗi xây dựng thay vì màn hình trống.

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});

Cả hai nửa đều tồn tại trong Generative UI Starter Project, đã được kết nối và khớp. search_flights trong 'a2ui_fixed_schema.py', danh mục FlightCard trong 'renderers.tsx'. Yêu cầu các chuyến bay. Xem các thẻ truyền vào chat.

Shubham Saboo - inline image

Các nút và các component tương tác khác mang một hành động trong lược đồ. Danh mục cơ bản kết nối nó với onClick. Nhấp chuột kích hoạt một sự kiện trở lại tác nhân qua AG-UI. Tác nhân quyết định hiển thị gì tiếp theo. Không cần xử lý nhấp chuột.

Phép tính token

50 loại thẻ hoặc 500, tác nhân thấy một hàm. Token mỗi lượt không đổi khi thư viện component của bạn phát triển.

Có thể mở rộng tới bất kỳ framework kết xuất nào vì nó chỉ là JSON. Bất kỳ tác nhân nào đã nói AG-UI đều có thể điều khiển A2UI ngay từ ngày đầu. Bạn không động đến code tác nhân để kết nối nó.

Đánh đổi (Trade-off): LLM sở hữu bố cục. Đầu ra thay đổi giữa các lần chạy trong danh mục của bạn. Nếu bạn đang phát hành các tiết lộ pháp lý, bề mặt tiếp thị hoặc bất cứ thứ gì mà vị trí pixel chính xác là quan trọng, đây không phải mẫu hình dành cho bạn.

Khai báo là mẫu hình được xây dựng cho phần đuôi dài (long tail). Dashboard, kết quả, biểu mẫu, thẻ, widget.

Khi nào nên phát hành Khai báo: Bạn có nhiều trường hợp sử dụng hơn thời gian để xây dựng sẵn. Bạn quan tâm đến kinh tế token sau giai đoạn nguyên mẫu.

Điều gì bị hỏng: Đã xây dựng một FlightCard tùy chỉnh. Mọi chuyến bay được hiển thị dưới dạng thẻ chung của danh mục cơ bản. Không có lỗi trong bảng điều khiển. CATALOG_ID trên tác nhân và catalogId trong createCatalog trên frontend không khớp. Frontend không nhận ra danh mục mà tác nhân đang nhắm đến, quay về cơ bản. Khớp chính xác các chuỗi ở cả hai phía.

Mẫu hình 3: Kết thúc mở, không có danh mục, không có quy tắc

Shubham Saboo - inline image

Mẫu hình thứ ba là cực đoan ngược lại. Không có danh mục. Không có lược đồ. Chỉ là một bức vẽ trống.

Hai mẫu hình phụ tồn tại trong nhóm này.

MCP Apps

Một máy chủ MCP hiển thị các bề mặt giao diện mà tác nhân điều khiển. Excalidraw là ví dụ gây ấn tượng với tôi. Tác nhân có toàn quyền kiểm soát canvas. Vẽ sơ đồ từ ngữ cảnh của bạn. Sở hữu mọi pixel trên bảng.

Shubham Saboo - inline image

Việc triển khai giao thức máy khách từ đầu rất khó khăn, vì vậy CopilotKit vận chuyển một MCPAppsMiddleware. Đính kèm nó vào tác nhân của bạn và trỏ nó tới bất kỳ máy chủ MCP Apps nào.

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);

Khởi động MCP Apps Showcase và bạn sẽ đặt chuyến bay và phòng khách sạn ngay trong cửa sổ chat. Cùng middleware đó, các máy chủ MCP thực. Hoặc đi xa hơn.

AI MCP App Builder cho phép tác nhân viết một ứng dụng hoàn toàn mới vào một sandbox E2B, sau đó hiển thị nó trực tiếp.

Shubham Saboo - inline image

0:45

HTML trong sandbox (Sandboxed HTML)

Tác nhân viết HTML thô. Ứng dụng của bạn hiển thị nó bên trong một iframe được sandbox để nó không thể chiếm quyền điều khiển phiên.

Runtime đăng ký một công cụ kết xuất HTML và gửi nó tới tác nhân qua AG-UI. Tác nhân gọi nó với bất kỳ markup nào nó muốn. Không có công cụ HTML nào để định nghĩa ở phía tác nhân. Runtime chèn nó vào.

Hướng dẫn phía tác nhân đang thực hiện công việc thực sự:

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)

Nếu không có các quy tắc về phong cách đó, mô hình sẽ mặc định theo bất kỳ thẩm mỹ nào nổi bật nhất trong dữ liệu huấn luyện của nó vào tuần đó. Với chúng, bạn sẽ nhận được một thứ gần với thương hiệu của mình hầu hết thời gian. Không phải lúc nào cũng vậy.

Vấn đề về sự không nhất quán của thương hiệu

Tôi đã thử phát hành Kết thúc mở như giao diện người dùng chính cho một tác nhân. Tôi đã gỡ nó sau một tuần.

"Tân bạo lực (Neo-brutalist)" vào thứ Ba. "Bản sao iOS 4" vào thứ Tư. Các quy tắc về phong cách trong lời nhắc hướng tác nhân về phía thương hiệu của bạn. Chúng không đảm bảo điều đó. Thương hiệu liên tục thay đổi. Sản phẩm có cảm giác không nghiêm túc.

Shubham Saboo - inline image

Kết thúc mở không phải là vô dụng. Nó bị áp dụng sai.

Phù hợp cho một thứ: các tương tác dùng một lần, nơi người dùng không quan tâm giao diện trông như thế nào và sẽ không bao giờ nhìn thấy nó nữa. "Cho tôi xem cách các electron hoạt động." "Đưa tôi một biểu đồ thanh kỳ lạ của 10 truy vấn gần đây nhất của tôi." "Trực quan hóa phản hồi API này." Loại thứ bạn thấy trong phần tổng quan của Google AI.

Khi nào nên phát hành Kết thúc mở: Các truy vấn một lần. Hình ảnh trực quan dùng một lần. Các thử nghiệm trong sandbox. Không bao giờ dùng làm bề mặt chính.

Điều gì bị hỏng: iframe hiển thị. Các nút không nhấp được. Biểu mẫu không gửi được. Các cờ sandbox quá chặt hoặc quá lỏng theo cách trình duyệt từ chối. Đặt sandbox iframe để cho phép scripts và cho phép biểu mẫu. Không có gì khác. Không bao giờ cho phép cùng nguồn gốc (allow-same-origin).

Cách chọn

Chạy cây quyết định trước khi bạn viết code.

Nhà thiết kế có bản mockup hoàn hảo từng pixel cho luồng này? Có kiểm soát.

Có hàng tá loại thẻ hoặc widget để phát hành? Khai báo.

Hình ảnh trực quan dùng một lần, người dùng sẽ không bao giờ thấy lại? Kết thúc mở.

Không thể quyết định? Mặc định là Khai báo. Nâng cấp lên Có kiểm soát cho 3 luồng hàng đầu. Không bao giờ để Kết thúc mở làm mặc định.

Nếu bạn đã phát hành và không chắc mình đang ở đâu, hãy đếm các công cụ kết xuất. Trên 15, bạn đang ở Có kiểm soát và bức tường đã gần kề. Bắt đầu kết nối A2UI trong tuần này.

Ba mẫu hình. Ba sự đặt cược.

Có kiểm soát đặt cược vào bạn. Các component xây dựng sẵn, hoàn hảo từng pixel. Đắt đỏ sau 25 component.

Khai báo đặt cược vào lược đồ. Lược đồ là hợp đồng. Tác nhân điền vào nó. Mở rộng theo chiều ngang.

Kết thúc mở đặt cược vào mô hình. Không có danh mục, không có lược đồ, HTML thô. Tốt cho việc dùng một lần. Dễ vỡ cho bất cứ thứ gì được phát hành hai lần.

Sai lầm không phải là chọn sai mẫu hình. Mà là không biết mình đã chọn một mẫu hình.

Hầu hết các đội ngũ mặc định là Có kiểm soát vì framework mặc định là Có kiểm soát. Họ đạt đến giới hạn 25 component và chuyển sang Kết thúc mở vì nó có vẻ hấp dẫn trong các bản demo. Cả hai đều không phải là một quyết định. Cả hai đều là sự trôi dạt.

Hãy chọn có chủ đích. Ghép mẫu hình với vấn đề. Có kiểm soát cho các luồng cần chính xác. Khai báo cho phần đuôi dài. Kết thúc mở cho những thứ dùng một lần.

Các mẫu tác nhân Generative UI nguồn mở

Tài liệu tham khảo cho cả ba nằm trong phần Generative UI Agents mới của awesome-llm-apps. Nhân bản những gì bạn cần. Loại bỏ những gì bạn không.

Tôi sẽ đăng thêm về việc phát hành các tác nhân trong sản xuất, AG-UI và các mẫu hình mở rộng quy mô. Theo dõi tôi @Saboo_Shubham_ để cập nhật.

Lưu một chạm

Đọc sâu bài viết viral bằng AI trong YouMind

Lưu nguồn, đặt câu hỏi tập trung, tóm tắt lập luận và biến một bài viết viral thành các ghi chú có thể tái sử dụng trong một không gian làm việc AI duy nhất.

Khám phá YouMind
Dành cho nhà sáng tạo

Biến Markdown của bạn thành bài viết 𝕏 gọn gàng

Khi bạn đăng bài viết dài của riêng mình, việc định dạng hình ảnh, bảng và khối mã cho 𝕏 rất mệt mỏi. YouMind biến cả bản nháp Markdown thành một bài viết 𝕏 gọn gàng, sẵn sàng để đăng.

Thử Markdown sang 𝕏

Thêm pattern để giải mã

Bài viết viral gần đây

Khám phá thêm bài viết viral