Skip to main content

a2ui-adk

Build agents that generate rich, interactive UIs using Google's A2UI (Agent-to-User Interface) format with Google ADK (Agent Development Kit). Use when building ADK agents that need to render dynamic UI components, forms, cards, lists, or interactive elements via the A2UI declarative JSON format. Includes a Tailwind CSS + shadcn/ui design system flavor for creating beautiful, modern A2UI catalogs and components. Covers A2UI schema management, catalog configuration, A2A transport integration, response parsing/validation, custom catalog creation, and the orchestrator pattern for multi-agent UI routing.

소스 정보

저장소
coolxeo/a2ui-adk
최근 소스 활동
2026년 3월 7일 10:18
감지된 SKILL.md 언어
영어
스타
11
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
a2ui-adk
description
Build agents that generate rich, interactive UIs using Google's A2UI (Agent-to-User Interface) format with Google ADK (Agent Development Kit). Use when building ADK agents that need to render dynamic UI components, forms, cards, lists, or interactive elements via the A2UI declarative JSON format. Includes a Tailwind CSS + shadcn/ui design system flavor for creating beautiful, modern A2UI catalogs and components. Covers A2UI schema management, catalog configuration, A2A transport integration, response parsing/validation, custom catalog creation, and the orchestrator pattern for multi-agent UI routing.
# A2UI + ADK Skill Build Google ADK agents that generate rich, interactive UIs using the A2UI declarative JSON format, styled with Tailwind CSS and shadcn/ui design principles. ## What is A2UI? **A2UI (Agent-to-User Interface)** is an open standard (Apache 2.0, github.com/google/A2UI) that lets agents "speak UI." Agents send declarative JSON describing UI intent; client apps render it using native components (Flutter, Angular, Lit, React, etc.). Core principles: - **Security first**: Declarative data format, not executable code. Clients maintain a catalog of trusted, pre-approved UI components. - **LLM-friendly**: Flat list of components with ID references, easy for LLMs to generate incrementally. - **Framework-agnostic**: Same JSON payload renders on any supported client framework. - **Currently v0.8 (Public Preview)**, v0.9 also available. ## Architecture Overview ``` Agent (ADK + A2UI SDK) → A2UI JSON Response → Transport (A2A/AG UI) → Client Renderer (Lit/Flutter/etc.) ``` 1. **Generation**: Agent uses LLM to generate A2UI JSON payload 2. **Transport**: Sent via A2A protocol or AG UI 3. **Resolution**: Client's A2UI Renderer parses the JSON 4. **Rendering**: Maps abstract components to native widgets ## Key Dependencies ```toml [project] dependencies = [ "a2a-sdk>=0.3.0", "google-adk>=1.8.0", "google-genai>=1.27.0", "litellm", "jsonschema>=4.0.0", "a2ui-agent", # The A2UI agent SDK (from agent_sdks/python in the A2UI repo) "python-dotenv>=1.1.0", "click>=8.1.8", ] ``` The `a2ui-agent` package is sourced from the A2UI repo at `agent_sdks/python` (use `uv` workspace source): ```toml [tool.uv.sources] a2ui-agent = { path = "../../../agent_sdks/python", editable = true } ``` ## Core A2UI SDK Imports ```python # Schema management from a2ui.core.schema.constants import VERSION_0_8, VERSION_0_9, A2UI_OPEN_TAG, A2UI_CLOSE_TAG from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.core.schema.common_modifiers import remove_strict_validation # Catalog (built-in component catalog) from a2ui.basic_catalog.provider import BasicCatalog # Response parsing from a2ui.core.parser.parser import parse_response, ResponsePart # A2A integration helpers from a2ui.a2a import ( create_a2ui_part, # Wrap A2UI JSON as A2A DataPart is_a2ui_part, # Check if an A2A Part contains A2UI data get_a2ui_agent_extension,# Create AgentExtension for agent card parse_response_to_parts, # Parse LLM response → list of A2A Parts try_activate_a2ui_extension, # Activate A2UI extension from request context A2UI_EXTENSION_URI, ) ``` ## Pattern 1: Single Agent with A2UI (e.g., Restaurant Finder) ### Step 1: Schema Manager Setup ```python from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.core.schema.constants import VERSION_0_8 from a2ui.basic_catalog.provider import BasicCatalog from a2ui.core.schema.common_modifiers import remove_strict_validation schema_manager = A2uiSchemaManager( VERSION_0_8, catalogs=[ BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples") ], schema_modifiers=[remove_strict_validation], ) ``` - `examples_path` points to a directory of JSON example files used for few-shot prompting - `remove_strict_validation` strips `additionalProperties: false` to make LLM generation easier ### Step 2: Build the ADK LlmAgent ```python from google.adk.agents.llm_agent import LlmAgent from google.adk.models.lite_llm import LiteLlm ROLE_DESCRIPTION = "You are a helpful assistant. Your final output MUST be an A2UI UI JSON response." UI_DESCRIPTION = "Describe when to use which template/layout..." instruction = schema_manager.generate_system_prompt( role_description=ROLE_DESCRIPTION, ui_description=UI_DESCRIPTION, include_schema=True, # Injects the JSON schema into the prompt include_examples=True, # Injects example A2UI payloads validate_examples=True, # Validates examples against schema at startup ) agent = LlmAgent( model=LiteLlm(model="gemini/gemini-2.5-flash"), name="my_agent", description="Agent description", instruction=instruction, tools=[my_tool_function], ) ``` ### Step 3: Agent Class with Streaming + Validation ```python from google.adk.runners import Runner from google.adk.artifacts import InMemoryArtifactService from google.adk.memory.in_memory_memory_service import InMemoryMemoryService from google.adk.sessions import InMemorySessionService from google.genai import types from a2ui.core.parser.parser import parse_response from a2ui.a2a import parse_response_to_parts class MyAgent: def __init__(self, base_url: str, use_ui: bool = False): self.use_ui = use_ui self._schema_manager = A2uiSchemaManager(...) if use_ui else None self._agent = self._build_agent(use_ui) self._runner = Runner( app_name=self._agent.name, agent=self._agent, artifact_service=InMemoryArtifactService(), session_service=InMemorySessionService(), memory_service=InMemoryMemoryService(), ) async def stream(self, query, session_id): # Create/get session session = await self._runner.session_service.get_session(...) if session is None: session = await self._runner.session_service.create_session(...) # Run agent with retry on validation failure max_retries = 1 for attempt in range(max_retries + 1): message = types.Content(role="user", parts=[types.Part.from_text(text=query)]) final_response = None async for event in self._runner.run_async( user_id=self._user_id, session_id=session.id, new_message=message ): if event.is_final_response(): final_response = "\n".join( [p.text for p in event.content.parts if p.text] ) break else: yield {"is_task_complete": False, "updates": "Processing..."} # Validate A2UI JSON if using UI if self.use_ui and final_response: try: response_parts = parse_response(final_response) for part in response_parts: if part.a2ui_json: selected_catalog = self._schema_manager.get_selected_catalog() selected_catalog.validator.validate(part.a2ui_json) # Valid! Send final response yield { "is_task_complete": True, "parts": parse_response_to_parts(final_response, fallback_text="OK."), } return except Exception as e: if attempt < max_retries: query = f"Previous response invalid: {e}. Retry with valid A2UI JSON." continue # Exhausted retries, send error yield {"is_task_complete": True, "parts": [Part(root=TextPart(text="Error generating UI."))]} return else: yield {"is_task_complete": True, "parts": parse_response_to_parts(final_response)} return ``` ### Step 4: Agent Executor (A2A Server) The executor bridges A2A protocol to your agent. Key pattern: maintain both a UI agent and a text-only agent, selecting based on whether the A2UI extension is active. ```python from a2a.server.agent_execution import AgentExecutor, RequestContext from a2a.server.events import EventQueue from a2a.server.tasks import TaskUpdater from a2a.types import DataPart, Part, TaskState, TextPart from a2a.utils import new_agent_parts_message, new_agent_text_message, new_task from a2ui.a2a import try_activate_a2ui_extension class MyAgentExecutor(AgentExecutor): def __init__(self, ui_agent, text_agent): self.ui_agent = ui_agent self.text_agent = text_agent async def execute(self, context: RequestContext, event_queue: EventQueue): use_ui = try_activate_a2ui_extension(context) agent = self.ui_agent if use_ui else self.text_agent # Extract query from message parts query = context.get_user_input() # Handle A2UI user actions (button clicks, form submissions) for part in (context.message.parts or []): if isinstance(part.root, DataPart) and "userAction" in part.root.data: ui_event = part.root.data["userAction"] action = ui_event.get("actionName") ctx = ui_event.get("context", {}) query = f"User action: {action} with data: {ctx}" break task = context.current_task or new_task(context.message) if not context.current_task: await event_queue.enqueue_event(task) updater = TaskUpdater(event_queue, task.id, task.context_id) async for item in agent.stream(query, task.context_id): if not item["is_task_complete"]: await updater.update_status( TaskState.working, new_agent_text_message(item["updates"], task.context_id, task.id), ) else: await updater.update_status( TaskState.input_required, # or TaskState.completed new_agent_parts_message(item["parts"], task.context_id, task.id), final=True, ) break ``` ### Step 5: Server Entry Point (__main__.py) ```python import click, os, logging, uvicorn from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore from dotenv import load_dotenv from starlette.middleware.cors import CORSMiddleware load_dotenv() @click.command() @click.option("--host", default="localhost") @click.option("--port", default=10002) def main(host, port): base_url = f"http://{host}:{port}" ui_agent = MyAgent(base_url=base_url, use_ui=True) text_agent = MyAgent(base_url=base_url, use_ui=False) executor = MyAgentExecutor(ui_agent, text_agent) handler = DefaultRequestHandler(agent_executor=executor, task_store=InMemoryTaskStore()) server = A2AStarletteApplication(agent_card=ui_agent.get_agent_card(), http_handler=handler) app = server.build() app.add_middleware(CORSMiddleware, allow_origin_regex=r"http://localhost:\d+", allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) uvicorn.run(app, host=host, port=port) if __name__ == "__main__": main() ``` ### Step 6: Agent Card with A2UI Extension ```python from a2a.types import AgentCapabilities, AgentCard, AgentSkill from a2ui.a2a import get_a2ui_agent_extension def get_agent_card(self) -> AgentCard: return AgentCard( name="My Agent", description="Agent description", url=self.base_url, version="1.0.0", default_input_modes=["text", "text/plain"], default_output_modes=["text", "text/plain"], capabilities=AgentCapabilities( streaming=True, extensions=[ get_a2ui_agent_extension( self._schema_manager.accepts_inline_catalogs, self._schema_manager.supported_catalog_ids, ) ], ), skills=[AgentSkill(id="my_skill", name="My Skill", description="...", tags=["tag"], examples=["example query"])], ) ``` ## Pattern 2: Orchestrator with Multi-Agent UI Routing For orchestrating multiple sub-agents that each produce A2UI, use: ### Remote Sub-Agent Discovery ```python from a2a.client import A2ACardResolver from google.adk.agents.remote_a2a_agent import RemoteA2aAgent from google.adk.agents.llm_agent import LlmAgent from google.adk.planners.built_in_planner import BuiltInPlanner # Discover sub-agents via A2A card resolution async with httpx.AsyncClient() as client: resolver = A2ACardResolver(httpx_client=client, base_url=subagent_url) subagent_card = await resolver.get_agent_card() # Create remote agent wrapper remote_agent = RemoteA2aAgent( name, subagent_card, description=json.dumps({...}), a2a_part_converter=convert_a2a_part_to_genai_part, genai_part_converter=convert_genai_part_to_a2a_part, a2a_client_factory=A2AClientFactoryWithA2UIMetadata(...) ) # Build orchestrator orchestrator = LlmAgent( model=LiteLlm(model="gemini/gemini-2.5-flash"), name="orchestrator_agent", instruction="Route tasks to the appropriate subagent.", sub_agents=[remote_agent_1, remote_agent_2], planner=BuiltInPlanner(thinking_config=genai_types.ThinkingConfig(include_thoughts=True)), before_model_callback=programmatic_route_user_action, # Route UI actions to correct sub-agent ) ``` ### Part Converters (A2A <-> GenAI) When A2UI parts pass through the orchestrator, they need conversion between A2A and GenAI formats: ```python from a2ui.a2a import is_a2ui_part def convert_a2a_part_to_genai_part(a2a_part): """Serialize A2UI DataParts to text for LLM context.""" if is_a2ui_part(a2a_part): return genai_types.Part(text=a2a_part.model_dump_json()) return part_converter.convert_a2a_part_to_genai_part(a2a_part) def convert_genai_part_to_a2a_part(part): """Deserialize A2UI text back to DataParts.""" if part.text: try: a2a_part = a2a_types.Part.model_validate_json(part.text) if is_a2ui_part(a2a_part): return a2a_part except pydantic.ValidationError: pass return part_converter.convert_genai_part_to_a2a_part(part) ``` ### Surface-to-Subagent Routing Route user actions to the correct sub-agent based on `surfaceId`:
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기