Fix inline catalog validation and multi-surface support#831
Fix inline catalog validation and multi-surface support#831sarthak96agarwal wants to merge 1 commit intogoogle:mainfrom
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
The pull request refactors the UI catalog selection and validation logic to support multi-surface rendering and incremental updates. The _select_catalog function now merges inline catalogs with a base catalog, and the component validation (_validate_component_integrity, _validate_topology) distinguishes between initial renders (where root component and orphan checks apply) and incremental updates (where these checks are skipped). Corresponding test cases have been added or updated to reflect this new behavior. Additionally, the OrgChart component schema was updated to allow chain to be either a path reference or an array, and the action schema was detailed. A review comment suggests improving the readability and maintainability of a long JSON string in prompt_builder.py by defining it as a Python dictionary and then serializing it.
| client_ui_capabilities_str = ( | ||
| '{"inlineCatalogs":[{"catalogId": "inline_catalog",' | ||
| ' "components":{"OrgChart":{"type":"object","properties":{"chain":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}},"action":{"$ref":"#/definitions/Action"}},"required":["chain"]},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}' | ||
| ' "components":{"OrgChart":{"type":"object","properties":{"chain":{"oneOf":[{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]},{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}}]},"action":{"type":"object","properties":{"name":{"type":"string"},"context":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"object","properties":{"path":{"type":"string"},"literalString":{"type":"string"},"literalNumber":{"type":"number"},"literalBoolean":{"type":"boolean"}}}},"required":["key","value"]}}},"required":["name"]}},"required":["chain"]},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}' | ||
| ) |
There was a problem hiding this comment.
This long, single-line JSON string is difficult to read and maintain. To improve readability and reduce the chance of syntax errors, consider defining this structure as a Python dictionary and then serializing it to a JSON string using json.dumps.
client_ui_capabilities_dict = {
"inlineCatalogs": [{
"catalogId": "inline_catalog",
"components": {
"OrgChart": {
"type": "object",
"properties": {
"chain": {"oneOf": [
{"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]},
{"type": "array", "items": {"type": "object", "properties": {"title": {"type": "string"}, "name": {"type": "string"}}, "required": ["title", "name"]}}
]},
"action": {"type": "object", "properties": {
"name": {"type": "string"},
"context": {"type": "array", "items": {"type": "object", "properties": {
"key": {"type": "string"},
"value": {"type": "object", "properties": {
"path": {"type": "string"}, "literalString": {"type": "string"},
"literalNumber": {"type": "number"}, "literalBoolean": {"type": "boolean"}
}}
}, "required": ["key", "value"]}}
}, "required": ["name"]}
},
"required": ["chain"]
},
"WebFrame": {
"type": "object",
"properties": {
"url": {"type": "string"}, "html": {"type": "string"}, "height": {"type": "number"},
"interactionMode": {"type": "string", "enum": ["readOnly", "interactive"]},
"allowedEvents": {"type": "array", "items": {"type": "string"}}
}
}
}
}]
}
client_ui_capabilities_str = json.dumps(client_ui_capabilities_dict)- Validator: support multiple surfaces with different root IDs by tracking per-surface roots instead of a single global root - Validator: distinguish initial renders (beginRendering/createSurface) from incremental updates; skip root and orphan checks on updates while still catching cycles, self-refs, and duplicates - Manager: merge inline catalog components onto the base catalog instead of using inline-only; allow both inlineCatalogs and supportedCatalogIds in client capabilities - Agent: remove schema from system prompt (provided per-request via client capabilities); pass client_ui_capabilities through to catalog selection and validation - Executor: extract a2uiClientCapabilities from all DataParts (not just request parts) so UI events use the correct catalog - Client: inline OrgChart action schema (was unresolvable $ref) and add path-reference oneOf for chain (matches agent examples)
193b962 to
7b6f441
Compare
Fixes #796
Summary
The
contact_multiple_surfacessample agent fails A2UI schema validation when clients sendinlineCatalogsina2uiClientCapabilities. This PR fixes the validation pipeline end-to-end: catalog merging, multi-surface root tracking, incremental update handling, and client capability plumbing.Problems
client_ui_capabilitiesnot passed to validation — The agent validated against the default base catalog, which has no knowledge of inline components. The executor also only extracted capabilities fromrequestDataParts, notuserActionDataParts, so UI event responses were validated against the wrong catalog.contact_multiple_surfacesagent generates multiple surfaces (e.g.,contact-cardandorg-chart-view) with different root IDs. The validator only tracked the first root, causing false "Missing root component" errors on subsequent surfaces.surfaceUpdatewithout abeginRendering(i.e., updating an existing surface), the validator falsely reported missing roots and orphaned components because it expected a complete component tree every time.$ref— The client's OrgChart registration used$ref: "#/definitions/Action"which doesn't resolve in an inline catalog context.Changes
agent_sdks/python/src/a2ui/core/schema/manager.pyinlineCatalogsandsupportedCatalogIdsin client capabilities (previously raised an error)supportedCatalogIdsto select which base catalog to merge ontoagent_sdks/python/src/a2ui/core/schema/validator.py_find_root_id→_find_root_ids) returning a map ofsurfaceId → root_idbeginRendering/createSurface) vs incremental updates in each message batchref_mapcomputation outside the per-message loopsamples/agent/adk/contact_multiple_surfaces/agent.pyinclude_schema=False) since the authoritative schema arrives per-request via client capabilitiesclient_ui_capabilitiesparameter tostream()and pass it toget_selected_catalog()samples/agent/adk/contact_multiple_surfaces/agent_executor.pya2uiClientCapabilitiesfrom any DataPart that has metadata, not justrequestparts — fixes UI events using the wrong catalog for validationsamples/agent/adk/contact_multiple_surfaces/prompt_builder.pyoneOfchain, inlined action)samples/client/lit/contact/ui/custom-components/register-components.tsactionschema (was an unresolvable$ref)oneOfpath-reference variant forchainproperty (the agent examples use{ "path": "/hierarchy" }, and LLMs follow examples over schema definitions)agent_sdks/python/tests/core/schema/test_validator.pybeginRendering+ twosurfaceUpdatewith different roots)Key Design Decisions
Inline catalogs are additive, not replacements. Components from
inlineCatalogsare merged on top of the base catalog. This matches how prompt construction works — the LLM generates both standard and custom components in the same response.All inline catalogs from the client are merged. If a client sends multiple inline catalogs, all their components are merged onto the base. This supports clients that split custom components across multiple catalog definitions.
Both
inlineCatalogsandsupportedCatalogIdsare allowed simultaneously. Previously this raised aValueError. NowsupportedCatalogIdsselects the base catalog, andinlineCatalogsextend it. This is needed because clients may support a specific base catalog AND provide additional custom components.Incremental updates skip root and orphan checks. A
surfaceUpdate/updateComponentswithout a correspondingbeginRendering/createSurfacein the same message batch is treated as a partial update. Root existence and orphan detection are meaningless for partial trees. Cycles, self-references, duplicates, and dangling references are still validated.Schema removed from system prompt when
accepts_inline_catalogs=True. The correct catalog is only known after client negotiation at request time. Baking the default catalog's schema into the system prompt risks showing the LLM a schema that doesn't match the negotiated catalog (e.g., agent supports 3 catalogs, system prompt shows catalog 1, client negotiates catalog 3).OrgChart
chainkeeps theoneOfwith path-reference variant. The agent examples (multi_surface.json,org_chart.json) use"chain": { "path": "/hierarchy" }. LLMs follow examples over schema definitions, so the client schema must accept what the agent actually generates.Validation Steps
pytest tests/core/schema/test_validator.py -v)contact_multiple_surfacesagent +contactclient end-to-endPre-launch Checklist
If you need help, consider asking for advice on the discussion board.