一键导入
a2ui-developer
Expert guide and patterns for building Agent-Driven User Interfaces (A2UI) using the ADK.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Expert guide and patterns for building Agent-Driven User Interfaces (A2UI) using the ADK.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | A2UI Developer |
| description | Expert guide and patterns for building Agent-Driven User Interfaces (A2UI) using the ADK. |
This skill equips you with the knowledge and best practices to design, implement, and debug A2UI-compliant agents and clients.
CRITICAL: This skill relies on local reference documentation that must be kept in sync with the official repository.
When starting a new session or tasks involving A2UI:
python3 scripts/update_skill.py
A2UI decouples UI generation (Agent) from UI rendering (Client).
---a2ui_JSON---)surfaceUpdate and dataModelUpdate.surfaceUpdate (Structure)Defines the component hierarchy using a flat Adjacency List.
surfaceId: Target surface (e.g., "main").components: Array of component definitions.{
"surfaceUpdate": {
"surfaceId": "main",
"components": [
{
"id": "root_col",
"component": {
"Column": {
"children": { "explicitList": ["header_1", "card_1"] }
}
}
}
]
}
}
dataModelUpdate (Data)Populates data for components.
{
"dataModelUpdate": {
"surfaceId": "main",
"contents": [
{ "key": "user_name", "valueString": "Alice" }
]
}
}
beginRendering (Signal)Tells the client to start drawing a specific root component on a surface.
{
"beginRendering": {
"surfaceId": "main",
"root": "root_col"
}
}
CRITICAL IF UNKNOWN: Before writing any integration code, you must ask the user:
"Are you deploying this A2UI agent to Cloud Run (Raw HTTP/A2A Mode) or natively to Vertex AI Agent Engine (Native Tools Mode)?"
The implementation architecture differs significantly between the two (see Section 4).
An A2UI agent MUST inherently be an A2A (Agent-to-Agent) agent first. The A2A protocol serves as the base data transfer and transport layer for A2UI structured payloads. Therefore, before adding A2UI generation or parsing, you must ensure the agent successfully implements the A2A specification.
Agents must be explicitly instructed to generate A2UI JSON.
Critical Rules:
---a2ui_JSON--- to separate text from JSON.Sample Instruction:
You are an agent that generates UIs. You MUST separate your conversational response from your A2UI JSON output using the delimiter
---a2ui_JSON---. The JSON must appear EXACTLY once at the end.
Requires a custom AgentExecutor to intercept tool responses or stream outputs and yield a native DataPart with mimeType="application/json+a2ui" directly to the HTTP stream.
import json
from a2a import types
from a2a.server import agent_execution
from a2a.server import events
from google.adk import runners
class AdkAgentToA2AExecutor(agent_execution.AgentExecutor):
def __init__(self):
self._agent = local_agent.root_agent # Your ADK LlmAgent
self._runner = runners.Runner(...)
async def execute(self, context: agent_execution.RequestContext, event_queue: events.EventQueue) -> None:
query = context.get_user_input()
# 1. Run the agent stream
final_response_content = ""
async for event in self._runner.run_async(...):
if event.is_final_response():
final_response_content += event.content.parts[0].text
# 2. Extract A2UI JSON and convert to DataPart
text_part, json_string = final_response_content.split("---a2ui_JSON---", 1)
# ... parse json_string ...
parts = []
parts.append(types.Part(root=types.TextPart(text=text_part)))
parts.append(types.Part(root=types.DataPart(
data=json.loads(json_string),
metadata={"mimeType": "application/json+a2ui"}
)))
# 3. Yield to stream
await updater.add_artifact(parts, name="response")
await updater.complete()
from starlette.applications import Starlette
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
agent_executor = DefaultRequestHandler() # Or appropriate executor
request_handler = DefaultRequestHandler(agent_executor=agent_executor)
app = Starlette()
a2a_app = A2AStarletteApplication(agent_card=agent_card, http_handler=request_handler)
a2a_app.add_routes_to_app(app, rpc_url="/a2a/my_agent", agent_card_url="/a2a/my_agent/.well-known/agent-card.json")
In this mode, standard tools return text by default. The Gemini Model reads that text and decides how to present it. Native binary application/json+a2ui injection inside standard tools can cause platform-level security blocks (or get stringified for the model).
For Agent Engine, you must use the text-based delimiter fallbacks (---JSON_DATA---) so the model can render them in plain text for readability (often as ASCII-art charts) when interactive cards are not supported natively by the runtime layer.
| File | Why it's Needed | Required for Text-Based Delimiter? | Required for Tool-Based Validation? |
|---|---|---|---|
app.py | Entry point for WSGI/ASGI compute (AdkApp). | YES (Can be manually created or generated dynamically via Terraform). | YES |
a2ui_tools.py | Defines Python function render_ui for LLM validation. | NO | YES |
a2ui_schema.json | Validates LLM tool arguments using jsonschema. | NO | YES |
agent_executor.py | Custom stream interceptor (Translates text blocks context to binary DataParts). | NO (Agent Engine handles native parsing without it). | NO |
[!TIP] Simplicity First: If you use standard text delimiters (
---JSON_DATA---), you do not needa2ui_tools.pyora2ui_schema.jsonin your file archive. The platform intercepts text automatically!
For A2UI agents deployed on Agent Engine using the Python SDK, you MUST include the a2ui-agent SDK from the git repository in your requirements.txt. Removing it will break A2A communication and cause 400 Bad Request errors.
a2ui-agent @ git+https://github.com/google/A2UI.git#subdirectory=agent_sdks/python
[!IMPORTANT] Do NOT remove unpublished dependencies from git repos if they are required by the system architecture.
When implementing a custom AgentExecutor (like AdkAgentToA2AExecutor), ensure it translates the LLM's output stream into A2UI DataParts rather than replacing it with static mock data.
DataPart inside the loop for testing.MOCK_A2UI_RESPONSE=True) to toggle intercepts, and ensure the default flow always accumulates the real run_async stream from the LLM.executor.execute loop. Wrap in try/except.run_async streaming splits the JSON string. Must accumulate before parsing.---a2ui_JSON--- delimiter is missing or if strict markdown blocks were used.npm install @a2ui/lit @a2a-js/sdk<a2ui-surface>.Column, Row, Divider, Modal, TabsText, Image, Video, AudioPlayer, CardButton, TextField, CheckBox, Slider---a2ui_JSON---.adk api_server . --a2a --allow_origins "*".agent.json (not agent-card.json) exists and has "capabilities": { "streaming": true }.A2AClient with JsonRpcTransportFactory and inject A2UI extension headers.For complex UIs or when you need strict validation before sending data to the client, use the Tool-Based Pattern instead of the streaming delimiter.
Mechanism:
send_a2ui_json_to_client) that accepts the A2UI JSON as an argument.DataPart for the client.Benefits:
DataPart event, not a raw text stream it has to parse.Implementation Example (Python ADK):
from a2ui.send_a2ui_to_client_toolset import SendA2uiToClientToolset
# In Agent Initialization
root_agent = LlmAgent(
# ...
tools=[
SendA2uiToClientToolset(
a2ui_enabled=True,
a2ui_schema=A2UI_SCHEMA
)
]
)
For consistent UIs, inject "Template Examples" into the system prompt.
loading.json, error.json, dashboard.json).This reduces hallucination and ensures visual consistency.
The A2UI Composer (https://a2ui-composer.ag-ui.com/) is a visual rapid prototyping tool.
examples in basic tool definitions.CopilotKit provides native support for A2UI as a "Declarative Generative UI".
<CopilotKit /> provider detects the A2UI spec and invokes the built-in renderer.[!WARNING] While the platform documentation may describe "native text interception" using delimiters like
---JSON_DATA---for standard Agent Engine deployments, in practice this has been found to be unreliable and is unsupported for rich A2UI rendering in this repository.
For reliable A2UI rendering (yielding binary DataPart objects), you MUST use the Custom Executor approach deployed via the Python SDK (Pickle-based).
Refer to the Agent Engine Python Deployer skill for instructions on how to package your agent with a custom agent_executor.py.
doctor_card_1, doctor_card_2).When deploying to environments like Gemini Enterprise, the frontend strictly enforces the official A2UI v0.8 Schema. If any component is structurally invalid, it actively blocks rendering (via TrustedHTML CSP policies) to protect the UI, resulting in blank responses despite correct network payloads.
Common Schema Pitfalls to Avoid:
MultipleChoice (Nesting Strictness): Use the options array containing objects with label and value.
label MUST be an object with a literalString property (e.g., {"label": {"literalString": "Option A"}}).value MUST be a plain string (e.g., {"value": "option_a"}).choices property. Do NOT over-nest value (making it an object like {"value": {"literalString": "option_a"}}).Button: The text on a button must be provided via the child property (which refers to a separate Text component id). Do NOT use a text property directly on the Button.Card: A Card only accepts a single child component (typically a Column or Row used to stack multiple elements). Do NOT use title or subtitle properties directly on the Card.Column and Row (Nesting Strictness): The explicitList of child IDs MUST be wrapped in a children object.
"Column": { "children": { "explicitList": ["id1", "id2"] } }"Column": { "explicitList": ["id1", "id2"] } (Missing children wrapper!)TextField Hallucinations (TextInput): A highly common LLM hallucination is generating an Unknown element TextInput. The official v0.8 A2UI schema component for free-form text entry is strictly named TextField. You MUST instruct your agent to use TextField and explicitly forbid TextInput. The TextField properties text and label must be objects (e.g. {"text": {"path": "my_txt"}, "label": {"literalString": "Reason"}}), NOT plain strings, and the property is text, not content.CheckBox Hallucinations (selected): LLMs frequently hallucinate selected or checked properties for the CheckBox component. The official schema strictly requires value (e.g. {"value": {"path": "is_checked"}}).dataModelUpdate Hallucinations (valueBool): When populating booleans in dataModelUpdate, LLMs may hallucinate valueBool. The schema strictly requires valueBoolean."a2ui_messages" key.
{ "a2ui_messages": [ ... ] }[ ... ] (Returning a raw array fails parsing!).{} MUST be escaped as {{}}. Failure to do so triggers SyntaxError: Expression nested too deeply.a2ui_examples.py, a2ui_schema.py), you MUST add them to the extra_packages list in your deployment script. Unlisted files will not be uploaded to the server, causing ModuleNotFoundError at runtime.message literals that explicitly append the target IDs inside the user-visible text string part (e.g. I want to book Dr. Smith (id: p1).). This guarantees that even if the server-side RAM cache flushes, the next replica reads the ID directly from the client-passed transcript body, enabling it to invoke continuity tool checks instantly!A2UI_SCHEMA before debugging the renderer. A missing wrapper (e.g., Text.literalString vs Text.text.literalString) breaks rendering.Card or styled Column to group related inputs. A flat list of components is valid but visually confusing.explicitList, ensure every ID appears exactly once to prevent "ghost" duplicates.Text should often inherit styles (e.g., color) from their containers (Buttons, Headers) rather than having hardcoded default styles.To handle LLM JSON errors gracefully:
A2UI_SCHEMA.Symptom: Server crashes on startup with PydanticSchemaGenerationError or RecursionError, especially involving ClientSession or GenericAlias.
Cause: Incompatibility between Pydantic's schema generator and Python 3.13's type handling in the ADK/FastAPI stack.
Fix: Apply strict monkey-patches to pydantic._internal._generate_schema to fallback to any_schema for unknown types.
Symptom: Client fails to connect to ADK server with Network Error or CORS policy block. Fix:
--allow_origins "*".vite.config.ts to proxy requests:
server: { proxy: { '/a2a': 'http://localhost:8000' } }
Lesson: When verifying A2UI in a browser (e.g., using adk web or Playwright):
Lesson: When migrating tools to A2UI agents:
open('data.json') fails if the file isn't copied to the new agent's folder.open( or read_text during migration.Symptom: 400 REMOTE_AGENT_FAILURE (internal 403 PermissionDenied in initializer.py).
Cause: The SDK reads the project number injected by the runtime and tries to resolve it to a name via Cloud Resource Manager API (which fails if the Service Account lacks resourcemanager.projects.get).
Fix: Initialize the Vertex AI SDK explicitly using aiplatform.init(project=...) using a user-provided PROJECT_ID environment variable (e.g., from Terraform) before any other vertexai imports. This forces the SDK to use the correct project ID and bypasses the project number resolution lookup:
import os
import google.cloud.aiplatform as aiplatform
if "PROJECT_ID" in os.environ:
aiplatform.init(project=os.environ["PROJECT_ID"])
os.environ["GOOGLE_CLOUD_PROJECT"] = os.environ["PROJECT_ID"]
Symptom: Server crashes on startup with RecursionError or compilation drops during cloudpickle unpickling.
Cause: Missing template type alignments in the runtime vertexai._genai suite.
Fix: At the very top of your agent_executor.py (before ADK imports), force alignment of Starlette request types:
import sys
from typing import Any
try:
import vertexai.preview.reasoning_engines.templates.a2a as a2a_module
import starlette.requests
import a2a.server.apps.rest.rest_adapter as adapter_module
if "Request" not in a2a_module.__dict__:
a2a_module.__dict__["Request"] = starlette.requests.Request
if "ServerCallContext" not in a2a_module.__dict__:
a2a_module.__dict__["ServerCallContext"] = adapter_module.ServerCallContext
except ImportError:
pass
The workspace contains a comprehensive library of A2UI samples. Always prefer reading these verified implementations over generating code from scratch.
Root Path: ./examples (Relative to this SKILL.md)
| Type | Name | Path | Key Patterns |
|---|---|---|---|
| Agent | RizzCharts | examples/agent/rizzcharts | Tool-Based Pattern, Dynamic Charts/Maps, Schema Wrapping |
| Agent | Contact Lookup | examples/agent/contact_lookup | Simple Forms, Card Layouts, Static Resources |
| Agent | Reasoning Engine (A2A) | examples/agent/agent_engine_a2a | A2aAgent, agent_executor.py, a2ui_schema.py |
| Client | Generic Shell | examples/client/generic_shell | Hybrid Chat Loop, A2UI Renderer Integration, SSE Parsing |
Instruction: When asked to build a feature (e.g., "add a chart"), first use list_dir on the relevant local example directory to find precedent, then view_file to copy the proven pattern.
This skill contains the entire A2UI framework source for reference.
| Resource | Path (Relative) | Description |
|---|---|---|
| Online Specification | https://a2ui.org/reference/components | Official web component gallery and behavioral reference. |
| Specification | ./specification | Canonical JSON Schemas (a2ui_schema.json) and Protocol specs. |
| Documentation | ./docs | Comprehensive Markdown guides on Architecture, Component Library, and Best Practices. |
| Renderers | ./renderers | Source code for official client renderers (e.g., ./renderers/lit, ./renderers/angular). Use for deep debugging of client issues. |
| Tools | ./tools | Helper scripts for schema validation and scaffolding. |
Usage:
view_file ./specification/components/<Component>.jsonview_file ./renderers/lit/src/components/<Component>.tsUse this a2ui_schema.py module to validate generated LLM payloads against platform validation strictness before streaming:
A2UI_SCHEMA = r"""{
"title": "A2UI Message Schema",
"description": "Describes a JSON payload for an A2UI (Agent to UI) message...",
"type": "object",
"properties": {
"beginRendering": { ... },
"surfaceUpdate": { ... },
"dataModelUpdate": { ... },
"deleteSurface": { ... }
}
}
"""
[!TIP] Refer to the repository root
/usr/local/google/home/veermuchandi/code/agents/rad-workshop/careconnect_navigator_a2ui/a2ui_schema.pyfor the full 300+ line JSON Schema string block ensuring v0.8 compliance.
Specialized role for designing interactive Agent-Driven User Interfaces (A2UI). Understands agent interactions, selects appropriate UI components, and creates UX design documents without generating code.
Comprehensive guide to building, orchestrating, and deploying agents with the Google Agent Development Kit (ADK).
Deploys ADK agents to Vertex AI Agent Engine using the Python SDK and registers them with Gemini Enterprise.
Deploys ADK agents to Vertex AI Agent Engine using the standard Terraform pattern.
Specialized role for registering and managing ADK agents within Gemini Enterprise using cURL.
specialized role for designing high-quality ADK agent architectures without generating code.