Repository navigation
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors A2UI negotiation across multiple agents (file_upload_summarizer, mcp_app_proxy, and rizzcharts) to use a single runner that dynamically adapts its instructions based on session state, while upgrading the a2ui-agent-sdk dependency and removing jsonschema. The review feedback correctly identifies a copy-paste bug in the rizzcharts agent's name initialization, a type mismatch in the examples path dictionary annotation, and an unnecessary performance bottleneck caused by validating static examples on every request.
nan-yu
force-pushed
the
refactor/samples-adk-toolset-per-request-processors
branch
from
October 16, 2026 19:34
cd889cb to
f1c1184
Compare
nan-yu
force-pushed
the
docs/python-agent-renderer-state
branch
from
October 16, 2026 19:34
0df8f19 to
cf5db13
Compare
nan-yu
force-pushed
the
refactor/samples-adk-toolset-per-request-processors
branch
from
October 16, 2026 19:51
f1c1184 to
c3dbf78
Compare
nan-yu
force-pushed
the
docs/python-agent-renderer-state
branch
2 times, most recently
from
October 16, 2026 19:53
6e471f6 to
74fb3d8
Compare
nan-yu
force-pushed
the
refactor/samples-adk-toolset-per-request-processors
branch
5 times, most recently
from
October 16, 2026 22:57
ca5ae15 to
ee487d1
Compare
nan-yu
force-pushed
the
docs/python-agent-renderer-state
branch
from
October 16, 2026 22:57
229788b to
7b4baed
Compare
rizzcharts, mcp_app_proxy and file_upload_summarizer send A2UI through `SendA2uiToClientToolset`. They built their processor, prompt and catalogs once, at startup, from a default renderer. 1. The executor creates an `A2uiRequestProcessor` for each request from the `a2uiClientCapabilities` the client sent, unchanged, and writes what the request needs into the session state: the processor's format rules and the negotiated catalogs under `system:a2ui_catalogs`, plus the preferred catalog and its examples in rizzcharts. 2. The instruction is built from the session state on each request, and the toolset and the A2UI event converter read the catalogs from the same keys, so the prompt, the tool's schema and its validation follow the request's catalogs. A client that sends no capabilities, or names none of the agent's catalogs, gets text answers. 3. Each README explains how the agent negotiates A2UI. The rizzcharts README's `cd` path points at the sample's current location. 4. The samples, and the orchestrator that runs them, pin `a2ui-agent-sdk>=0.9.0`, and the unused `jsonschema` dependency is dropped. Signed-off-by: Nan Yu <nanyu@google.com>
The executor now loads the examples with validate=True against the request's catalogs, and the v0.9 examples named the old GitHub URL, which no catalog declares. They now name the id that the v0.9 rizzcharts catalog declares. Signed-off-by: Nan Yu <nanyu@google.com>
…es paths 1. The rizzcharts agent and runner used the name "mcp_app_proxy_agent", copied from another sample. They now use "rizzcharts_agent". 2. The examples paths are keyed by ProtocolVersion in their annotation, as they are in the code. Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu
force-pushed
the
docs/python-agent-renderer-state
branch
from
October 16, 2026 23:07
7b4baed to
eebbda5
Compare
nan-yu
force-pushed
the
refactor/samples-adk-toolset-per-request-processors
branch
from
October 16, 2026 23:07
ee487d1 to
4c414a9
Compare
…' into refactor/samples-adk-toolset-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
…logs A2uiPartConverter defaulted its version to v0.8, so A2uiEventConverter and SendA2uiToClientToolset.get_part_converter, which pass no version, tagged v1.0 parts with the deprecated MIME type. The converter now defaults to the protocol version of its catalogs. Signed-off-by: Nan Yu <nanyu@google.com>
Add a v1.0 generator to the rizzcharts, mcp_app_proxy and file_upload_summarizer samples, with v1.0 catalogs mirroring the 0.9 ones and v1.0 versions of the rizzcharts examples. The executors read the renderer capabilities from a2uiRendererCapabilities for v1.0, and the mcp_app_proxy and file_upload_summarizer tools write their messages in the negotiated version. Signed-off-by: Nan Yu <nanyu@google.com>
…the ADK toolset samples v1.0 mixes catalogs on one surface, so the v1.0 custom catalogs hold only their custom components, and v1.0 surfaces take the basic catalog as their default while each custom component names its own catalog in catalogId. rizzcharts passes every negotiated v1.0 catalog to the prompt and the toolset, and file_upload_summarizer registers the basic catalog in its v1.0 generator. mcp_app_proxy's surfaces use only its own components, so its v1.0 catalog stays their default and drops the unused Column. v0.8 and v0.9 are unchanged. Signed-off-by: Nan Yu <nanyu@google.com>
…G bullet The Unreleased section said both that A2uiPartConverter defaults its version to the protocol version of its catalogs and that it defaults to ProtocolVersion.V0_8. Keep the existing bullet, which now states the new default, the v0.8 fallback without catalogs and the MIME type it sets. Signed-off-by: Nan Yu <nanyu@google.com>
v1.0 renames the client to the renderer, and the message metadata key of its capabilities to a2uiRendererCapabilities. a2ui.constants only had the v0.8/v0.9 key, so each sample hardcoded the v1.0 one. Signed-off-by: Nan Yu <nanyu@google.com>
…s relatively
The rizzcharts examples of every version set the data model to flat keys
such as "chart.items[0].label" and bound "/chart.items", a JSON Pointer
to a key that doesn't exist, so the chart, the map and the lists rendered
empty, and the examples taught the model the same pattern. The components
that a List template repeats also bound absolute paths ("/label",
"${/name}"), which resolve from the root rather than the list item.
The v0.9 and v1.0 examples now send a nested data model, bind
"/chart/items" and the like, and bind template items relative to the
item ("label", "${name}"). v0.8 can't nest more than one level in a
dataModelUpdate, so the v0.8 examples send one dataModelUpdate per level.
The v0.8 renderer of the Angular orchestrator client resolves the new
paths too, and now also shows the item labels of the basic chart.
test_rizzcharts_examples.py validates each example against exactly the
catalogs its surfaces use, and checks that every binding, inside
templates too, resolves in the data model the example sends. The prompt
no longer says that every example holds three messages.
Signed-off-by: Nan Yu <nanyu@google.com>
… the catalogs A v1.0 client that named only the rizzcharts catalog got a processor, and the per-request load_examples(validate=True) then failed outside any try: the rizzcharts examples default the surface to the basic catalog, so the A2A task failed instead of falling back to text. The examples were also read from disk and validated on every request. The agent now loads and validates each catalog's examples once at startup, against the catalogs their surfaces use, and gives a request the examples of the client's most preferred catalog whose examples use only catalogs the client named. A request that no examples fit, or whose format rules can't be built, is answered in text. The executor also stops appending a session event on every request: the state follows from the negotiated version and the client's capabilities, so a request that negotiates as the previous one did reuses the state. It reads the v1.0 capabilities key from a2ui.constants, and takes override from typing_extensions, since typing.override needs Python 3.12 and the sample supports 3.10 (the README said 3.9). Signed-off-by: Nan Yu <nanyu@google.com>
…render The v1.0 uploader surface defaults to the basic catalog and its FileUpload names the file upload catalog, but a v1.0 client that named only one of the two still got A2UI, and the tool sent the surface unvalidated (the event converter bypasses validation for tool responses). The v0.9 surface also failed validation: its Button checks 'required', a function that the v0.9 file upload catalog didn't define. A request whose client lacks a catalog that the uploader surface uses is now answered in text. The tools validate their messages against the negotiated catalogs and return an error to the model instead of an invalid surface. The v0.9 file upload catalog gains the 'required' function of the v0.9 basic catalog, which the Angular file upload client already implements under that catalog. The executor also stops appending a session event on every request, drops the examples it always wrote empty, reads the v1.0 capabilities key from a2ui.constants and takes override from typing_extensions, since typing.override needs Python 3.12 and the sample supports 3.10. Signed-off-by: Nan Yu <nanyu@google.com>
The agent advertised and negotiated v0.8, but its tools write v0.9 messages for anything but v1.0, so a v0.8 client got messages it can't read. The agent now holds generators for v0.9 and v1.0 only, so a v0.8 client gets text answers, and the unused v0.8 catalog is removed. The executor also stops appending a session event on every request, drops the examples it always wrote empty (with its TODO), reads the v1.0 capabilities key from a2ui.constants and takes override from typing_extensions, since typing.override needs Python 3.12 and the sample supports 3.10 (the README said 3.9). Signed-off-by: Nan Yu <nanyu@google.com>
Drive each executor's _prepare_session with an A2UI request and the client's capabilities, without a model, and check the session state: rizzcharts and file_upload_summarizer answer in text when the v1.0 client lacks a catalog their surfaces use, rizzcharts picks the examples of the preferred catalog and adds a session event only when the negotiation changes, the file upload tools send valid surfaces and return an error instead of an invalid one, and the MCP app proxy answers v0.8 in text. Signed-off-by: Nan Yu <nanyu@google.com>
…' into refactor/samples-adk-toolset-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com> # Conflicts: # python/a2ui_agent/CHANGELOG.md # python/a2ui_agent/src/a2ui/constants.py
…lper __main__.py still imported get_a2ui_examples, which the per-request executor no longer defines, so `uv run .` failed with ImportError. It used none of the three helpers it imported. Signed-off-by: Nan Yu <nanyu@google.com>
… tests Signed-off-by: Nan Yu <nanyu@google.com>
…onftest.py Signed-off-by: Nan Yu <nanyu@google.com>
…-adk-toolset-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #3271.
rizzcharts, mcp_app_proxy and file_upload_summarizer send A2UI through
SendA2uiToClientToolset. They built their processor, prompt and catalogs once, at startup, from a default renderer. They now negotiate per request, and speak A2UI v1.0 as well.A2uiRequestProcessorfor each request from the capabilities the client sent, unchanged, and writes what the request needs into the session state: the processor's format rules and the negotiated catalogs undersystem:a2ui_catalogs, plus the preferred catalog and its examples in rizzcharts (agent_executor.py). The state follows from the negotiated version and the client's capabilities alone, so the executor records a digest of them (system:a2ui_negotiation) and adds a session event only when they change, instead of on every request.cdpath points at the sample's current location, and the READMEs ask for Python 3.10, as thepyproject.tomlfiles do.a2ui-agent-sdk>=0.9.0, as does the orchestrator sample, and the unusedjsonschemadependency is dropped (with the matchinguv.lockentries). The three executors takeoverridefromtyping_extensions, sincetyping.overrideneeds Python 3.12, so the samples depend ontyping-extensions>=4.4.0.v1.0 support
a2uiRendererCapabilities, as the v1.0 A2A extension defines; v0.8 and v0.9 keepa2uiClientCapabilities. The key isA2UI_RENDERER_CAPABILITIES_KEYina2ui.constants, next toA2UI_CLIENT_CAPABILITIES_KEY; docs(python/agent): document per-request processors, RendererState and streaming #3271 adds it.instructions: Canvas, Chart and GoogleMap in rizzcharts_catalog_definition.json, FileUpload in file_upload_catalog.json, and the MCP App components in mcp_app_catalog.json, which drops the v0.9Columnthat no surface uses. A v1.0 surface takes the basic catalog as its default (createSurface.catalogId), and each custom component names its catalog in its owncatalogId; the basic catalog also resolves file_upload'srequiredcheck. A v1.0 client names both catalogs insupportedCatalogIds, and the rizzcharts executor then passes both to the prompt and the toolset's validation; a basic-only client gets the basic catalog and the standard examples, and a client that names only the rizzcharts catalog gets text answers, since a v1.0 rizzcharts surface defaults to the basic catalog. file_upload_summarizer's v1.0 generator registers the basic catalog next to its own, and a v1.0 client that names only one of the two gets text answers. mcp_app_proxy's surfaces use only MCP App components, so they keep that catalog as their default. v0.8 and v0.9 are unchanged: a surface there uses one catalog, so their custom catalogs keep the copied basic components.{"@path": ...}bindings and notheme. The rizzcharts*catalog ones use the basic catalog as the surface default, and their Canvas, Chart and GoogleMap name the rizzcharts catalog; the standard_catalog ones use the basic catalog alone. As with the basic catalog's v1.0Text, headings are Markdown, and component ids use*instead of-."chart.items[0].label"and bound/chart.items, which names no data, and the components aListtemplate repeats bound absolute paths such as/label. They now send a nested data model, bind/chart/itemsand the like, and bind template items relative to the item (label,${name}). v0.8 can't nest more than one level in adataModelUpdate, so the v0.8 examples send one per level; the v0.8 renderer of the Angular orchestrator client resolves the new paths.system:a2ui_version), and their tools write v1.0 messages (@path,@call, the v1.0 catalog id) for a v1.0 request. The event converter sends tool responses on unvalidated, so the file upload tools validate their messages against the negotiated catalogs and return an error to the model instead of a surface the client can't render. The v0.9 file upload catalog gains therequiredfunction of the v0.9 basic catalog, which the v0.9 uploader's button check calls and the Angular file upload client already implements.A2uiPartConverterdefaulted to v0.8, and the toolset and event converter pass no version, so v1.0 parts went out with the deprecatedapplication/json+a2uiMIME type. The version now defaults to the protocol version of the catalogs, so v0.8 and v0.9 output is unchanged.a2uiRendererDataModel.Testing
uv run pytest(the whole suite), including a new part converter test and two new sample test modules: test_rizzcharts_examples.py validates each rizzcharts example against exactly the catalogs its surfaces use and checks that every binding resolves in the data model it sends, and test_community_adk_toolset_samples.py drives each executor's_prepare_sessionwithout a model: the text fallbacks of points 6 and 7, the rizzcharts example choice, the session events of point 1, and the file upload tools' payloads and errors.gemini-3.6-flash(at 651616f, before the data model change of point 9), from a client naming both catalogs: it returnedcreateSurfaceon the basic catalog,updateComponentswith Canvas and Chart naming the rizzcharts catalog, andupdateDataModel, asapplication/a2ui+json.ruff check,pyinkand Prettier on the changed files;uv run prompt_builder.py 0.8|0.9|1.0for rizzcharts.