整理一下,我今晚上的上課學習筆記
原始資料來源: https://codelabs.developers.google.com/next26/adk-a2ui
多數 AI Agent 應用受限於純文字或 Markdown 輸出,在面對雲端資源監控、資料庫狀態或分散式服務排程等複雜維運場景時,單純文字會造成使用者極大的閱讀與決策負擔。
Google Agent Development Kit (ADK) 結合 Agent-to-User Interface (A2UI) 協定解決了此問題。A2UI 是一種開放的宣告式 UI 協定,內建 18 個基礎元件(Primitives)與 3 種訊息格式。後端 Agent 只需負責產生描述 UI 階層與資料關聯的結構化 JSON,前端客戶端(如 React、Lit、Angular 或 Flutter)即可原生渲染為對應的 UI 元件,無須針對每種對話情境撰寫專屬的前端頁面。
+-------------------------------------------------------------------------------+
| Agent 後端 |
| |
| +---------------------+ +---------------------+ |
| | Mock 資源 / Tools | ----> | LLM 推論核心 | |
| | (resources.py) | | (gemini-3-flash) | |
| +---------------------+ +---------------------+ |
| | |
| v |
| +---------------------+ |
| | A2UI Schema Manager | |
| +---------------------+ |
| | |
| v |
| +-------------------------+ |
| | 後處理回呼 (Callback) | |
| | (a2ui_callback) | |
| +-------------------------+ |
+-------------------------------------------|-----------------------------------+
| A2UI 訊息 (JSON / DataPart)
v
+-------------------------------------------------------------------------------+
| 前端客戶端 |
| |
| +-------------------+ +--------------------+ +--------------------+ |
| | beginRendering | | surfaceUpdate | | dataModelUpdate | |
| | (畫布與根節點) | | (扁平化元件樹) | | (響應式資料模型) | |
| +-------------------+ +--------------------+ +--------------------+ |
| | |
| v |
| +---------------------------------+ |
| | A2UI 原生渲染引擎 | |
| | (ADK Dev UI, React, Flutter) | |
| +---------------------------------+ |
| | |
| v |
| +---------------------------------+ |
| | 互動式動態儀表板 | |
| +---------------------------------+ |
+-------------------------------------------------------------------------------+
A2UI 協定主要基於三大訊息類型、扁平化元件樹與 18 個基礎元件。
每一次 A2UI 回應包含由以下三種結構組成的 JSON 陣列:
beginRendering(啟動渲染):宣告渲染畫布(Surface)與根節點 ID。{"beginRendering": {"surfaceId": "default", "root": "main-column"}}
surfaceUpdate(介面結構更新):傳遞 UI 元件結構。A2UI 採用扁平化列表(Flat List)而非深度巢狀物件,父元件以 ID 參考子元件,能有效降低 LLM Token 消耗並避免解析上限。{
"surfaceUpdate": {
"surfaceId": "default",
"components": [
{"id": "main-column", "component": {"Column": {"children": {"explicitList": ["header", "svc-card"]}}}},
{"id": "header", "component": {"Text": {"text": {"literalString": "雲端資源清單"}, "usageHint": "h1"}}},
{"id": "svc-card", "component": {"Card": {"child": "svc-name"}}},
{"id": "svc-name", "component": {"Text": {"text": {"path": "service_name"}}}}
]
}
}
dataModelUpdate(資料模型更新):將動態資料與介面結構抽離。元件透過 {"path": "key"} 綁定欄位,資料變更時僅需推送更新值即可刷新介面。{
"dataModelUpdate": {
"surfaceId": "default",
"contents": [
{"key": "service_name", "valueString": "auth-service"}
]
}
}
Card、Column、Row、List、Tabs、Divider、Modal
Text、Image、Icon、Video、AudioPlayer
TextField、DateTimeInput、MultipleChoice、CheckBox、Slider
Button
執行環境以 Google Cloud Shell 為主,使用專案 test20260902001 與 Vertex AI。
1. 設定環境變數與切換專案
gcloud config set project test20260902001
export GOOGLE_CLOUD_PROJECT=test20260902001
export GOOGLE_CLOUD_LOCATION=global
export GOOGLE_GENAI_USE_VERTEXAI=True
2. 啟用 Vertex AI API
gcloud services enable aiplatform.googleapis.com --project test20260902001
3. 安裝必要套件與設定路徑
pip install -U google-adk a2ui-agent-sdk
export PATH="$HOME/.local/bin:$PATH"
4. 建立工作目錄
mkdir -p ~/test20260902001/a2ui_agent
cd ~/test20260902001
首先建立傳統純文字 Agent,用於對比導入 A2UI 前後的輸出差異。
1. 建立資源查詢 Tool (a2ui_agent/resources.py)
cat <<'EOF' > a2ui_agent/resources.py
RESOURCES = [
{
"name": "auth-service",
"type": "Cloud Run",
"region": "us-west1",
"status": "healthy",
"cpu": "2 vCPU",
"memory": "1 GiB",
"instances": 3,
"url": "https://auth-service-abc123.run.app",
"last_deployed": "2026-04-18T14:22:00Z",
},
{
"name": "events-db",
"type": "Cloud SQL",
"region": "us-east1",
"status": "warning",
"tier": "db-custom-8-32768",
"storage": "500 GB SSD",
"connections": 195,
"version": "PostgreSQL 16",
"issue": "Storage usage at 92%",
},
{
"name": "analytics-pipeline",
"type": "Cloud Run",
"region": "us-west1",
"status": "error",
"cpu": "2 vCPU",
"memory": "4 GiB",
"instances": 0,
"url": "https://analytics-pipeline-ghi789.run.app",
"last_deployed": "2026-04-10T16:45:00Z",
"issue": "CrashLoopBackOff: OOM killed",
},
]
def get_resources() -> list[dict]:
"""Get all cloud resources in the current project."""
return RESOURCES
EOF
2. 建立純文字 Agent (a2ui_agent/agent.py)
cat <<'EOF' > a2ui_agent/agent.py
from google.adk.agents import Agent
from .resources import get_resources
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that reports on project resources.",
instruction=(
"You are a cloud infrastructure assistant. When users ask about their "
"cloud resources, use the get_resources tool to fetch the current state. "
"Summarize the results clearly in plain text."
),
tools=[get_resources],
)
EOF
3. 啟動與測試
adk web --port 8080 --allow_origins "*" --reload_agents
開啟網頁預覽(通訊埠 8080),發送提示詞 What's running in my project?,觀察純文字條列回傳。測試完畢後在終端機按 Ctrl + C。
使用 A2uiSchemaManager 自動組裝 Prompt,讓模型學會 18 個基礎元件並輸出符合格式的 JSON 陣列。
覆寫更新 a2ui_agent/agent.py:
cat <<'EOF' > a2ui_agent/agent.py
from google.adk.agents import Agent
from a2ui.schema.manager import A2uiSchemaManager
from a2ui.basic_catalog.provider import BasicCatalog
from .resources import get_resources
schema_manager = A2uiSchemaManager(
version="0.8",
catalogs=[BasicCatalog.get_config("0.8")],
)
instruction = schema_manager.generate_system_prompt(
role_description=(
"You are a cloud infrastructure assistant. When users ask about "
"their cloud resources, use the get_resources tool to fetch the "
"current state."
),
workflow_description=(
"Analyze the user's request and return structured UI when appropriate."
),
ui_description=(
"Use cards for resource summaries, rows and columns for comparisons, "
"icons for status indicators, and buttons for drill-down actions. "
"Do NOT use markdown formatting in text values. Use the usageHint "
"property for heading levels instead. "
"Respond ONLY with the A2UI JSON array. Do NOT include any text "
"outside the JSON. Put all explanations into Text components."
),
include_schema=True,
include_examples=True,
)
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that renders rich A2UI interfaces.",
instruction=instruction,
tools=[get_resources],
)
EOF
建立後處理回呼(Callback),將模型輸出的 JSON 字串轉換為前端渲染器可辨識的 DataPart 二進位封包。
1. 建立後處理工具模組 (a2ui_agent/a2ui_utils.py)
cat <<'EOF' > a2ui_agent/a2ui_utils.py
import json
import re
from google.genai import types
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_response import LlmResponse
def _wrap_a2ui_part(a2ui_message: dict) -> types.Part:
"""將 A2UI 訊息封裝為 adk web 可渲染的 DataPart"""
datapart_json = json.dumps({
"kind": "data",
"metadata": {"mimeType": "application/json+a2ui"},
"data": a2ui_message,
})
blob_data = (
b"<a2a_datapart_json>"
+ datapart_json.encode("utf-8")
+ b"</a2a_datapart_json>"
)
return types.Part(
inline_data=types.Blob(
data=blob_data,
mime_type="text/plain",
)
)
def a2ui_callback(
callback_context: CallbackContext,
llm_response: LlmResponse,
) -> LlmResponse | None:
"""攔截文字 JSON 並轉換為前端渲染結構"""
if not llm_response.content or not llm_response.content.parts:
return None
for part in llm_response.content.parts:
if not part.text:
continue
text = part.text.strip()
if not text:
continue
if not any(k in text for k in ("beginRendering", "surfaceUpdate", "dataModelUpdate")):
continue
# 移除 markdown 代碼標記
if text.startswith("```"):
text = text.split("\n", 1)[-1]
if text.endswith("```"):
text = text[:-3].strip()
# 尋找 JSON 開頭位置
json_start = None
for i, ch in enumerate(text):
if ch in ("[", "{"):
json_start = i
break
if json_start is None:
continue
json_text = text[json_start:]
try:
parsed, _ = json.JSONDecoder().raw_decode(json_text)
except json.JSONDecodeError:
try:
fixed = "[" + re.sub(r'\}\s*\{', '},{', json_text) + "]"
parsed, _ = json.JSONDecoder().raw_decode(fixed)
except json.JSONDecodeError:
continue
if not isinstance(parsed, list):
parsed = [parsed]
a2ui_keys = {"beginRendering", "surfaceUpdate", "dataModelUpdate", "deleteSurface"}
a2ui_messages = [msg for msg in parsed if isinstance(msg, dict) and any(k in msg for k in a2ui_keys)]
if not a2ui_messages:
continue
new_parts = [_wrap_a2ui_part(msg) for msg in a2ui_messages]
return LlmResponse(
content=types.Content(role="model", parts=new_parts),
custom_metadata={"a2a:response": "true"},
)
return None
EOF
2. 註冊回呼函式至 Agent (a2ui_agent/agent.py)
cat <<'EOF' > a2ui_agent/agent.py
from google.adk.agents import Agent
from a2ui.schema.manager import A2uiSchemaManager
from a2ui.basic_catalog.provider import BasicCatalog
from .resources import get_resources
from .a2ui_utils import a2ui_callback
schema_manager = A2uiSchemaManager(
version="0.8",
catalogs=[BasicCatalog.get_config("0.8")],
)
instruction = schema_manager.generate_system_prompt(
role_description=(
"You are a cloud infrastructure assistant. When users ask about "
"their cloud resources, use the get_resources tool to fetch the "
"current state."
),
workflow_description=(
"Analyze the user's request and return structured UI when appropriate."
),
ui_description=(
"Use cards for resource summaries, rows and columns for comparisons, "
"icons for status indicators, and buttons for drill-down actions. "
"Do NOT use markdown formatting in text values. Use the usageHint "
"property for heading levels instead. "
"Respond ONLY with the A2UI JSON array. Do NOT include any text "
"outside the JSON. Put all explanations into Text components."
),
include_schema=True,
include_examples=True,
)
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that renders rich A2UI interfaces.",
instruction=instruction,
tools=[get_resources],
after_model_callback=a2ui_callback,
)
EOF
1. 釋放通訊埠並啟動伺服器
fuser -k 8080/tcp || kill -9 $(lsof -t -i:8080)
adk web --port 8080 --allow_origins "*" --reload_agents
2. 介面測試驗證
在預覽視窗重新整理後點擊 +New Session,依序測試三種不同意圖的提示詞:
全面概覽:輸入 What's running in my project?
呈現包含服務名稱、規格、URL 連結與運行狀態的完整 Card 列表。
告警聚焦:輸入 Does anything need my attention?
自動過濾正常服務,僅針對警告(Cloud SQL 儲存空間達 92%)與錯誤(Cloud Run OOM CrashLoopBackOff)生成醒目的告警排版。
動態表單:輸入 I need to deploy a new service
動態生成包含服務名稱文字欄位、規格下拉選單及部署動作按鈕的互動表單。
Errno 98 Address already in use):fuser -k 8080/tcp || kill -9 $(lsof -t -i:8080)
ValueError: No API key was provided):export GOOGLE_CLOUD_PROJECT=test20260902001
export GOOGLE_CLOUD_LOCATION=global
export GOOGLE_GENAI_USE_VERTEXAI=True
gcloud auth application-default login --no-launch-browser
本實驗使用 adk web 的內建渲染器進行驗證。於實際生產架構中,後端 Agent 程式碼完全不需更動,前端僅需導入對應生態系的官方 A2UI 渲染套件:
| 平台 / 框架 | 官方套件名稱 | 安裝指令 |
|---|---|---|
| React | @a2ui/react |
npm install @a2ui/react |
| Lit | @a2ui/lit |
npm install @a2ui/lit |
| Angular | @a2ui/angular |
npm install @a2ui/angular |
| Flutter (行動/桌面端) | Flutter GenUI SDK |
透過 Flutter pub 依賴引入 |
測試完成後可終止本機伺服器與清理雲端專案:
1. 終止伺服器:在 Cloud Shell 終端機按下 Ctrl + C。
2. (選用)刪除測試專案:
gcloud projects delete test20260902001 --quiet
以下是英文的筆記
Modern generative artificial intelligence applications have historically been constrained by the limitations of conversational text interfaces. While Large Language Models (LLMs) excel at reasoning, contextual understanding, and semantic synthesis, delivering output as unformatted plain text or raw markdown places substantial cognitive overhead on human users. In enterprise environments—especially across cloud infrastructure monitoring, database administration, and distributed service management—operational efficiency demands structured, scannable, and actionable visual representations.
The combination of the Agent Development Kit (ADK) and the Agent-to-User Interface (A2UI) protocol bridges this architectural divide. Rather than producing static blocks of prose, autonomous agents leverage declarative UI specifications to compose dynamic, rich, and contextually grounded frontend experiences.
A2UI decouples visualization from business logic by introducing an intermediate layout representation governed by 18 universal primitives and 3 foundational message types. The backend agent remains agnostic of the target rendering environment, emitting structured messages that describe interface hierarchies, component attributes, and reactive data models. The client application—whether executing within a browser via React, Lit, or Angular, or natively on mobile and desktop runtimes via Flutter—interprets this protocol to instantiate native components. Consequently, engineering teams eliminate the need to implement bespoke frontend routes or static templates for every specialized tool query or multi-step agent workflow.
+-------------------------------------------------------------------------------+
| Agent Backend |
| |
| +---------------------+ +---------------------+ |
| | Mock Data / Tools | ----> | LLM Reasoning | |
| | (resources.py) | | (gemini-3-flash) | |
| +---------------------+ +---------------------+ |
| | |
| v |
| +---------------------+ |
| | A2UI Schema Manager | |
| +---------------------+ |
| | |
| v |
| +-------------------------+ |
| | Post-Processing / Hook | |
| | (a2ui_callback) | |
| +-------------------------+ |
+-------------------------------------------|-----------------------------------+
| A2UI Messages (JSON / DataPart)
v
+-------------------------------------------------------------------------------+
| Frontend Client |
| |
| +-------------------+ +--------------------+ +--------------------+ |
| | beginRendering | | surfaceUpdate | | dataModelUpdate | |
| | (Canvas / Root) | | (Component Tree) | | (Reactive State) | |
| +-------------------+ +--------------------+ +--------------------+ |
| | |
| v |
| +---------------------------------+ |
| | A2UI Native Renderer Engine | |
| | (ADK Dev UI, React, Flutter) | |
| +---------------------------------+ |
| | |
| v |
| +---------------------------------+ |
| | Interactive Dynamic Dashboard | |
| +---------------------------------+ |
+-------------------------------------------------------------------------------+
The A2UI specification is built upon three pillars: Message Segregation, Flat Component Graphing, and a Constrained Primitives Catalog. Understanding these mechanics is essential for developing performant, deterministic, and error-tolerant agent interfaces.
Every A2UI transmission consists of an array containing one or more declarative payloads belonging to three discrete schemas:
beginRendering{
"beginRendering": {
"surfaceId": "default",
"root": "main-column"
}
}
surfaceUpdate{
"surfaceUpdate": {
"surfaceId": "default",
"components": [
{
"id": "main-column",
"component": {
"Column": {
"children": {
"explicitList": ["header-title", "service-card"]
}
}
}
},
{
"id": "header-title",
"component": {
"Text": {
"text": {"literalString": "Production Workloads"},
"usageHint": "h1"
}
}
},
{
"id": "service-card",
"component": {
"Card": {
"child": "service-label"
}
}
},
{
"id": "service-label",
"component": {
"Text": {
"text": {"path": "service_name"}
}
}
}
]
}
}
dataModelUpdate{"path": "key_name"}). The agent can publish state updates independently of structural updates:{
"dataModelUpdate": {
"surfaceId": "default",
"contents": [
{"key": "service_name", "valueString": "auth-service"},
{"key": "system_status", "valueString": "healthy"}
]
}
}
The A2UI design system enforces layout consistency across heterogeneous renderers by limiting generative assembly to 18 fundamental primitives across four functional categories:
| Category | Primitives | Functional Description & Enterprise Use Case |
|---|---|---|
| Layout | Card, Column, Row, List, Tabs, Divider, Modal |
Establishes spatial hierarchy, flexible grids, segmented tab views, and modal overlays. Used for multi-service dashboards, inspection drawers, and split-screen telemetry comparisons. |
| Display | Text, Image, Icon, Video, AudioPlayer |
Renders typography with semantic usage hints (h1, body, caption), contextual state icons, network architecture schematics, and media telemetry. |
| Input | TextField, DateTimeInput, MultipleChoice, CheckBox, Slider |
Captures end-user parameters directly within agent chat streams. Used for provisioning filters, replica scaling parameters, and deployment time-windows. |
| Action | Button |
Exposes event dispatchers back to the agent backend (e.g., executing rollbacks, restarting pods, or drilling into logs). |
Executing this workflow requires an active Google Cloud Platform (GCP) project with Google Cloud Shell or a local Linux environment provisioned with Python 3.12+. The primary cloud integration relies on the Vertex AI platform.
Within the shell terminal, export the necessary context variables to instruct the Agent Development Kit to authenticate through Google Cloud Vertex AI:
# Set active project and regional configuration
gcloud config set project test20260902001
export GOOGLE_CLOUD_PROJECT=test20260902001
export GOOGLE_CLOUD_LOCATION=global
export GOOGLE_GENAI_USE_VERTEXAI=True
Enable the Vertex AI (AI Platform) API within your project. This grants the ADK access to high-performance foundation models such as gemini-3-flash-preview:
gcloud services enable aiplatform.googleapis.com --project test20260902001
Install the Agent Development Kit (google-adk) along with the A2UI Agent SDK (a2ui-agent-sdk). Update the local path variable to ensure installed executable binaries are globally accessible:
pip install -U google-adk a2ui-agent-sdk
export PATH="$HOME/.local/bin:$PATH"
Establish a dedicated workspace directory and transition into it:
mkdir -p ~/test20260902001/a2ui_agent
cd ~/test20260902001
To evaluate the paradigm shift introduced by A2UI, you first construct a traditional agent architecture. Traditional conversational agents return operational status as raw, unstructured text strings.
Create the resource telemetry provider at a2ui_agent/resources.py. This script simulates an enterprise monitoring API capturing real-time infrastructure state across Cloud Run serverless containers and Cloud SQL relational instances:
# a2ui_agent/resources.py
RESOURCES = [
{
"name": "auth-service",
"type": "Cloud Run",
"region": "us-west1",
"status": "healthy",
"cpu": "2 vCPU",
"memory": "1 GiB",
"instances": 3,
"url": "https://auth-service-abc123.run.app",
"last_deployed": "2026-04-18T14:22:00Z",
},
{
"name": "events-db",
"type": "Cloud SQL",
"region": "us-east1",
"status": "warning",
"tier": "db-custom-8-32768",
"storage": "500 GB SSD",
"connections": 195,
"version": "PostgreSQL 16",
"issue": "Storage usage at 92%",
},
{
"name": "analytics-pipeline",
"type": "Cloud Run",
"region": "us-west1",
"status": "error",
"cpu": "2 vCPU",
"memory": "4 GiB",
"instances": 0,
"url": "https://analytics-pipeline-ghi789.run.app",
"last_deployed": "2026-04-10T16:45:00Z",
"issue": "CrashLoopBackOff: OOM killed",
},
]
def get_resources() -> list[dict]:
"""Get all cloud resources in the current project.
Returns a list of cloud infrastructure resources including their
name, type, region, status, and type-specific details.
Status is one of: healthy, warning, error. Resources with
warning or error status include an 'issue' field describing
the problem.
"""
return RESOURCES
Create a2ui_agent/agent.py to bind the resource tool to an ADK Root Agent powered by the gemini-3-flash-preview model:
# a2ui_agent/agent.py
from google.adk.agents import Agent
from .resources import get_resources
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that reports on project resources.",
instruction=(
"You are a cloud infrastructure assistant. When users ask about their "
"cloud resources, use the get_resources tool to fetch the current state. "
"Summarize the results clearly in plain text."
),
tools=[get_resources],
)
Launch the ADK Dev server from the workspace root:
adk web --port 8080 --allow_origins "*" --reload_agents
Open the web interface via Cloud Shell Web Preview (Port 8080). Select a2ui_agent and submit evaluation prompts:
What's running in my project?
Does anything need my attention?
The response generates dense paragraphs. While factually accurate, human triage is impeded: error states are buried within prose, URLs require manual copying, and status attributes lack visual categorization.
To replace prose with declarative interfaces, the LLM must be educated on the A2UI JSON schema without requiring manual prompt engineering of all 18 primitives. The A2uiSchemaManager automates this compilation.
Update a2ui_agent/agent.py to dynamically construct system prompts that bind schema definitions, catalog constraints, and few-shot formatting rules:
# a2ui_agent/agent.py
from google.adk.agents import Agent
from a2ui.schema.manager import A2uiSchemaManager
from a2ui.basic_catalog.provider import BasicCatalog
from .resources import get_resources
# Initialize schema manager targeting specification version 0.8
schema_manager = A2uiSchemaManager(
version="0.8",
catalogs=[BasicCatalog.get_config("0.8")],
)
# Compile dynamic instructions embedding role context and declarative constraints
instruction = schema_manager.generate_system_prompt(
role_description=(
"You are a cloud infrastructure assistant. When users ask about "
"their cloud resources, use the get_resources tool to fetch the "
"current state."
),
workflow_description=(
"Analyze the user's request and return structured UI when appropriate."
),
ui_description=(
"Use cards for resource summaries, rows and columns for comparisons, "
"icons for status indicators, and buttons for drill-down actions. "
"Do NOT use markdown formatting in text values. Use the usageHint "
"property for heading levels instead. "
"Respond ONLY with the A2UI JSON array. Do NOT include any text "
"outside the JSON. Put all explanations into Text components."
),
include_schema=True,
include_examples=True,
)
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that renders rich A2UI interfaces.",
instruction=instruction,
tools=[get_resources],
)
With hot-reloading active (--reload_agents), return to the ADK Dev UI, initiate a +New Session, and re-submit:
What's running in my project?
The output transforms into a validated JSON array containing the triad of A2UI messages (beginRendering, surfaceUpdate, and dataModelUpdate). While the structural blueprint is complete, the ADK browser client defaults to rendering raw JSON text because it has not yet received the MIME-typed metadata required to invoke its native visual renderer.
To render A2UI payloads as interactive frontend components, output emitted by the model must be intercepted, cleansed of formatting fences, and wrapped inside an A2A (Agent-to-Agent / Agent-to-App) application/json+a2ui DataPart envelope.
Create a2ui_agent/a2ui_utils.py. This module defines an ADK after_model_callback pipeline that inspects model responses, extracts JSON constructs, handles malformed concatenations, and wraps elements into binary DataPart blobs:
# a2ui_agent/a2ui_utils.py
import json
import re
from google.genai import types
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_response import LlmResponse
def _wrap_a2ui_part(a2ui_message: dict) -> types.Part:
"""Encapsulates an A2UI message inside an ADK-compliant binary datapart."""
datapart_json = json.dumps({
"kind": "data",
"metadata": {"mimeType": "application/json+a2ui"},
"data": a2ui_message,
})
blob_data = (
b"<a2a_datapart_json>"
+ datapart_json.encode("utf-8")
+ b"</a2a_datapart_json>"
)
return types.Part(
inline_data=types.Blob(
data=blob_data,
mime_type="text/plain",
)
)
def a2ui_callback(
callback_context: CallbackContext,
llm_response: LlmResponse,
) -> LlmResponse | None:
"""Transforms raw textual A2UI JSON into structured, rendered components."""
if not llm_response.content or not llm_response.content.parts:
return None
for part in llm_response.content.parts:
if not part.text:
continue
text = part.text.strip()
if not text:
continue
# Verify presence of core A2UI lifecycle keys
if not any(k in text for k in ("beginRendering", "surfaceUpdate", "dataModelUpdate")):
continue
# Strip markdown syntax fences if accidentally produced by LLM
if text.startswith("```"):
text = text.split("\n", 1)[-1]
if text.endswith("```"):
text = text[:-3].strip()
# Locate valid JSON opening delimiters
json_start = None
for i, ch in enumerate(text):
if ch in ("[", "{"):
json_start = i
break
if json_start is None:
continue
json_text = text[json_start:]
# Resilient JSON deserialization with delimiter error recovery
try:
parsed, _ = json.JSONDecoder().raw_decode(json_text)
except json.JSONDecodeError:
try:
fixed = "[" + re.sub(r'\}\s*\{', '},{', json_text) + "]"
parsed, _ = json.JSONDecoder().raw_decode(fixed)
except json.JSONDecodeError:
continue
if not isinstance(parsed, list):
parsed = [parsed]
a2ui_keys = {"beginRendering", "surfaceUpdate", "dataModelUpdate", "deleteSurface"}
a2ui_messages = [msg for msg in parsed if isinstance(msg, dict) and any(k in msg for k in a2ui_keys)]
if not a2ui_messages:
continue
# Convert each A2UI message into an ADK rendering part
new_parts = [_wrap_a2ui_part(msg) for msg in a2ui_messages]
return LlmResponse(
content=types.Content(role="model", parts=new_parts),
custom_metadata={"a2a:response": "true"},
)
return None
Update a2ui_agent/agent.py to attach a2ui_callback to the after_model_callback event hook:
# a2ui_agent/agent.py
from google.adk.agents import Agent
from a2ui.schema.manager import A2uiSchemaManager
from a2ui.basic_catalog.provider import BasicCatalog
from .resources import get_resources
from .a2ui_utils import a2ui_callback
schema_manager = A2uiSchemaManager(
version="0.8",
catalogs=[BasicCatalog.get_config("0.8")],
)
instruction = schema_manager.generate_system_prompt(
role_description=(
"You are a cloud infrastructure assistant. When users ask about "
"their cloud resources, use the get_resources tool to fetch the "
"current state."
),
workflow_description=(
"Analyze the user's request and return structured UI when appropriate."
),
ui_description=(
"Use cards for resource summaries, rows and columns for comparisons, "
"icons for status indicators, and buttons for drill-down actions. "
"Do NOT use markdown formatting in text values. Use the usageHint "
"property for heading levels instead. "
"Respond ONLY with the A2UI JSON array. Do NOT include any text "
"outside the JSON. Put all explanations into Text components."
),
include_schema=True,
include_examples=True,
)
root_agent = Agent(
model="gemini-3-flash-preview",
name="cloud_dashboard",
description="A cloud infrastructure assistant that renders rich A2UI interfaces.",
instruction=instruction,
tools=[get_resources],
after_model_callback=a2ui_callback,
)
With the callback pipeline operational, relaunch or refresh the ADK Web interface to observe dynamic interface generation across distinct user prompts.
User Query:
What's running in my project?
Interface Result:
The agent synthesizes a multi-column card layout. Each cloud workload (auth-service, events-db, analytics-pipeline) is presented as an encapsulated Card primitive containing:
usageHint: "h2".Icon representations mapping healthy, warning, or error conditions.Button components linking directly to service endpoint URLs.User Query:
Does anything need my attention?
Interface Result:
The agent filters out healthy workloads and alters layout composition. Instead of a general catalog, it displays an alert banner followed by priority containers focusing on:
events-db highlighting 92% storage capacity limits.analytics-pipeline highlighting OOM termination status.User Query:
I need to deploy a new service
Interface Result:
Without requiring changes to backend Python logic, the agent composes a provisioning form using input primitives (TextField for service name, MultipleChoice for region selection, Slider for concurrency limits, and a primary submit Button). This demonstrates the power of the 18 primitives: UI layouts adapt dynamically to user intent.
When operating ADK and A2UI services inside cloud shell environments, several common system errors may occur:
address already in use (Port 8080 Conflict)Occurs when an orphaned background process retains the bind port:
fuser -k 8080/tcp || kill -9 $(lsof -t -i:8080)
ValueError: No API key was providedOccurs when SDK client libraries fall back to Google AI Studio authentication instead of Vertex AI:
# Explicitly enforce Vertex AI routing
export GOOGLE_GENAI_USE_VERTEXAI=True
export GOOGLE_CLOUD_PROJECT=test20260902001
export GOOGLE_CLOUD_LOCATION=global
# Refresh Application Default Credentials
gcloud auth application-default login --no-launch-browser
Text primitives. Applying markdown (e.g., # Header, **bold**) results in unparsed markdown literals. Always instruct the model to use the usageHint attribute (h1, h2, body, caption) for typographic hierarchy.explicitList match exact unique IDs defined within the components array.While adk web provides a rapid local environment for development and prototyping, production deployment involves rendering A2UI streams within enterprise frontend frameworks.
The agent architecture remains completely unchanged; production clients consume the same A2UI JSON payloads using official client packages:
+-----------------------------------------------------------------------------------+
| Target Production Ecosystem |
| |
| +--------------------+ +--------------------+ +--------------------+ +------+ |
| | React (Web) | | Lit (Web) | | Angular (Web) | |Flutter| |
| | @a2ui/react | | @a2ui/lit | | @a2ui/angular | |GenUI | |
| +--------------------+ +--------------------+ +--------------------+ +------+ |
+-----------------------------------------------------------------------------------+
npm install @a2ui/react
Provides native React functional components that deserialize surfaceUpdate graphs directly into React DOM trees.
npm install @a2ui/lit
Delivers lightweight, framework-agnostic custom elements for embedding within existing micro-frontends.
npm install @a2ui/angular
Offers declarative Angular modules with native reactive form bindings for input primitives.
To maintain infrastructure hygiene and eliminate resource costs following codelab completion, execute cleanup routines:
Terminate the ADK web development server by sending a SIGINT signal (Ctrl + C) within the running Cloud Shell terminal session.
If the Google Cloud project was created solely for this codelab, delete the project and all associated Vertex AI metadata:
gcloud projects delete test20260902001 --quiet
Declarative agent interfaces represent an evolution in human-agent collaboration. By decoupling backend agent reasoning from frontend visual rendering, engineering organizations unlock key architectural advantages: