A2UI 正從以網頁為主的元件轉換為原生 Android。A2UI 0.9 版的新 Jetpack Compose 轉譯器可讓開發人員以原生方式轉譯大型語言模型生成的動態 UI。這項工具不使用網頁檢視,而是採用協同程式驅動的 Actor 架構、密封類別生命週期,以及原生 Material 3 整合。
本課程將逐步介紹新架構,說明伺服器驅動的元件樹狀結構如何轉換為反應式 Compose 節點,以及內建版面配置轉換如何順暢處理結構更新,而不會阻擋 UI。
轉錄稿
大家好,歡迎參加這場講座。我們將介紹如何使用全新的 A2UI 程式庫,在 Android 應用程式中生成 UI。
我是 Nick Butcher,負責 Jetpack Compose 的產品經理。
大家好,我是 Anton,Compose 團隊的工程師。
我正在開發新的 Jetpack Compose A2UI 轉譯器,很高興今天能與大家分享。
是的,隨著 AI 功能崛起,許多應用程式都在研究如何運用 AI 提升或打造全新的使用者體驗。
生成式 UI 是許多人期待的體驗之一,也就是代理程式後端在執行階段為使用者建立全新 UI,我們認為這非常有趣。
但很可惜,目前許多代理後端的回應都像這樣,夾雜在大量文字中。
舉例來說,我要求代理生成預訂行程的新功能。
提示詞如下:
如您所見,它給出的答案其實相當不錯,但卻被困在這一大段文字中,我無法輕鬆與之互動,而且看起來與我的應用程式其他部分完全不同。
生成式 UI 則可讓您建立類似的體驗,根據動態輸入內容即時生成新的 UI,進而提供更多自訂選項或全新體驗。
以下是使用標準原生元件建立的使用者介面範例。
因此,您現在絕對可以自行建構這類體驗,但必須自行定義與後端交換的格式、事件串流和函式等。
A2UI 是 Agent-to-UI 的縮寫,是一種開放通訊協定,採用 JSON 格式定義上述所有內容。
我非常喜歡 A2UI 的其中一個原因,就是它採用宣告式格式。
代理只會傳回要轉譯的 UI JSON 表示法。
它不會傳回用戶端需要執行的任何程式碼,這可能會導致您面臨某些安全漏洞。
只是宣告性質。
那麼,關於 A2UI,我們還需要瞭解哪些概念?
只有兩個主要概念:目錄和平台。
目錄包含文字、按鈕、列和欄等元件,代理可使用這些元件建構 UI。
目錄也提供函式,可讓代理用於設定文字格式、驗證使用者輸入內容或開啟網址。
目錄是一份合約,定義應用程式支援的元件和函式,代理只能使用這些元件和函式建構。
也就是說,正如 Nick 所說,代理不會在應用程式中執行任意程式碼。
它會根據您設計和信任的建構區塊組成 UI。
另一個重要概念是介面。
介面是代理控制的 UI 元素,例如投影片中的行程規劃幫手。
代理會使用 A2UI 通訊協定訊息 (例如 createSurface、updateComponents),以及目錄中定義的元件和函式,以 JSON 格式描述介面的 UI。
說到目錄,目錄是 A2UI 的基本概念,因為它控制代理可用來建構介面的元件。
A2UI 規格定義了基本目錄,這是一組可用於建構大多數 UI 的通用元件。
Jetpack Compose A2UI 轉譯器會使用 Material 3 元件,提供基本目錄的實作。
大部分的目錄元件都可直接使用。
媒體元件有三種:圖片、影片和音訊播放器,您需要插入所選程式庫。
舉例來說,您可以選擇 Coil 載入圖片,並選擇 Media3 播放影片和音訊。
不過,A2UI 和新版轉譯器程式庫的真正威力,在於您可以建立完全自訂的元件和目錄,並完整控管代理可用來建構 UI 的內容。
我們稍後會再說明目錄。
好的,我們來看看這項功能。
請切換到展示機。
這是我們要使用的示範應用程式,我們將建構 A2UI 轉譯器,向您展示這有多簡單。
如畫面中央的小方塊所示,這是一組可傳送給代理的提示。
因此,如果我們點選其中一個提示,系統會將該提示傳送至實際的服務專員後端,希望示範順利。除了提示之外,我們也會附加一些系統指令。
因此,我們會向後端說明 A2UI 通訊協定和元件目錄 (在本例中,是我們在這裡使用的基本目錄),後端可以使用這些元件來建構 UI,我們也會提供一些建構範例。
在示範應用程式中,對話工作階段實作項目已知道如何從代理的回覆中,擷取原始 JSON A2UI 訊息。
因此,在這個案例中,代理回覆了一些文字,後面接著 A2UI 通訊協定訊息:createSurface、updateComponents。
但目前應用程式只會顯示這個原始 JSON,不會轉譯為 UI。
好的,在我們深入探討並實作程式碼之前,如果可以切換回投影片,只要 1 秒鐘,讓我們退一步,告訴您我們將用來實作這個系統的設計。
喔,有了。
好,計畫如下。
因此,AI 代理會將回覆串流至應用程式,並由 ChatSession 類別接收。
ChatSession 會從代理的回覆中擷取原始 A2UI JSON 訊息,並傳送至 A2UI 訊息處理器。
訊息處理器會設定剖析器,將原始 JSON 訊息轉換為語意表示法。
接著,處理器會根據目錄定義驗證訊息,並將訊息對應至一組介面模型。
介面模型會定義 UI 的外觀,以及代理控制的 UI 部分。
接著,系統會使用程式庫提供的 A2UI 介面可組合項,將這些介面模型算繪為原生 Compose UI。
代理與 UI 之間的通訊是雙向的,因此當使用者與 UI 互動時,使用者動作和錯誤會回傳至代理。
總結來說,主要建構模塊有三個:目錄、訊息處理器和介面。
現在就來編寫程式碼。
好,如果可以切換機器,我們就來建構。
好,第一步是新增依附元件。
如您所見,我們已加入 Material 3 A2UI 依附元件,並同步處理專案,因此不必等待 Gradle 執行。
這是頂層依附元件,定義了使用這些 Material 3 元件的基本目錄實作,但下方還有其他更精細的依附元件。
因此,如果不想使用基本目錄 (稍後會說明原因),可以使用更精細的目錄。
但這會以遞移方式帶入其餘內容。
好的。
我們提過有三個要素。
第一步是定義目錄。
因此,我們要在應用程式中建立工廠函式,協助建構基本目錄的 Material 3 實作例項。
因此,我們在這裡使用這個程式庫函式,即 Material A2UI 基本目錄,這個函式也有版本。
如前所述,這個程式庫提供您需要的所有通用元件,但我們不想強迫您使用特定圖片載入或媒體轉譯程式庫,因此您必須自行提供。
舉例來說,Anton 已經建立這個 Coil AsyncImage 可組合項的包裝函式,我們將使用這個包裝函式載入圖片,以及一些額外的邏輯,讓示範從維基百科查詢圖片。
因此,我們可以輕鬆地將其插入目錄元件中。
同樣地,我們也有類似的轉譯、影片和音訊實作,我們會使用 Media3 程式庫,透過 ExoPlayer 轉譯。
只要像這樣,就能輕鬆插入自己的元件。
目錄接下來需要的是一組標準函式。
因此,這是用戶端可以提供的函式。
同樣地,我們會提供這項功能的實作方式以及基本目錄,但我們需要為其提供情境,您將使用該情境執行特定動作,例如在使用者輕觸項目時開啟 URI。
這樣就完成第一個步驟了。
我們已建立目錄。
接下來,我們必須設定訊息處理器。
因此,這個元件會實際剖析回應等內容。
因此,我們將切換至應用程式的檢視模型,首先,我們將實際例項化先前設定的目錄,因為我們需要該目錄。
接著,我們要建立訊息剖析器和訊息處理器物件,這個物件需要存取目錄,才能根據要使用的元件驗證回應。
我們還需要建立一個元件,也就是剖析器,這個物件負責剖析傳回的 JSON 訊息。
我們在這裡建立 ChatSession 物件。
如前所述,ChatSession 已從代理的回覆中擷取原始 A2UI 訊息。
每當有新的 A2UI 訊息準備就緒時,系統就會呼叫這個 onA2uiMessage 回呼。
因此,我們只需要在每則訊息送達時,將訊息傳遞給處理器即可。
我們呼叫 processInput 函式,並提供剖析器和訊息。
因此,processInput 函式會剖析每則訊息,然後將工作排入佇列,讓處理器根據目錄驗證工作,並更新介面模型。
因此,處理器會將目前的一組有效介面模型公開為 activeSurfaces StateFlow。
ChatSession 包含一些邏輯,可將介面模型新增至代理回覆的 UI 狀態,以便轉譯。
因此,我們只需將這個 activeSurfaces StateFlow 做為參數傳遞至 ChatSession。
此外,如前所述,processInput 函式會將工作排入處理器的佇列。
但如要實際開始處理,我們需要在背景執行緒上啟動協同程式。
因此,我們會使用檢視模型範圍,並設定預設調度工具。
在該協同程式中,我們只需要呼叫 processor.collectMessages。
因此,當這個協同程式處於活動狀態時,處理器會處理訊息佇列中的訊息。
因此,我們還需要做一件事:當使用者清除對話記錄時,我們也需要清除目前的介面。
因此,我們呼叫 clearSurfaces。
好的。
我們已完成前兩個步驟。
我們建立了目錄,也建立了剖析訊息的處理器。
接下來,我們需要在 UI 中實際算繪這些回覆。
因此,我們會在剖析器剖析這些表面時收集這些表面。
現在,我們會在 UI 中,將這些內容傳遞至這個代理回覆物件,並直接疊代。
接著,我們會使用這個 A2UI 介面可組合函式,並在疊代模型時將每個模型傳遞給它,以便在所需位置算繪內容。
請注意,如果代理選擇顯示大量內容,可能會選擇使用清單等方式。
因此,我們需要為這個元件設定有界高度 (即高度上限),確保元件能正確算繪。
這樣一來,我們就實作了基本的 A2UI 轉譯器。
現在回到應用程式,重新啟動並再次輸入提示詞,代理應該就會使用該指令,
所以我們直接編寫程式碼。
這不是障眼法。
但沒錯,這項功能會使用實際圖片。
它會使用分頁、對話方塊、日期挑選器、滑桿等所有標準 Material 元件,讓您用來建構應用程式,而不只是顯示文字回覆。
由於這是使用實際 Material 元件的實際應用程式,因此您知道,它會執行實際元件執行的操作。
效能與原生應用程式相當。
例如回應深色模式。
這些是 Material 元件。
因此,如果您有自訂顏色、形狀、字體排版等項目的外部 Material Design 主題設定可組合項,這些元件就會採用這些項目,讓外觀與應用程式的其他部分一致。
正如 Nick 所說,這些是原生 Compose 元件,因此具有原生語意。
也就是說,Compose 內建的所有無障礙功能都支援。
因此,TalkBack、字型縮放、鍵盤導覽等功能都能立即使用。
現在,由於您叫用代理在執行階段生成新的 UI,因此可能會有一些延遲。
你可能需要等待服務專員回覆。
程式庫會盡可能縮短這段延遲時間。
因此,如果我們使用這種方法,就會插入一些等待時間,以便更慢地顯示,說明如何盡量減少這種情況。
沒錯,由於代理可能需要一些時間才能串流完整回覆,因此我們會使用漸進式算繪。
因此,只要根元件一到,我們就會開始顯示介面的 UI。
如果子項尚未載入,所有容器元件都會顯示子項的載入預留位置。
子項元件到達時,會使用 Material 3 動作配置進行動畫處理。
好的。
接著切換到下一個提示,展示程式庫的另一項功能。
以下是另一個提示,我們要預訂晚餐座位。
代理選擇使用這個滑桿元件,
如您所見,您可以拖曳並與代理即時建立的使用者介面互動。
請注意,這不只會變更元件上的數字,下方還有這個格式字串,用於計算數字。
這應該不會為了更新,而來回代理,對吧?
這是雙向繫結。
因此,滑桿元件會繫結至這個介面的記憶體內資料模型。
當值變更時,繫結至資料模型中相同路徑的所有元件都會自動更新。
因此,滑桿會讀取要顯示的值,當我拖曳滑桿時,滑桿也會將值寫回模型以進行更新。
因此,所有作業都會在本機執行,不必與代理程式來回通訊。
但我們也可以要求代理更新資料。
所以我們將人數設為 6 人。
請留意代理回覆時的滑桿。
剛剛跳到 6。
來看看代理傳回的內容。
代理不會傳送全新的 UI 說明,而是只傳送一則簡短訊息 updateDataModel,更新滑桿的路徑。
由於滑桿繫結至該路徑,因此會就地更新。
因此,每個介面的資料模型都是以 Compose 快照狀態為基礎建構,而繫結至路徑的每個元件屬性都是衍生狀態。
因此,每當使用者或代理程式更新資料模型中的值時,繫結至相同值的所有元件都會自動重組。
好的。
Anton,如果我們想採取行動,按下預訂按鈕,會發生什麼事?
目前不會有任何影響。
按鈕會調度動作,但我們尚未將動作傳送給代理。
我們來解決這個問題。
好的,就像我們必須連結至處理器來處理輸入內容一樣,我們也需要連結至任何輸出內容,以及使用者在代理建立的 UI 上執行的任何動作。
為此,我們使用處理器公開的 outboundEvents 流程,並收集這項資訊。
因此,您可以根據使用者輕觸動作或對 UI 執行操作,插入任何想要的邏輯。
因此,我們在這裡將所有動作傳回代理,由代理決定要執行的動作,但您也可以在這個階段決定是否要呼叫自己的後端,或前往應用程式中的其他畫面,一切由您決定。
Anton 已經在這裡插入這個動作,現在只要點選「確認預訂」動作,系統就會將你執行的動作傳回代理。
在後端,您甚至可以決定叫用某些功能。
我們來看看實際傳送給代理的內容。
這是 A2UI 動作訊息。
其中包含情境,而情境包含一組屬性,是代理程式要求在執行這項動作時傳送的屬性。
因此,當我點選按鈕時,系統會根據資料模型中的目前值,解析該特定屬性,並將其傳送給代理。
好的,到目前為止,代理都相當配合,但我們談論的是 AI,而 AI 可能會出錯。
如果代理在 UI 中犯錯,並將錯誤回報給我們,會發生什麼事?
最後一個提示詞,我們要求代理故意出錯,以便向您示範。
我們來看看會發生什麼事。
是啊
如您所見,其中一個元件標示為錯誤。
現在問題已解決。
這就是代理自我修正迴圈。
在每個元件到達 UI 之前,我們會根據目錄結構定義驗證該訊息。
如果元件與結構定義不符,就會標示為錯誤。
基本目錄實作的轉譯器顯示,錯誤僅限於該特定元件,而其餘 UI 仍會顯示。
在背景中,轉譯器會傳送錯誤訊息給代理,讓代理知道發生什麼問題。
因此,在本例中,我們傳送了一則訊息,指出目錄中找不到不存在的未知元件類型。
接著,代理有機會傳送後續回覆,更新現有 UI,並修正 UI。
是啊
在目前的 Material 基本目錄實作中,我們會立即顯示錯誤,但在自訂元件中,您可以決定在等待代理修正錯誤時,繼續顯示元件的載入指標。
這樣一來,使用者可能完全不會看到錯誤。
說到目錄和自訂元件,目前我們只使用基本目錄,但您當然可以根據自己的需求自訂。
因此,當我們使用工廠函式建立目錄時,所有不同元件都會使用預設值。
因此,您可以視需要輕鬆插入自己的元件。
舉例來說,如果我們在應用程式中有自己的設計系統和按鈕,就可以輕鬆插入。
因此,我們只要在建立目錄執行個體時覆寫這個參數,並建立這個元件定義即可。
因此,如果我們建立新的類別來實作,您會看到這裡有一個介面,即 A2UI 基本目錄類型按鈕。
因此,如果我們實作這個介面,就可以插入自己的元件。
是啊
由於我們要覆寫基本目錄中的元件,因此需要實作這個 A2UI 基本目錄按鈕介面。
因此,這個介面已包含元件的部分資訊,例如名稱、說明和屬性結構定義。
因此,如要實作自己的版本,只要實作這個可組合函式即可。
這個函式需要幾個參數。
因此,這些參數是從代理收到的值,且已通過驗證。
例如子項 ID,這會參照將成為按鈕內容的元件。
此外,這個函式還有一個變體,用於描述按鈕類型、動作定義和其他參數。
在正式環境中導入按鈕時,我們需要使用所有這些參數,但為了示範,我們只需要子項和動作。
現在就來實作這個可組合函式。
在這個可組合函式中,我們要發出自己的自訂讚按鈕類型。
如您所見,我們可以使用 A2UI 提供並驗證的參數。
舉例來說,我們可以在 onClick 函式中調度該動作。
我們可以使用這個程式庫類型 DispatchAction,並將 A2UI 定義的動作傳遞給它。
因此,我們使用自己的按鈕,但仍想轉譯子項內容、標籤或代理程式決定使用的任何內容。
因此,我們使用 observeA2uiComponentState 函式並傳遞 childId,以便取得應轉譯的子項內容。
接著,我們會使用這個 A2uiComponent 可組合函式,並傳遞子元件狀態來算繪。
您可以看到,我們只在子內容載入成功時才算繪,但您也可以在這裡加入自己的邏輯,決定在載入中或錯誤狀態時要顯示什麼內容。
回到示範應用程式中的其中一個範例提示,現在我們使用標準 Material 按鈕取代按鈕。
非常細微,希望你看得出來。
您可以看到我們在這裡也使用了非常自訂的按鈕。
很好,這就是替換程式庫類型的方法。但如果想用自訂類型擴充程式庫,該怎麼做?
只要在目錄中新增內容,就能輕鬆完成這項操作。
因此,Anton 會在這裡建立新類別,實作星級評等元件。
因此,我們要在應用程式中新增一些內容,以便在假設的行程中為餐廳評分。
因此,由於我們並未取代現有元件,所以使用這個更通用的介面,也就是 A2UI 元件介面,您可以看到這個介面有幾個需要覆寫的參數。
前兩個是名稱和說明。
這段程式碼說明瞭元件的內容和功能。
這點非常重要,因為當我們序列化目錄並傳送至後端,告知後端可用來建構 UI 的內容時,代理會使用這項資訊決定是否要在回覆時使用這個元件。
因此,您需要詳細說明,並告訴 AI 您確切知道這項操作的作用。
接下來要覆寫的是 properties 屬性,這個屬性基本上會定義這個元件處理的資料。
因此,我們要建立星級評等列,讓使用者為餐廳評分,並需要兩個屬性。
這會是值,例如使用者選取的星級數量,這會是使用者可以互動及變更的可變動屬性,以及星級數量上限。
因此,我們可以看到 Anton 使用這個動態數字類型屬性做為值。
這樣就能知道實際選取了幾顆星。
這項屬性也有專屬說明,可讓代理瞭解這個值的用途。
接著,我們有一個靜態類型屬性,因此我們只使用這個 A2UI 屬性 (類型為數字),定義這個元件可顯示的星級評等上限。
定義這兩個屬性後,只要傳回即可。
因此,覆寫這個 properties 屬性後,我們會建立清單並傳回。
這樣一來,代理就會知道這個元件的用途。
是啊
正如 Nick 所說,這些屬性定義會做為目錄結構定義的一部分傳送,讓代理瞭解如何使用這個元件。
但這個屬性,相同的屬性定義也用於在執行階段擷取類型值。
因此,只有單一事實來源。
接著來看看如何實作。
首先,假設要存取 max 屬性,由於 max 屬性是靜態屬性,因此代理程式會在 properties 物件中內嵌傳送該屬性的值。
因此,如要存取,我們只需要從屬性物件中取得,方法是使用適當的 max 屬性物件做為索引鍵來參照。
由於這項屬性定義為數字,我們也需要將其轉換為 int 並設定預設值。
接下來是 value 屬性。
由於值屬性是動態屬性,因此會參照資料模型中的某個項目。
因此,如要存取,我們需要使用 properties.bind API,並將屬性物件做為索引鍵做為參數傳遞。
因此,這個方法會傳回資料模型中儲存的這個屬性目前值,並訂閱日後的更新。
因此,每當資料模型更新時,這個元件就會自動重新組成。
同樣地,我們也轉換為 int 並設定一些預設值。
我們還需要設定雙向繫結,才能變更評分。
為此,我們使用 bindUpdater API。
我們傳遞定義屬性的相同屬性物件,因此 bindUpdater 會傳回 lambda,讓我們能變更資料模型中這個屬性的值。
現在可以發出星級評等元件了。
好的。
定義自訂元件後,接著要將其新增至目前使用的目錄。
因此,我們之前只是照原樣使用基本目錄。
現在,我們需要建立新目錄,其中會包含所有基本目錄,並加入自訂元件。
因此,目錄是由三個部分組成。
包含 ID、一組元件和一組函式。
我們會使用 URI 定義 ID,也就是專屬的目錄 ID,以及我們使用的版本。
至於元件,我們會從基本目錄中擷取所有元件,然後使用 add 運算子新增剛才建立的自訂元件。
至於函式,我們只會使用基本目錄中的函式。
這樣一來,我們就擴充了基本目錄,加入新的自訂元件類型。
建立自訂目錄就是這麼簡單。
因此,代理會從我們傳送的系統指令,瞭解如何使用目錄。
現在我們已建立自訂目錄,接著也來更新系統指令。
因此,我們會將目錄物件傳遞至建構系統指令的函式。
現在就來看看吧!
因此,到目前為止,我們都是使用這個標準目錄,傳送基本目錄的靜態結構定義。
現在我們有了自訂目錄,可以使用 toJsonSchemaString 函式動態產生結構定義。
這樣一來,系統就會建立這個目錄的完整 JSON 表示法,包括我們剛才新增的星級評等元件。
系統指示也包含一些範例回覆,供代理參考。
因此,我們也會在這裡變更目錄 ID,讓範例參照正確的目錄。
好的。
這樣一來,我們就擴充了基本目錄,加入自己的自訂類型。
因此,如果我們重新部署並嘗試其中一個提示,應該就能看到評分 UI。
接著代理會回覆。
可以看到,這個星號元件是全新的元件,代理在我們實作之前從未見過。
這不是基本目錄的一部分,而是我們剛建立的。
當我點選星號時,系統會呼叫 bindUpdater lambda,並在資料模型中更新值。
如果提交評分,我們就能查看動作,而動作的背景資訊會包含我們剛才設定的值。
好的,現在請切換回投影片。
我們快速介紹了許多內容,希望您已瞭解如何輕鬆將代理的回覆從文字牆,轉換為實際建立的原生 UI。
希望您覺得整合過程相當簡單。
您需要完成三個主要部分:建立元件目錄、實作剖析邏輯,然後在 UI 中算繪。
希望我們有成功展示,這個程式庫能為您完成許多工作,例如漸進式算繪、驗證結構定義、自我修正迴圈或錯誤處理等。
希望今天的示範能讓您快速瞭解,自訂功能非常簡單。
我們示範了如果您使用 Material,基本目錄如何回應 Material Design 主題設定,但擴充或取代目錄中的元件其實非常簡單。
不過,我認為最有效的方法之一,是實際建立自己的目錄。
如果想盡可能控管代理實際可用的 UI,可以建立完全自訂的元件。
如果想讓它完全自由發揮,可以提供非常基本的元件,例如文字標籤和按鈕,然後說「你來建立 UI」。
如要進一步控管,可以建立更精細的元件,並指定「只能使用這些元件」。
因此,您可以控管代理能執行的操作,以及向使用者顯示的內容。
這就是我們剛推出的新 A2UI 轉譯器程式庫和 Jetpack 程式庫。
implementation("androidx.compose.material3:material3-a2ui:1.0.0-alpha02")目前為 Alpha 版,Alpha 02 版最近才推出。
請點選下列連結查看。
developer.android.com 網站上提供說明文件和幾個範例。
我們提供一個基本範例,展示基本目錄和更精細的範例,以及我們剛建立的這個 JetPacker 範例應用程式,您將在今天稍後的 ADS 專題演講中再次看到這個應用程式,示範如何建立這些更精細的元件,控制使用者看到的內容。
如前所述,我們目前處於 Alpha 階段,因此現在是試用、提供意見回饋和功能要求的好時機,歡迎告訴我們您想如何使用這項功能,或希望這項功能可以做到哪些事。
歡迎查看程式庫並提供意見。
這就是我們得到的結果。
期待您和代理能運用這項功能,打造出精彩成果。
感謝您!
感謝您!