From 9e911d459cb8a5b27392e23b83282b5694b782f6 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 11 Feb 2026 21:58:14 +0000 Subject: [PATCH] Update the rizzcharts sample It updates the sample to use the A2uiSchemaManager from the a2ui-agent python SDK. Tested: - [x] The `rizzcharts` Angular client successfully connected to the `rizzcharts` agent and rendered the response correctly. --- .../extension/send_a2ui_to_client_toolset.py | 86 +++--- .../test_send_a2ui_to_client_toolset.py | 118 +++++--- renderers/lit/package-lock.json | 5 +- renderers/web_core/package-lock.json | 4 +- samples/agent/adk/rizzcharts/__main__.py | 48 ++-- samples/agent/adk/rizzcharts/agent.py | 261 ++++++++---------- .../agent/adk/rizzcharts/agent_executor.py | 119 +++----- .../rizzcharts/component_catalog_builder.py | 103 ------- .../rizzcharts_catalog_definition.json | 3 +- 9 files changed, 308 insertions(+), 439 deletions(-) delete mode 100644 samples/agent/adk/rizzcharts/component_catalog_builder.py diff --git a/a2a_agents/python/a2ui_agent/src/a2ui/extension/send_a2ui_to_client_toolset.py b/a2a_agents/python/a2ui_agent/src/a2ui/extension/send_a2ui_to_client_toolset.py index 1101869cf..4e41cb090 100644 --- a/a2a_agents/python/a2ui_agent/src/a2ui/extension/send_a2ui_to_client_toolset.py +++ b/a2a_agents/python/a2ui_agent/src/a2ui/extension/send_a2ui_to_client_toolset.py @@ -39,16 +39,16 @@ ```python # Simple boolean and dict - toolset = SendA2uiToClientToolset(a2ui_enabled=True, a2ui_schema=MY_SCHEMA) + toolset = SendA2uiToClientToolset(a2ui_enabled=True, a2ui_catalog=MY_CATALOG) # Async providers async def check_enabled(ctx: ReadonlyContext) -> bool: return await some_condition(ctx) - async def get_schema(ctx: ReadonlyContext) -> dict[str, Any]: - return await fetch_schema(ctx) + async def get_catalog(ctx: ReadonlyContext) -> A2uiCatalog: + return await fetch_catalog(ctx) - toolset = SendA2uiToClientToolset(a2ui_enabled=check_enabled, a2ui_schema=get_schema) + toolset = SendA2uiToClientToolset(a2ui_enabled=check_enabled, a2ui_catalog=get_catalog) ``` 2. Integration with Agent: @@ -60,7 +60,7 @@ async def get_schema(ctx: ReadonlyContext) -> dict[str, Any]: tools=[ SendA2uiToClientToolset( a2ui_enabled=True, - a2ui_schema=MY_SCHEMA + a2ui_catalog=MY_CATALOG ) ] ) @@ -86,7 +86,7 @@ async def get_schema(ctx: ReadonlyContext) -> dict[str, Any]: from a2a import types as a2a_types from a2ui.extension.a2ui_extension import create_a2ui_part -from a2ui.extension.a2ui_schema_utils import wrap_as_json_array +from a2ui.inference.schema.catalog import A2uiCatalog from google.adk.a2a.converters import part_converter from google.adk.agents.readonly_context import ReadonlyContext from google.adk.models import LlmRequest @@ -101,8 +101,11 @@ async def get_schema(ctx: ReadonlyContext) -> dict[str, Any]: A2uiEnabledProvider: TypeAlias = Callable[ [ReadonlyContext], Union[bool, Awaitable[bool]] ] -A2uiSchemaProvider: TypeAlias = Callable[ - [ReadonlyContext], Union[dict[str, Any], Awaitable[dict[str, Any]]] +A2uiCatalogProvider: TypeAlias = Callable[ + [ReadonlyContext], Union[A2uiCatalog, Awaitable[A2uiCatalog]] +] +A2uiExamplesProvider: TypeAlias = Callable[ + [ReadonlyContext], Union[str, Awaitable[str]] ] @@ -113,11 +116,12 @@ class SendA2uiToClientToolset(base_toolset.BaseToolset): def __init__( self, a2ui_enabled: Union[bool, A2uiEnabledProvider], - a2ui_schema: Union[dict[str, Any], A2uiSchemaProvider], + a2ui_catalog: Union[A2uiCatalog, A2uiCatalogProvider], + a2ui_examples: Union[str, A2uiExamplesProvider], ): super().__init__() self._a2ui_enabled = a2ui_enabled - self._ui_tools = [self._SendA2uiJsonToClientTool(a2ui_schema)] + self._ui_tools = [self._SendA2uiJsonToClientTool(a2ui_catalog, a2ui_examples)] async def _resolve_a2ui_enabled(self, ctx: ReadonlyContext) -> bool: """The resolved self.a2ui_enabled field to construct instruction for this agent. @@ -164,8 +168,13 @@ class _SendA2uiJsonToClientTool(BaseTool): A2UI_JSON_ARG_NAME = "a2ui_json" TOOL_ERROR_KEY = "error" - def __init__(self, a2ui_schema: Union[dict[str, Any], A2uiSchemaProvider]): - self._a2ui_schema = a2ui_schema + def __init__( + self, + a2ui_catalog: Union[A2uiCatalog, A2uiCatalogProvider], + a2ui_examples: Union[str, A2uiExamplesProvider], + ): + self._a2ui_catalog = a2ui_catalog + self._a2ui_examples = a2ui_examples super().__init__( name=self.TOOL_NAME, description=( @@ -195,34 +204,39 @@ def _get_declaration(self) -> genai_types.FunctionDeclaration | None: ), ) - async def _resolve_a2ui_schema(self, ctx: ReadonlyContext) -> dict[str, Any]: - """The resolved self.a2ui_schema field to construct instruction for this agent. + async def _resolve_a2ui_examples(self, ctx: ReadonlyContext) -> str: + """The resolved self.a2ui_examples field to construct instruction for this agent. Args: ctx: The ReadonlyContext to resolve the provider with. Returns: - The A2UI schema to send to the client. + The A2UI examples string. """ - if isinstance(self._a2ui_schema, dict): - return self._a2ui_schema + if isinstance(self._a2ui_examples, str): + return self._a2ui_examples else: - a2ui_schema = self._a2ui_schema(ctx) - if inspect.isawaitable(a2ui_schema): - a2ui_schema = await a2ui_schema - return a2ui_schema + a2ui_examples = self._a2ui_examples(ctx) + if inspect.isawaitable(a2ui_examples): + a2ui_examples = await a2ui_examples + return a2ui_examples - async def get_a2ui_schema(self, ctx: ReadonlyContext) -> dict[str, Any]: - """Retrieves and wraps the A2UI schema. + async def _resolve_a2ui_catalog(self, ctx: ReadonlyContext) -> A2uiCatalog: + """The resolved self.a2ui_catalog field to construct instruction for this agent. Args: - ctx: The ReadonlyContext for resolving the schema. + ctx: The ReadonlyContext to resolve the provider with. Returns: - The wrapped A2UI schema. + The A2UI catalog object. """ - a2ui_schema = await self._resolve_a2ui_schema(ctx) - return wrap_as_json_array(a2ui_schema) + if isinstance(self._a2ui_catalog, A2uiCatalog): + return self._a2ui_catalog + else: + a2ui_catalog = self._a2ui_catalog(ctx) + if inspect.isawaitable(a2ui_catalog): + a2ui_catalog = await a2ui_catalog + return a2ui_catalog async def process_llm_request( self, *, tool_context: ToolContext, llm_request: LlmRequest @@ -231,15 +245,14 @@ async def process_llm_request( tool_context=tool_context, llm_request=llm_request ) - a2ui_schema = await self.get_a2ui_schema(tool_context) + a2ui_catalog = await self._resolve_a2ui_catalog(tool_context) + + instruction = a2ui_catalog.render_as_llm_instructions() + examples = await self._resolve_a2ui_examples(tool_context) - llm_request.append_instructions([f""" ----BEGIN A2UI JSON SCHEMA--- -{json.dumps(a2ui_schema)} ----END A2UI JSON SCHEMA--- -"""]) + llm_request.append_instructions([instruction, examples]) - logger.info("Added a2ui_schema to system instructions") + logger.info("Added A2UI schema and examples to system instructions") async def run_async( self, *, args: dict[str, Any], tool_context: ToolContext @@ -261,8 +274,9 @@ async def run_async( ) a2ui_json_payload = [a2ui_json_payload] - a2ui_schema = await self.get_a2ui_schema(tool_context) - jsonschema.validate(instance=a2ui_json_payload, schema=a2ui_schema) + a2ui_catalog = await self._resolve_a2ui_catalog(tool_context) + + a2ui_catalog.validator.validate(a2ui_json_payload) logger.info( f"Validated call to tool {self.TOOL_NAME} with {self.A2UI_JSON_ARG_NAME}" diff --git a/a2a_agents/python/a2ui_agent/tests/extension/test_send_a2ui_to_client_toolset.py b/a2a_agents/python/a2ui_agent/tests/extension/test_send_a2ui_to_client_toolset.py index 03fd74748..de3d9b8a8 100644 --- a/a2a_agents/python/a2ui_agent/tests/extension/test_send_a2ui_to_client_toolset.py +++ b/a2a_agents/python/a2ui_agent/tests/extension/test_send_a2ui_to_client_toolset.py @@ -22,45 +22,49 @@ from a2ui.extension.send_a2ui_to_client_toolset import convert_send_a2ui_to_client_genai_part_to_a2a_part from a2ui.extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset +from a2ui.inference.schema.catalog import A2uiCatalog from google.adk.agents.readonly_context import ReadonlyContext from google.adk.tools.tool_context import ToolContext from google.genai import types as genai_types -# Basic A2UI Schema for testing -TEST_A2UI_SCHEMA = { - "type": "object", - "properties": {"type": {"const": "Text"}, "text": {"type": "string"}}, - "required": ["type", "text"], -} - # region SendA2uiToClientToolset Tests """Tests for the SendA2uiToClientToolset class.""" @pytest.mark.asyncio async def test_toolset_init_bool(): - toolset = SendA2uiToClientToolset(a2ui_enabled=True, a2ui_schema=TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + toolset = SendA2uiToClientToolset( + a2ui_enabled=True, a2ui_catalog=catalog_mock, a2ui_examples="examples" + ) ctx = MagicMock(spec=ReadonlyContext) assert await toolset._resolve_a2ui_enabled(ctx) == True # Access the tool to check schema resolution tool = toolset._ui_tools[0] - assert await tool._resolve_a2ui_schema(ctx) == TEST_A2UI_SCHEMA + assert await tool._resolve_a2ui_catalog(ctx) == catalog_mock @pytest.mark.asyncio async def test_toolset_init_callable(): enabled_mock = MagicMock(return_value=True) - schema_mock = MagicMock(return_value=TEST_A2UI_SCHEMA) - toolset = SendA2uiToClientToolset(a2ui_enabled=enabled_mock, a2ui_schema=schema_mock) + catalog_mock = MagicMock(spec=A2uiCatalog) + examples_mock = MagicMock(return_value="examples") + toolset = SendA2uiToClientToolset( + a2ui_enabled=enabled_mock, + a2ui_catalog=catalog_mock, + a2ui_examples=examples_mock, + ) ctx = MagicMock(spec=ReadonlyContext) assert await toolset._resolve_a2ui_enabled(ctx) == True # Access the tool to check schema resolution tool = toolset._ui_tools[0] - assert await tool._resolve_a2ui_schema(ctx) == TEST_A2UI_SCHEMA + assert await tool._resolve_a2ui_catalog(ctx) == catalog_mock + assert await tool._resolve_a2ui_examples(ctx) == "examples" enabled_mock.assert_called_once_with(ctx) - schema_mock.assert_called_once_with(ctx) + catalog_mock.assert_not_called() # It's an object, not a callable in this test + examples_mock.assert_called_once_with(ctx) @pytest.mark.asyncio @@ -68,23 +72,33 @@ async def test_toolset_init_async_callable(): async def async_enabled(_ctx): return True - async def async_schema(_ctx): - return TEST_A2UI_SCHEMA + catalog_mock = MagicMock(spec=A2uiCatalog) + + async def async_catalog(_ctx): + return catalog_mock + + async def async_examples(_ctx): + return "examples" toolset = SendA2uiToClientToolset( - a2ui_enabled=async_enabled, a2ui_schema=async_schema + a2ui_enabled=async_enabled, + a2ui_catalog=async_catalog, + a2ui_examples=async_examples, ) ctx = MagicMock(spec=ReadonlyContext) assert await toolset._resolve_a2ui_enabled(ctx) == True # Access the tool to check schema resolution tool = toolset._ui_tools[0] - assert await tool._resolve_a2ui_schema(ctx) == TEST_A2UI_SCHEMA + assert await tool._resolve_a2ui_catalog(ctx) == catalog_mock + assert await tool._resolve_a2ui_examples(ctx) == "examples" @pytest.mark.asyncio async def test_toolset_get_tools_enabled(): - toolset = SendA2uiToClientToolset(a2ui_enabled=True, a2ui_schema=TEST_A2UI_SCHEMA) + toolset = SendA2uiToClientToolset( + a2ui_enabled=True, a2ui_catalog=MagicMock(spec=A2uiCatalog), a2ui_examples="" + ) tools = await toolset.get_tools(MagicMock(spec=ReadonlyContext)) assert len(tools) == 1 assert isinstance(tools[0], SendA2uiToClientToolset._SendA2uiJsonToClientTool) @@ -92,7 +106,11 @@ async def test_toolset_get_tools_enabled(): @pytest.mark.asyncio async def test_toolset_get_tools_disabled(): - toolset = SendA2uiToClientToolset(a2ui_enabled=False, a2ui_schema=TEST_A2UI_SCHEMA) + toolset = SendA2uiToClientToolset( + a2ui_enabled=False, + a2ui_catalog=MagicMock(spec=A2uiCatalog), + a2ui_examples="", + ) tools = await toolset.get_tools(MagicMock(spec=ReadonlyContext)) assert len(tools) == 0 @@ -104,13 +122,16 @@ async def test_toolset_get_tools_disabled(): def test_send_tool_init(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") assert tool.name == SendA2uiToClientToolset._SendA2uiJsonToClientTool.TOOL_NAME - assert tool._a2ui_schema == TEST_A2UI_SCHEMA + assert tool._a2ui_catalog == catalog_mock + assert tool._a2ui_examples == "examples" def test_send_tool_get_declaration(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") declaration = tool._get_declaration() assert declaration is not None assert declaration.name == SendA2uiToClientToolset._SendA2uiJsonToClientTool.TOOL_NAME @@ -125,24 +146,28 @@ def test_send_tool_get_declaration(): @pytest.mark.asyncio -async def test_send_tool_get_a2ui_schema(): - schema_mock = MagicMock(return_value=TEST_A2UI_SCHEMA) - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(schema_mock) - schema = await tool.get_a2ui_schema(MagicMock(spec=ReadonlyContext)) - assert schema == {"type": "array", "items": TEST_A2UI_SCHEMA} +async def test_send_tool_resolve_catalog(): + catalog_mock = MagicMock(spec=A2uiCatalog) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") + catalog = await tool._resolve_a2ui_catalog(MagicMock(spec=ReadonlyContext)) + assert catalog == catalog_mock @pytest.mark.asyncio -async def test_send_tool_get_a2ui_schema_empty(): - schema_mock = MagicMock(return_value=None) - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(schema_mock) - with pytest.raises(ValueError): - await tool.get_a2ui_schema(MagicMock(spec=ReadonlyContext)) +async def test_send_tool_resolve_examples(): + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool( + MagicMock(spec=A2uiCatalog), "examples" + ) + examples = await tool._resolve_a2ui_examples(MagicMock(spec=ReadonlyContext)) + assert examples == "examples" @pytest.mark.asyncio async def test_send_tool_process_llm_request(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + catalog_mock.render_as_llm_instructions.return_value = "rendered_catalog" + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") + tool_context_mock = MagicMock(spec=ToolContext) tool_context_mock.state = {} llm_request_mock = MagicMock() @@ -154,15 +179,15 @@ async def test_send_tool_process_llm_request(): llm_request_mock.append_instructions.assert_called_once() args, _ = llm_request_mock.append_instructions.call_args - instruction = args[0][0] - assert "---BEGIN A2UI JSON SCHEMA---" in instruction - assert json.dumps({"type": "array", "items": TEST_A2UI_SCHEMA}) in instruction - assert "---END A2UI JSON SCHEMA---" in instruction + instructions = args[0] + assert "rendered_catalog" in instructions + assert "examples" in instructions @pytest.mark.asyncio async def test_send_tool_run_async_valid(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") tool_context_mock = MagicMock(spec=ToolContext) tool_context_mock.state = {} tool_context_mock.actions = MagicMock(skip_summarization=False) @@ -185,7 +210,8 @@ async def test_send_tool_run_async_valid(): @pytest.mark.asyncio async def test_send_tool_run_async_valid_list(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") tool_context_mock = MagicMock(spec=ToolContext) tool_context_mock.state = {} tool_context_mock.actions = MagicMock(skip_summarization=False) @@ -208,7 +234,9 @@ async def test_send_tool_run_async_valid_list(): @pytest.mark.asyncio async def test_send_tool_run_async_missing_arg(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool( + MagicMock(spec=A2uiCatalog), "examples" + ) result = await tool.run_async(args={}, tool_context=MagicMock()) assert "error" in result assert ( @@ -219,7 +247,9 @@ async def test_send_tool_run_async_missing_arg(): @pytest.mark.asyncio async def test_send_tool_run_async_invalid_json(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool( + MagicMock(spec=A2uiCatalog), "examples" + ) args = { SendA2uiToClientToolset._SendA2uiJsonToClientTool.A2UI_JSON_ARG_NAME: "{invalid" } @@ -230,7 +260,11 @@ async def test_send_tool_run_async_invalid_json(): @pytest.mark.asyncio async def test_send_tool_run_async_schema_validation_fail(): - tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(TEST_A2UI_SCHEMA) + catalog_mock = MagicMock(spec=A2uiCatalog) + catalog_mock.validator.validate.side_effect = Exception( + "'text' is a required property" + ) + tool = SendA2uiToClientToolset._SendA2uiJsonToClientTool(catalog_mock, "examples") invalid_a2ui = [{"type": "Text"}] # Missing 'text' args = { SendA2uiToClientToolset._SendA2uiJsonToClientTool.A2UI_JSON_ARG_NAME: json.dumps( diff --git a/renderers/lit/package-lock.json b/renderers/lit/package-lock.json index ced356e5f..f4f322b3e 100644 --- a/renderers/lit/package-lock.json +++ b/renderers/lit/package-lock.json @@ -26,7 +26,7 @@ }, "../web_core": { "name": "@a2ui/web_core", - "version": "0.8.0", + "version": "0.8.2", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.8.3", @@ -1007,8 +1007,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/signal-polyfill/-/signal-polyfill-0.2.2.tgz", "integrity": "sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/signal-utils": { "version": "0.21.1", diff --git a/renderers/web_core/package-lock.json b/renderers/web_core/package-lock.json index fdcd63be0..365fea171 100644 --- a/renderers/web_core/package-lock.json +++ b/renderers/web_core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@a2ui/web_core", - "version": "0.8.0", + "version": "0.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@a2ui/web_core", - "version": "0.8.0", + "version": "0.8.2", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.8.3", diff --git a/samples/agent/adk/rizzcharts/__main__.py b/samples/agent/adk/rizzcharts/__main__.py index 7b9481214..5ac4a7e16 100644 --- a/samples/agent/adk/rizzcharts/__main__.py +++ b/samples/agent/adk/rizzcharts/__main__.py @@ -21,7 +21,8 @@ from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore -from agent_executor import RizzchartsAgentExecutor, get_a2ui_enabled, get_a2ui_schema +from a2ui.inference.schema.manager import A2uiSchemaManager, CustomCatalogConfig +from agent_executor import RizzchartsAgentExecutor, get_a2ui_enabled, get_a2ui_catalog, get_a2ui_examples from agent import RizzchartsAgent from google.adk.artifacts import InMemoryArtifactService from google.adk.memory.in_memory_memory_service import InMemoryMemoryService @@ -55,10 +56,29 @@ def main(host, port): ) lite_llm_model = os.getenv("LITELLM_MODEL", "gemini/gemini-2.5-flash") + + base_url = f"http://{host}:{port}" + + schema_manager = A2uiSchemaManager( + version="0.8", + basic_examples_path="examples/standard_catalog", + custom_catalogs=[ + CustomCatalogConfig( + name="rizzcharts", + catalog_path="rizzcharts_catalog_definition.json", + examples_path="examples/rizzcharts_catalog", + ) + ], + accepts_inline_catalogs=True, + ) + agent = RizzchartsAgent( + base_url=base_url, model=LiteLlm(model=lite_llm_model), + schema_manager=schema_manager, a2ui_enabled_provider=get_a2ui_enabled, - a2ui_schema_provider=get_a2ui_schema, + a2ui_catalog_provider=get_a2ui_catalog, + a2ui_examples_provider=get_a2ui_examples, ) runner = Runner( app_name=agent.name, @@ -68,30 +88,10 @@ def main(host, port): memory_service=InMemoryMemoryService(), ) - current_dir = pathlib.Path(__file__).resolve().parent - spec_root = current_dir / "../../../../specification/v0_8/json" - - try: - a2ui_schema_content = (spec_root / "server_to_client.json").read_text() - standard_catalog_content = ( - spec_root / "standard_catalog_definition.json" - ).read_text() - rizzcharts_catalog_content = ( - current_dir / "rizzcharts_catalog_definition.json" - ).read_text() - except FileNotFoundError as e: - logger.error(f"Failed to load required JSON files: {e}") - exit(1) - - logger.info(f"Loaded schema from {spec_root}") - - base_url = f"http://{host}:{port}" agent_executor = RizzchartsAgentExecutor( base_url=base_url, runner=runner, - a2ui_schema_content=a2ui_schema_content, - standard_catalog_content=standard_catalog_content, - rizzcharts_catalog_content=rizzcharts_catalog_content, + schema_manager=schema_manager, ) request_handler = DefaultRequestHandler( @@ -99,7 +99,7 @@ def main(host, port): task_store=InMemoryTaskStore(), ) server = A2AStarletteApplication( - agent_card=agent_executor.get_agent_card(), http_handler=request_handler + agent_card=agent.get_agent_card(), http_handler=request_handler ) import uvicorn diff --git a/samples/agent/adk/rizzcharts/agent.py b/samples/agent/adk/rizzcharts/agent.py index a1958d4fb..7c42580df 100644 --- a/samples/agent/adk/rizzcharts/agent.py +++ b/samples/agent/adk/rizzcharts/agent.py @@ -17,10 +17,10 @@ from pathlib import Path import pkgutil from typing import Any, ClassVar - +from a2a.types import AgentCapabilities, AgentCard, AgentSkill from a2ui.extension.a2ui_extension import STANDARD_CATALOG_ID -from a2ui.extension.a2ui_schema_utils import wrap_as_json_array -from a2ui.extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset, A2uiEnabledProvider, A2uiSchemaProvider +from a2ui.extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset, A2uiEnabledProvider, A2uiCatalogProvider, A2uiExamplesProvider +from a2ui.inference.schema.manager import A2uiSchemaManager from google.adk.agents.llm_agent import LlmAgent from google.adk.agents.readonly_context import ReadonlyContext from google.adk.planners.built_in_planner import BuiltInPlanner @@ -36,40 +36,103 @@ logger = logging.getLogger(__name__) RIZZCHARTS_CATALOG_URI = "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json" -A2UI_CATALOG_URI_STATE_KEY = "user:a2ui_catalog_uri" + +ROLE_DESCRIPTION = """ +You are an expert A2UI Ecommerce Dashboard analyst. Your primary function is to translate user requests for ecommerce data into A2UI JSON payloads to display charts and visualizations. You MUST use the `send_a2ui_json_to_client` tool with the `a2ui_json` argument set to the A2UI JSON payload to send to the client. You should also include a brief text message with each response saying what you did and asking if you can help with anything else. +""" + +WORKFLOW_DESCRIPTION = """ +Your task is to analyze the user's request, fetch the necessary data, select the correct generic template, and send the corresponding A2UI JSON payload. + +1. **Analyze the Request:** Determine the user's intent (Visual Chart vs. Geospatial Map). + * "show my sales breakdown by product category for q3" -> **Intent:** Chart. + * "show revenue trends yoy by month" -> **Intent:** Chart. + * "were there any outlier stores in the northeast region" -> **Intent:** Map. + +2. **Fetch Data:** Select and use the appropriate tool to retrieve the necessary data. + * Use **`get_sales_data`** for general sales, revenue, and product category trends (typically for Charts). + * Use **`get_store_sales`** for regional performance, store locations, and geospatial outliers (typically for Maps). + +3. **Select Example:** Based on the intent, choose the correct example block to use as your template. + * **Intent** (Chart/Data Viz) -> Use `---BEGIN CHART EXAMPLE---`. + * **Intent** (Map/Geospatial) -> Use `---BEGIN MAP EXAMPLE---`. + +4. **Construct the JSON Payload:** + * Use the **entire** JSON array from the chosen example as the base value for the `a2ui_json` argument. + * **Generate a new `surfaceId`:** You MUST generate a new, unique `surfaceId` for this request (e.g., `sales_breakdown_q3_surface`, `regional_outliers_northeast_surface`). This new ID must be used for the `surfaceId` in all three messages within the JSON array (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`). + * **Update the title Text:** You MUST update the `literalString` value for the `Text` component (the component with `id: "page_header"`) to accurately reflect the specific user query. For example, if the user asks for "Q3" sales, update the generic template text to "Q3 2025 Sales by Product Category". + * Ensure the generated JSON perfectly matches the A2UI specification. It will be validated against the json_schema and rejected if it does not conform. + * If you get an error in the tool response apologize to the user and let them know they should try again. + +5. **Call the Tool:** Call the `send_a2ui_json_to_client` tool with the fully constructed `a2ui_json` payload. +""" + +UI_DESCRIPTION = """ +**Core Objective:** To provide a dynamic and interactive dashboard by constructing UI surfaces with the appropriate visualization components based on user queries. + +**Key Components & Examples:** + +You will be provided a schema that defines the A2UI message structure and two key generic component templates for displaying data. + +1. **Charts:** Used for requests about sales breakdowns, revenue performance, comparisons, or trends. + * **Template:** Use the JSON from `---BEGIN CHART EXAMPLE---`. +2. **Maps:** Used for requests about regional data, store locations, geography-based performance, or regional outliers. + * **Template:** Use the JSON from `---BEGIN MAP EXAMPLE---`. + +You will also use layout components like `Column` (as the `root`) and `Text` (to provide a title). +""" class RizzchartsAgent(LlmAgent): """An agent that runs an ecommerce dashboard""" SUPPORTED_CONTENT_TYPES: ClassVar[list[str]] = ["text", "text/plain"] + base_url: str = "" + schema_manager: A2uiSchemaManager = None _a2ui_enabled_provider: A2uiEnabledProvider = PrivateAttr() - _a2ui_schema_provider: A2uiSchemaProvider = PrivateAttr() + _a2ui_catalog_provider: A2uiCatalogProvider = PrivateAttr() + _a2ui_examples_provider: A2uiExamplesProvider = PrivateAttr() def __init__( self, model: Any, + base_url: str, + schema_manager: A2uiSchemaManager, a2ui_enabled_provider: A2uiEnabledProvider, - a2ui_schema_provider: A2uiSchemaProvider, + a2ui_catalog_provider: A2uiCatalogProvider, + a2ui_examples_provider: A2uiExamplesProvider, ): """Initializes the RizzchartsAgent. Args: model: The LLM model to use. + base_url: The base URL for the agent. + schema_manager: The A2UI schema manager. a2ui_enabled_provider: A provider to check if A2UI is enabled. - a2ui_schema_provider: A provider to retrieve the A2UI schema. + a2ui_catalog_provider: A provider to retrieve the A2UI catalog (A2uiCatalog object). + a2ui_examples_provider: A provider to retrieve the A2UI examples (str). """ + + system_instructions = schema_manager.generate_system_prompt( + role_description=ROLE_DESCRIPTION, + workflow_description=WORKFLOW_DESCRIPTION, + ui_description=UI_DESCRIPTION, + include_schema=False, + include_examples=False, + validate_examples=False, + ) super().__init__( model=model, name="rizzcharts_agent", description="An agent that lets sales managers request sales data.", - instruction=self.get_instructions, + instruction=system_instructions, tools=[ get_store_sales, get_sales_data, SendA2uiToClientToolset( - a2ui_schema=a2ui_schema_provider, + a2ui_catalog=a2ui_catalog_provider, a2ui_enabled=a2ui_enabled_provider, + a2ui_examples=a2ui_examples_provider, ), ], planner=BuiltInPlanner( @@ -78,142 +141,60 @@ def __init__( ) ), disallow_transfer_to_peers=True, + base_url=base_url, + schema_manager=schema_manager, ) self._a2ui_enabled_provider = a2ui_enabled_provider - self._a2ui_schema_provider = a2ui_schema_provider + self._a2ui_catalog_provider = a2ui_catalog_provider + self._a2ui_examples_provider = a2ui_examples_provider - def get_a2ui_schema(self, ctx: ReadonlyContext) -> dict[str, Any]: - """Retrieves and wraps the A2UI schema from the session state. - - Args: - ctx: The ReadonlyContext for resolving the schema. + def get_agent_card(self) -> AgentCard: + """Returns the AgentCard defining this agent's metadata and skills. Returns: - The wrapped A2UI schema. + An AgentCard object. """ - a2ui_schema = self._a2ui_schema_provider(ctx) - return wrap_as_json_array(a2ui_schema) - - def load_example(self, path: str, a2ui_schema: dict[str, Any]) -> dict[str, Any]: - """Loads an example JSON file and validates it against the A2UI schema. - - Args: - path: Relative path to the example JSON file. - a2ui_schema: The A2UI schema to validate against. - - Returns: - The loaded and validated JSON data. - """ - data = None - try: - # Try pkgutil first (for Google3) - package_name = __package__ or "" - data = pkgutil.get_data(package_name, path) - except ImportError: - logger.info("pkgutil failed to get data, falling back to file system.") - - if data: - example_str = data.decode("utf-8") - else: - # Fallback to direct Path relative to this file (for local dev) - full_path = Path(__file__).parent / path - example_str = full_path.read_text() - - example_json = json.loads(example_str) - jsonschema.validate(instance=example_json, schema=a2ui_schema) - return example_json - - def get_instructions(self, readonly_context: ReadonlyContext) -> str: - """Generates the system instructions for the agent. - - Args: - readonly_context: The ReadonlyContext for resolving instructions. - - Returns: - The generated system instructions. - """ - use_ui = self._a2ui_enabled_provider(readonly_context) - if not use_ui: - raise ValueError("A2UI must be enabled to run rizzcharts agent") - - a2ui_schema = self.get_a2ui_schema(readonly_context) - catalog_uri = readonly_context.state.get(A2UI_CATALOG_URI_STATE_KEY) - if catalog_uri == RIZZCHARTS_CATALOG_URI: - map_example = self.load_example( - "examples/rizzcharts_catalog/map.json", a2ui_schema - ) - chart_example = self.load_example( - "examples/rizzcharts_catalog/chart.json", a2ui_schema - ) - elif catalog_uri == STANDARD_CATALOG_ID: - map_example = self.load_example("examples/standard_catalog/map.json", a2ui_schema) - chart_example = self.load_example( - "examples/standard_catalog/chart.json", a2ui_schema - ) - else: - raise ValueError( - f"Unsupported catalog uri: {catalog_uri if catalog_uri else 'None'}" - ) - - final_prompt = f""" -### System Instructions - -You are an expert A2UI Ecommerce Dashboard analyst. Your primary function is to translate user requests for ecommerce data into A2UI JSON payloads to display charts and visualizations. You MUST use the `send_a2ui_json_to_client` tool with the `a2ui_json` argument set to the A2UI JSON payload to send to the client. You should also include a brief text message with each response saying what you did and asking if you can help with anything else. - -**Core Objective:** To provide a dynamic and interactive dashboard by constructing UI surfaces with the appropriate visualization components based on user queries. - -**Key Components & Examples:** - -You will be provided a schema that defines the A2UI message structure and two key generic component templates for displaying data. - -1. **Charts:** Used for requests about sales breakdowns, revenue performance, comparisons, or trends. - * **Template:** Use the JSON from `---BEGIN CHART EXAMPLE---`. -2. **Maps:** Used for requests about regional data, store locations, geography-based performance, or regional outliers. - * **Template:** Use the JSON from `---BEGIN MAP EXAMPLE---`. - -You will also use layout components like `Column` (as the `root`) and `Text` (to provide a title). - ---- - -### Workflow and Rules - -Your task is to analyze the user's request, fetch the necessary data, select the correct generic template, and send the corresponding A2UI JSON payload. - -1. **Analyze the Request:** Determine the user's intent (Visual Chart vs. Geospatial Map). - * "show my sales breakdown by product category for q3" -> **Intent:** Chart. - * "show revenue trends yoy by month" -> **Intent:** Chart. - * "were there any outlier stores in the northeast region" -> **Intent:** Map. - -2. **Fetch Data:** Select and use the appropriate tool to retrieve the necessary data. - * Use **`get_sales_data`** for general sales, revenue, and product category trends (typically for Charts). - * Use **`get_store_sales`** for regional performance, store locations, and geospatial outliers (typically for Maps). - -3. **Select Example:** Based on the intent, choose the correct example block to use as your template. - * **Intent** (Chart/Data Viz) -> Use `---BEGIN CHART EXAMPLE---`. - * **Intent** (Map/Geospatial) -> Use `---BEGIN MAP EXAMPLE---`. - -4. **Construct the JSON Payload:** - * Use the **entire** JSON array from the chosen example as the base value for the `a2ui_json` argument. - * **Generate a new `surfaceId`:** You MUST generate a new, unique `surfaceId` for this request (e.g., `sales_breakdown_q3_surface`, `regional_outliers_northeast_surface`). This new ID must be used for the `surfaceId` in all three messages within the JSON array (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`). - * **Update the title Text:** You MUST update the `literalString` value for the `Text` component (the component with `id: "page_header"`) to accurately reflect the specific user query. For example, if the user asks for "Q3" sales, update the generic template text to "Q3 2025 Sales by Product Category". - * Ensure the generated JSON perfectly matches the A2UI specification. It will be validated against the json_schema and rejected if it does not conform. - * If you get an error in the tool response apologize to the user and let them know they should try again. - -5. **Call the Tool:** Call the `send_a2ui_json_to_client` tool with the fully constructed `a2ui_json` payload. - ----BEGIN CHART EXAMPLE--- -{json.dumps(chart_example)} ----END CHART EXAMPLE--- - ----BEGIN MAP EXAMPLE--- -{json.dumps(map_example)} ----END MAP EXAMPLE--- -""" - - logger.info( - f"Generated system instructions for A2UI {'ENABLED' if use_ui else 'DISABLED'}" - f" and catalog {catalog_uri}" + return AgentCard( + name="Ecommerce Dashboard Agent", + description=( + "This agent visualizes ecommerce data, showing sales breakdowns, YOY" + " revenue performance, and regional sales outliers." + ), + url=self.base_url, + version="1.0.0", + default_input_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, + default_output_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, + capabilities=AgentCapabilities( + streaming=True, + extensions=[self.schema_manager.get_agent_extension()], + ), + skills=[ + AgentSkill( + id="view_sales_by_category", + name="View Sales by Category", + description=( + "Displays a pie chart of sales broken down by product category for" + " a given time period." + ), + tags=["sales", "breakdown", "category", "pie chart", "revenue"], + examples=[ + "show my sales breakdown by product category for q3", + "What's the sales breakdown for last month?", + ], + ), + AgentSkill( + id="view_regional_outliers", + name="View Regional Sales Outliers", + description=( + "Displays a map showing regional sales outliers or store-level" + " performance." + ), + tags=["sales", "regional", "outliers", "stores", "map", "performance"], + examples=[ + "interesting. were there any outlier stores", + "show me a map of store performance", + ], + ), + ], ) - - return final_prompt diff --git a/samples/agent/adk/rizzcharts/agent_executor.py b/samples/agent/adk/rizzcharts/agent_executor.py index 05d985fc9..f859f7c9c 100644 --- a/samples/agent/adk/rizzcharts/agent_executor.py +++ b/samples/agent/adk/rizzcharts/agent_executor.py @@ -23,18 +23,8 @@ from a2ui.extension.a2ui_extension import STANDARD_CATALOG_ID from a2ui.extension.a2ui_extension import get_a2ui_agent_extension from a2ui.extension.a2ui_extension import try_activate_a2ui_extension +from a2ui.inference.schema.manager import A2uiSchemaManager from a2ui.extension.send_a2ui_to_client_toolset import convert_send_a2ui_to_client_genai_part_to_a2a_part - -try: - from .agent import A2UI_CATALOG_URI_STATE_KEY # pylint: disable=import-error - from .agent import RIZZCHARTS_CATALOG_URI # pylint: disable=import-error - from .agent import RizzchartsAgent # pylint: disable=import-error - from .component_catalog_builder import ComponentCatalogBuilder # pylint: disable=import-error -except ImportError: - from agent import A2UI_CATALOG_URI_STATE_KEY - from agent import RIZZCHARTS_CATALOG_URI - from agent import RizzchartsAgent - from component_catalog_builder import ComponentCatalogBuilder from google.adk.a2a.converters.request_converter import AgentRunRequest from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutorConfig @@ -47,19 +37,32 @@ logger = logging.getLogger(__name__) _A2UI_ENABLED_KEY = "system:a2ui_enabled" -_A2UI_SCHEMA_KEY = "system:a2ui_schema" +_A2UI_CATALOG_KEY = "system:a2ui_catalog" +_A2UI_EXAMPLES_KEY = "system:a2ui_examples" -def get_a2ui_schema(ctx: ReadonlyContext): - """Retrieves the A2UI schema from the session state. +def get_a2ui_catalog(ctx: ReadonlyContext): + """Retrieves the A2UI catalog from the session state. Args: - ctx: The ReadonlyContext for resolving the schema. + ctx: The ReadonlyContext for resolving the catalog. Returns: - The A2UI schema or None if not found. + The A2UI catalog or None if not found. """ - return ctx.state.get(_A2UI_SCHEMA_KEY) + return ctx.state.get(_A2UI_CATALOG_KEY) + + +def get_a2ui_examples(ctx: ReadonlyContext): + """Retrieves the A2UI examples from the session state. + + Args: + ctx: The ReadonlyContext for resolving the examples. + + Returns: + The A2UI examples or None if not found. + """ + return ctx.state.get(_A2UI_EXAMPLES_KEY) def get_a2ui_enabled(ctx: ReadonlyContext): @@ -81,79 +84,16 @@ def __init__( self, base_url: str, runner: Runner, - a2ui_schema_content: str, - standard_catalog_content: str, - rizzcharts_catalog_content: str, + schema_manager: A2uiSchemaManager, ): self._base_url = base_url - self._component_catalog_builder = ComponentCatalogBuilder( - a2ui_schema_content=a2ui_schema_content, - uri_to_local_catalog_content={ - STANDARD_CATALOG_ID: standard_catalog_content, - RIZZCHARTS_CATALOG_URI: rizzcharts_catalog_content, - }, - default_catalog_uri=STANDARD_CATALOG_ID, - ) + self.schema_manager = schema_manager config = A2aAgentExecutorConfig( gen_ai_part_converter=convert_send_a2ui_to_client_genai_part_to_a2a_part ) super().__init__(runner=runner, config=config) - def get_agent_card(self) -> AgentCard: - """Returns the AgentCard defining this agent's metadata and skills. - - Returns: - An AgentCard object. - """ - return AgentCard( - name="Ecommerce Dashboard Agent", - description=( - "This agent visualizes ecommerce data, showing sales breakdowns, YOY" - " revenue performance, and regional sales outliers." - ), - url=self._base_url, - version="1.0.0", - default_input_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, - default_output_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities( - streaming=True, - extensions=[ - get_a2ui_agent_extension( - supported_catalog_ids=[STANDARD_CATALOG_ID, RIZZCHARTS_CATALOG_URI] - ) - ], - ), - skills=[ - AgentSkill( - id="view_sales_by_category", - name="View Sales by Category", - description=( - "Displays a pie chart of sales broken down by product category for" - " a given time period." - ), - tags=["sales", "breakdown", "category", "pie chart", "revenue"], - examples=[ - "show my sales breakdown by product category for q3", - "What's the sales breakdown for last month?", - ], - ), - AgentSkill( - id="view_regional_outliers", - name="View Regional Sales Outliers", - description=( - "Displays a map showing regional sales outliers or store-level" - " performance." - ), - tags=["sales", "regional", "outliers", "stores", "map", "performance"], - examples=[ - "interesting. were there any outlier stores", - "show me a map of store performance", - ], - ), - ], - ) - @override async def _prepare_session( self, @@ -170,13 +110,16 @@ async def _prepare_session( use_ui = try_activate_a2ui_extension(context) if use_ui: - a2ui_schema, catalog_uri = self._component_catalog_builder.load_a2ui_schema( - client_ui_capabilities=context.message.metadata.get( - A2UI_CLIENT_CAPABILITIES_KEY - ) + capabilities = ( + context.message.metadata.get(A2UI_CLIENT_CAPABILITIES_KEY) if context.message and context.message.metadata else None ) + a2ui_catalog = self.schema_manager.get_effective_catalog( + client_ui_capabilities=capabilities + ) + + examples = self.schema_manager.load_examples(a2ui_catalog, validate=True) await runner.session_service.append_event( session, @@ -186,8 +129,8 @@ async def _prepare_session( actions=EventActions( state_delta={ _A2UI_ENABLED_KEY: True, - _A2UI_SCHEMA_KEY: a2ui_schema, - A2UI_CATALOG_URI_STATE_KEY: catalog_uri, + _A2UI_CATALOG_KEY: a2ui_catalog, + _A2UI_EXAMPLES_KEY: examples, } ), ), diff --git a/samples/agent/adk/rizzcharts/component_catalog_builder.py b/samples/agent/adk/rizzcharts/component_catalog_builder.py deleted file mode 100644 index 4b7f45dd5..000000000 --- a/samples/agent/adk/rizzcharts/component_catalog_builder.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -import logging -from typing import Any, List, Optional -from a2ui.extension.a2ui_extension import INLINE_CATALOGS_KEY, SUPPORTED_CATALOG_IDS_KEY - -try: - from .agent import RIZZCHARTS_CATALOG_URI, STANDARD_CATALOG_ID -except ImportError: - from agent import RIZZCHARTS_CATALOG_URI, STANDARD_CATALOG_ID - -logger = logging.getLogger(__name__) - - -class ComponentCatalogBuilder: - - def __init__( - self, - a2ui_schema_content: str, - uri_to_local_catalog_content: dict[str, str], - default_catalog_uri: Optional[str], - ): - self._a2ui_schema_content = a2ui_schema_content - self._uri_to_local_catalog_content = uri_to_local_catalog_content - self._default_catalog_uri = default_catalog_uri - - def load_a2ui_schema( - self, client_ui_capabilities: Optional[dict[str, Any]] - ) -> tuple[dict[str, Any], Optional[str]]: - """ - Returns: - A tuple of the a2ui_schema and the catalog uri - """ - try: - logger.info(f"Loading A2UI client capabilities {client_ui_capabilities}") - - if client_ui_capabilities: - supported_catalog_uris: List[str] = client_ui_capabilities.get( - SUPPORTED_CATALOG_IDS_KEY - ) - if RIZZCHARTS_CATALOG_URI in supported_catalog_uris: - catalog_uri = RIZZCHARTS_CATALOG_URI - elif STANDARD_CATALOG_ID in supported_catalog_uris: - catalog_uri = STANDARD_CATALOG_ID - else: - catalog_uri = None - - inline_catalog_str = client_ui_capabilities.get(INLINE_CATALOGS_KEY) - elif self._default_catalog_uri: - logger.info( - f"Using default catalog {self._default_catalog_uri} since client UI" - " capabilities not found" - ) - catalog_uri = self._default_catalog_uri - inline_catalog_str = None - else: - raise ValueError("Client UI capabilities not provided") - - if catalog_uri and inline_catalog_str: - raise ValueError( - f"Cannot set both {SUPPORTED_CATALOG_IDS_KEY} and {INLINE_CATALOGS_KEY} in" - f" ClientUiCapabilities: {client_ui_capabilities}" - ) - elif catalog_uri: - if catalog_str := self._uri_to_local_catalog_content.get(catalog_uri): - logger.info(f"Loading local component catalog with uri {catalog_uri}") - catalog_json = json.loads(catalog_str) - else: - raise ValueError(f"Local component catalog with URI {catalog_uri} not found") - elif inline_catalog_str: - logger.info(f"Loading inline component catalog {inline_catalog_str[:200]}") - catalog_json = json.loads(inline_catalog_str) - else: - raise ValueError("No supported catalogs found in client UI capabilities") - - logger.info("Loading A2UI schema") - a2ui_schema_json = json.loads(self._a2ui_schema_content) - - a2ui_schema_json["properties"]["surfaceUpdate"]["properties"]["components"][ - "items" - ]["properties"]["component"]["properties"] = catalog_json - - return a2ui_schema_json, catalog_uri - - except Exception as e: - logger.error( - "Failed to a2ui schema with client ui capabilities" - f" {client_ui_capabilities}: {e}" - ) - raise e diff --git a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json b/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json index 88006a6fa..9915d47cf 100644 --- a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json +++ b/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json @@ -1,6 +1,7 @@ { + "catalogId": "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json", "components": { - "$ref": "../../../../specification/v0_8/json/standard_catalog_definition.json#/components", + "$ref": "https://a2ui.org/specification/v0_8/standard_catalog_definition.json#/components", "Canvas": { "type": "object", "description": "Renders the UI element in a stateful panel next to the chat window.",