A2UI 的 README 介紹自己的時候,用了一句很漂亮的話:agent 產生的介面「safe like data, but expressive like code」。同一個 repo 的規格文件,講到 v0.9 之後模型吐出來的 JSON,卻寫著「the LLM is not strictly constrained by the schema」。

兩句話都沒寫錯,放在一起讀,才看得出安全到底住在哪裡。A2UI 裡真正擋住危險的,是你自己 client 裡那份元件清單。格式本身幫你擋掉的,比名字聽起來少得多。

A2UI 要處理的場景很具體。agent 想回你的是一張表單、一張卡片、一顆按鈕。讓模型直接寫 HTML 或 JavaScript 丟給前端執行,誰都不放心。A2UI 的答案是讓 agent 送一份描述,畫的事交給 client。

點菜單上寫的是字

README 的說法是這樣:

Agents send a declarative JSON format describing the intent of the UI. The client application then renders this using its own native component library (Flutter, Angular, Lit, etc.).

agent 送的是「意圖」,client 用自己原生的元件庫把它畫出來。拿餐廳來比,agent 是寫點菜單的客人,client 是廚房。點菜單上寫「一碗牛肉麵」,客人不會自己帶刀走進廚房。菜怎麼煮、用哪個鍋,都是廚房的事。

這就是「safe like data」的意思。一張紙條沒辦法在你的機器上執行任何東西。

可是紙條上寫什麼,客人說了算。上面也可以寫「後門鑰匙一把」。擋住這一行的,是廚房只做菜單上有的菜。README 另一段講的正是這件事:

A2UI is a declarative data format, not executable code. Your client application maintains a “catalog” of trusted, pre-approved UI components (e.g., Card, Button, TextField), and the agent can only request to render components from that catalog.

整段的重心在最後半句。agent 只能要求畫 catalog 裡有的元件。宣告式格式讓 agent 沒辦法直接跑程式碼,這是前提;真正的邊界是那份由你維護、事先核准過的清單。

同一份菜單,兩種用途

catalog 在 A2UI 裡其實身兼兩職。官方文件講到換成自己的元件庫時是這樣寫的:

Defining your own catalog allows you to restrict the agent to using exactly the components and visual language that exist in your application. To use your own catalog, simply include it in the prompt in place of the basic catalog.

「include it in the prompt」。同一份 catalog,一份放進 prompt 交給模型,告訴它有哪些菜可以點;另一份留在 client,畫之前拿來對照。

這兩份的份量不一樣。放在 prompt 裡的那份是建議,模型讀了,照不照做是另一回事。留在 client 的那份才是執行:清單上沒有的元件,client 不畫,事情就到此為止。只做了前半段的系統,等於把菜單發給客人,廚房卻照單全收。

我這樣讀,是因為規格自己把前半段的保證拿掉了。

v0.9 放掉的那個保證

A2UI 的 v0.8 是為支援 structured output 的模型設計的。到了 v0.9,規格換了一條路:

While v0.8 was optimized for LLMs that support structured output, v0.9 is designed to be embedded directly within a model’s prompt. The LLM is then asked to produce JSON that matches the provided examples and schema descriptions.

模型現在是「被要求」產出符合範例與 schema 描述的 JSON。被要求,跟被綁住,差很多。規格沒有迴避這一點,緊接著就寫了代價:

The main disadvantage of this approach is that it requires more complex post-generation validation, as the LLM is not strictly constrained by the schema. This requires robust error handling and correction, so the system can identify discrepancies and attempt to fix them before rendering, or request a retry or correction from the LLM.

JSON 進到你手上之後,還得有一段程式去找出哪裡不對、在畫之前修掉,或是退回去請模型重來。這段程式規格不會替你寫。

這個取捨我認為記得很誠實。v0.9 換到的是能整段寫進 prompt 的做法,付出去的是形狀保證。它把帳明明白白記在文件裡,沒有讓「宣告式」三個字替它掩護。

A2UI 規格走到哪裡(2026-09-30 查 README 與 GitHub API)

2025-09-24 repo 建立

GitHub 上的建立時間。現在的 full name 是 a2ui-project/a2ui。

v0.8

為支援 structured output 的模型最佳化。README 現在把它標為 legacy。

v0.9

改成直接嵌進 prompt,模型不再被 schema 硬性約束,改由生成後驗證接手。

v0.9.1

現行的正式版。跟 v0.9 只差兩件小事:MIME type 統一成 application/a2ui+json,surfaceId 只需在現存的 surface 之間唯一。

v1.0

候選版。草稿階段叫 0.10,v1_0/README.md 寫著「currently a candidate for becoming stable」。

2026-09-28 Python 套件發版

python/a2ui-core/v0.2.0 與 python/a2ui-agent-sdk/v0.7.0 同一天發佈。

可以亂序的元件,不能亂序的訊息

協定細節裡有兩條講順序的規則,放在不同段落。元件這一條:A2UI 的元件不是一棵巢狀的樹,是一份扁平清單:

The components are provided as a flat list, and their relationships are defined by ID references in an adjacency list.

每個元件只記自己的 ID 和它引用了誰。規格接著說,這讓 server 可以用任何順序送元件定義,只要 root 那個元件到了,client 就能開始畫,其他部分陸續到、陸續補。client 不必等整份元件定義送完才動手。

可是往下翻到傳輸契約,第一條寫的是另一回事:

Reliable delivery: Messages must be delivered in the order they were generated. A2UI relies on stateful updates (e.g., creating a surface before updating it), so out-of-order delivery can corrupt the UI state.

元件定義可以亂序,訊息本身不行。兩條規則管的是不同層:一條管哪個元件先定義、哪個後補,一條管訊息送達的先後。拼圖片倒出來的順序無所謂,但你總得先把盒子拿出來,才能往裡面倒。

訊息一共四種,每一則都必須「exactly one of」下面這四個 key:

訊息 規格裡寫的重點
createSurface 其中一個元件的 id 必須是 root;建立之後 surfaceId 與 catalogId 就固定了
updateComponents 可以引用還不存在的子元件或資料綁定,client 要先畫 placeholder 頂著
updateDataModel 改介面上的內容,不必重送整個元件結構
deleteSurface 想換 surfaceId 或 catalogId,只能刪掉再重新建立

把這張表跟前面兩節疊起來看,同一件事一再出現。引用了還沒到的元件,client 要能撐住;agent 要了清單以外的元件,client 要能拒絕;模型吐出不合 schema 的 JSON,得有一段程式在畫之前把它抓出來,這段要放在 agent 那端還是 client 那端,我讀到的段落沒有指定。協定定義的是「可以說什麼」,接住這些意外的程式,都要實作的人自己寫。

長得像網址的字串

createSurface 裡那個固定下來的 catalogId,是整份規格裡最容易讀錯的欄位。它的慣例寫法長這樣:https://mycompany.com/1.0/somecatalog。

第一眼看到,很自然會以為 client 拿到這串網址,就去那裡下載 catalog 的定義。規格直接否定了這個想像:

catalogId is a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g., https://mycompany.com/1.0/somecatalog) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Client and server developers must agree on shared catalogs with well-known IDs in order to build systems that are compatible with each other.

寫成網址的樣子,只是為了讓不同組織取的名字不要撞在一起。那個位址底下可以什麼都沒有。A2A 擴充規格講 supportedCatalogIds 時也補了一句「This is not necessarily a resolvable URI.」

所以雙方協商的是一個名字,catalog 的內容各自寫在 client 和 server 的程式碼裡,靠開發者事先約好。同一個 ID 底下,兩邊的元件定義如果各自改了版,協定要怎麼察覺,我讀過的 protocol 與 extension 段落裡沒看到說明。規格其他部分我沒讀完,不能說它沒有。

逃生口,也交給你

catalog 管得這麼嚴,總會碰到清單裝不下的東西,例如一段舊系統的畫面。README 留了一個口:open registry pattern,用它說的「Smart Wrapper」把任何現有元件接進來,「including secure iframe containers for legacy content」。

然後它自己把話講完了:

this places security firmly in the developer’s hands, enabling them to enforce strict sandboxing policies and “trust ladders” directly within their custom component logic rather than relying solely on the core system.

連逃生口的說明,寫的都是同一句話:安全在開發者手上。我讀過的段落裡,沒有一處說「用了這個格式你就安全了」。說它「safe like data」的那句,講的是格式不會被執行,其他的,每一處都指回寫 client 和 agent 的開發者自己。

我會怎麼估這件事的工

A2UI 的方向我是認同的。讓 agent 描述意圖、讓 client 用自己的元件畫,比讓模型直接產前端程式碼乾淨太多。它也不綁傳輸,README 寫的是可以走 A2A,也可以走 AG-UI。站上之前那篇 AG-UI 談的是 AG-UI 自己的事件串流跟 conformance fixture,放在 A2UI 這邊看,AG-UI 只是把這些 JSON 送到 client 的其中一條路。宿主框架目前是 Web 或 Flutter,Flutter 那邊的 GenUI SDK 底層用的就是 A2UI。

它也還很年輕。README 自己標著「Early stage public preview … Expect changes.」現行正式版是 v0.9.1,v1.0 還是候選(兩者都是 2026-09-30 查 README 的狀態)。同一天用 GitHub API 查,16,564 顆星、1,322 個 fork,貢獻者約 97 位(從分頁的 Link header 推的,不是逐頁數)。

如果要把它接進一個真的產品,我會把工時主要估在驗證上,renderer 反而是次要的:catalog 以外的元件怎麼拒絕、不合 schema 的 JSON 在哪一端檢查並修正或退回、跟 server 端怎麼對齊同一個 catalogId 底下的清單內容。這三件事做完之前,「像資料一樣安全」只說對了一半。

會讓我改估的是官方 renderer 本身。如果讀過它的程式碼,確認它預設就拒絕 catalog 以外的元件、預設就擋下不合 schema 的輸入,那這份工會縮成「維護好 catalog」一件事。這一塊我沒讀,SDK 的規格文件也沒讀,所以現在只能照規格文字估。

誠實交代這篇的範圍:我沒有安裝或執行過 A2UI,沒跑過 repo 裡的 Restaurant Finder demo,也沒用任何 renderer 畫過一行 JSON。上面的內容全部來自 GitHub 上的 README 與規格文字,加上 2026-09-30 用 gh api 查的 metadata。v0.9.1 我讀了 protocol、evolution guide 與 A2A 擴充規格的部分段落,custom functions、basic catalog 的實作指南沒讀;v1.0 只讀了它 README 的開頭。

格式與清單

格式決定 agent 能說什麼,清單決定你肯畫什麼。兩條線之間的距離,就是你得自己寫的那段程式。

「safe like data」是格式那條線給的承諾,agent 送來的東西不會被執行。它送來的東西長得對不對、要不要畫,格式管不到。所以之後再看到標榜「模型產出、安全可控」的宣告式格式,我會先去找那份清單住在誰家、由誰負責拒絕。

參考來源