Repository navigation
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors the custom components and restaurant finder agent samples to utilize A2uiGenerator and A2uiRequestProcessor for negotiating client capabilities, validating streamed responses, and managing renderer states. It also removes the direct jsonschema dependency. The review feedback identifies several critical issues: missing imports for InMemoryArtifactService in both agents' code, a potential NameError in the custom components executor due to an uninitialized client_ui_capabilities variable, a memory leak risk from uncleaned self._renderer_states sessions, and a type annotation mismatch for self._generators.
nan-yu
added this pull request to stack #3275
October 16, 2026 19:09
nan-yu
force-pushed
the
refactor/samples-core-per-request-processors
branch
from
October 16, 2026 19:34
30a80d5 to
f120575
Compare
nan-yu
force-pushed
the
refactor/samples-core-per-request-processors
branch
from
October 16, 2026 19:51
f120575 to
bd5ef66
Compare
nan-yu
force-pushed
the
refactor/samples-core-per-request-processors
branch
from
October 16, 2026 19:53
bd5ef66 to
f5dc548
Compare
nan-yu
force-pushed
the
refactor/samples-core-per-request-processors
branch
3 times, most recently
from
October 16, 2026 20:23
00b77d5 to
101677d
Compare
nan-yu
force-pushed
the
refactor/samples-core-per-request-processors
branch
from
October 16, 2026 22:57
101677d to
03b3802
Compare
…stom-components-example The samples kept a default processor, cached one stateful parser per session, and reshaped the client's capabilities before negotiating catalogs. They now follow the SDK's per-request model. 1. Each request gets its own `A2uiRequestProcessor`, created by the generator for its protocol version from the `a2uiClientCapabilities` the client sent, unchanged. A client that sends none gets a text answer. 2. The system prompt is built from that processor. restaurant_finder gives its examples to the generator, so the prompt snippet lists them and creating the processor checks them against the request's catalogs. 3. Responses are read with `processor.open_stream()` when streaming and `processor.parse_response()` otherwise, and turned into A2A parts with `stream_response_to_parts` and `to_a2a_parts`. On a validation error the agent asks the model to try again. 4. custom-components-example keeps a canvas `RendererState` per session, since its turns update the surfaces earlier turns created. 5. Each README gains a "How it works" section, and the unused `jsonschema` dependency is dropped. Requires the sample clients to send keyed capabilities. Signed-off-by: Nan Yu <nanyu@google.com>
…urant_finder's generators 1. custom-components-example keeps the renderer states of at most 1000 conversations, dropping the least recently used, as the samples did for their parsers before. 2. restaurant_finder's generators 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
refactor/samples-core-per-request-processors
branch
from
October 16, 2026 23:07
03b3802 to
f50fe7e
Compare
…' into refactor/samples-core-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
…omponents-example The processor now tells whether a kept renderer state fits its catalogs, so the sample drops its own _catalog_keys copy of that check. Signed-off-by: Nan Yu <nanyu@google.com>
…' into refactor/samples-core-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
…e section Signed-off-by: Nan Yu <nanyu@google.com>
…onents-example
Adds a v1.0 generator (v1.0 basic catalog) to both ADK samples, with
examples/1.0 converted from examples/0.9 and an inline_catalog_1.0.json
for the custom components. The executors read a2uiRendererCapabilities
for v1.0 and accept {"version": "v1.0", "action": ...} events.
The runners now pass the instruction through a function so ADK does not
treat {XID_Start} in the v1.0 schemas as a state variable, and the token
streams skip ADK's final aggregated SSE event, which repeated the whole
response and made v1.0 reject the second createSurface.
Signed-off-by: Nan Yu <nanyu@google.com>
…custom-components-example v1.0 lets one surface use several catalogs, so the v1.0 inline catalog no longer repeats the basic catalog. inline_catalog_1.0.json now defines only OrgChart and WebFrame, under its own catalog id. The v1.0 examples and the floor plan and send-message UIs use the basic catalog as the surface's default, and each OrgChart and WebFrame names the custom catalog in its catalogId. The floor plan shows the mix: a basic Card, Column, Text and Button around a custom WebFrame. Signed-off-by: Nan Yu <nanyu@google.com>
The v1.0 A2A extension sends the renderer capabilities under a2uiRendererCapabilities. Both ADK samples defined the key themselves. Signed-off-by: Nan Yu <nanyu@google.com>
…t's surfaces
The agent keeps a RendererState per conversation, so a second lookup or a
second send_message re-created a surface the client already showed, and
both attempts were rejected.
- The system prompt and the retry message list the surfaces in the state,
and tell the model to update them or delete one before re-creating it.
- The action UIs delete their surface before re-creating it, and closing
the floor plan sends nothing when it isn't open.
- A client without usable capabilities gets a text answer, not an apology.
- useStreaming is read as in restaurant_finder; without streaming the
response is parsed whole and sent in the final message.
- The executor finds v1.0 actions sent as a list or {"messages": [...]},
and uses A2UI_RENDERER_CAPABILITIES_KEY. stream() takes the same
parameters as restaurant_finder's.
- a2ui_examples.py derives the catalog ids, is typed with ProtocolVersion,
and drops the fallback UIs for the tracked action_confirmation.json.
- inline_catalog_1.0.json gets a $id equal to its catalogId.
Signed-off-by: Nan Yu <nanyu@google.com>
…ient holds
A streamed attempt that failed left its surfaces on the client, and the
retry sent createSurface for them again, as did a second search in the
same conversation. v1.0 renderers reject both.
- The agent remembers each conversation's surfaces and sends deleteSurface
before a response creates one of them again (v0.9 and later).
- Before a retry it deletes the surfaces the failed attempt created.
- A response with no A2UI messages is retried again, as before the
per-request refactor.
- A client without usable capabilities gets a text answer.
- The executor finds v1.0 actions sent as a list or {"messages": [...]},
and uses A2UI_RENDERER_CAPABILITIES_KEY.
Signed-off-by: Nan Yu <nanyu@google.com>
Two lookups and two send_message actions in one custom-components session, a retry after a partly streamed failure, and a restaurant_finder streaming retry and repeat search, for v0.9 and v1.0, against a mirror of the renderer's surfaces. Also uses to_version_number for the examples paths. Signed-off-by: Nan Yu <nanyu@google.com>
…' into refactor/samples-core-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
…ents.py Signed-off-by: Nan Yu <nanyu@google.com>
…-core-per-request-processors Signed-off-by: Nan Yu <nanyu@google.com>
…-core-per-request-processors
nan-yu
removed this pull request from stack #3275
October 20, 2026 17:42
nan-yu
added this pull request to stack #3340
October 20, 2026 17:47
…-core-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. Needs #3266, since these agents no longer reshape the client's capabilities.
restaurant_finder and custom-components-example kept a default processor, cached one stateful parser per session, and reshaped the client's capabilities before negotiating catalogs. They now follow the per-request model from #3269 and #3270, and speak A2UI v1.0 as well as v0.8 and v0.9.
A2uiRequestProcessor, created by the generator for its protocol version from the capabilities the client sent, unchanged. A client that activates the A2UI extension but sends no capabilities, or none the agent can use, gets a text answer from the text runner.useStreamingfrom the request. Responses are read withprocessor.open_stream()when streaming andprocessor.parse_response()otherwise, and turned into A2A parts withstream_response_to_partsandto_a2a_parts. When streaming, the parts go out inworkingstatus updates and the final message has none; otherwise the final message holds them. On a validation error, or in restaurant_finder a response without A2UI messages, the agent asks the model to try again.RendererStateper session (agent.py), since its turns update the surfaces earlier turns created. It starts a new state whenprocessor.accepts_state(state)says the client's catalogs changed.jsonschemadependency is dropped (with the matchinguv.lockentries).Surfaces that the client already holds
From v0.9 on, creating a surface the renderer already holds is an integrity error, and both samples use fixed surface ids.
deleteSurfacebefore it creates one again. Without this, a second lookup or a second "send message" in one conversation failed on both attempts. The action UIs delete their surface before they create it again, and closing the floor plan sends nothing when it isn't open.deleteSurfacebefore a response creates one of them again (a second search), and before a retry it deletes the surfaces that the failed streamed attempt created. Text that a failed streamed attempt already sent can't be taken back.v1.0 support
a2uiRendererCapabilities(A2UI_RENDERER_CAPABILITIES_KEYina2ui.constants, added in docs(python/agent): document per-request processors, RendererState and streaming #3271), as the v1.0 A2A extension defines; v0.8 and v0.9 keepa2uiClientCapabilities. Both acceptactionevents sent as v0.9 or v1.0, as a single message, a list of messages, or the{"messages": [...]}wrapper; custom-components-example previously handled only v0.8userAction. The agents still send each outbound A2UI message in its own data part, as the SDK'sto_a2a_partsdoes."version": "v1.0", bind data with{"@path": ...}, and droptheme. Two differences follow from v1.0: the v1.0 basicTexthas no h1 to h5 variants, so headings are Markdown (## ...), and component ids use_instead of-, since the SDK requires v1.0 ids to be UAX Initial commit for a2ui_restaurant_finder and a generic a2UI extension. #31 identifiers.$id), rather than repeating the basic catalog as the v0.9 one must. The v1.0 surfaces use the basic catalog as their default, and each OrgChart and WebFrame names the custom catalog in itscatalogId. examples/1.0/floor_plan.json shows it: a basicCard,Column,TextandButtonaround a customWebFrame. The floor plan UI that a2ui_examples.py builds for the view_location action does the same; the send-message UI (examples/1.0/action_confirmation.json) uses only the basic catalog.{XID_Start}in the v1.0 prompt as a session state variable and failed every request withKeyError. The instruction is now passed as a function, which skips state injection, and restaurant_finder's"expression": "{expression}"workaround is gone.createSurface, so the first attempt always failed. The aggregated event is now skipped, which also fixes doubled text in restaurant_finder's text-only answers..../a2ui/v1.0inX-A2A-Extensionsand senda2uiRendererCapabilitiesin the message metadata.Testing
uv run pytest samples/agent/adk/tests, which validates the v1.0 examples too. The new test_sample_agents.py replays the examples through each agent with a fake ADK runner and mirrors the renderer's surfaces: two lookups and two send_message actions in one custom-components session, a retry after a partly streamed failure, and a restaurant_finder streaming retry and repeat search, for v0.9 and v1.0, checking that nocreateSurfacereaches a surface the client holds. It also checks the action shapes the executors accept.uv run pytest python/a2ui_agent/tests.parse_response(custom-components-example's later-turn example against the state its earlier turn left).gemini-3.6-flash: every returned part statedv1.0and passedvalidate_payload. This was before the surface handling above, which was tested only with the fake runner.pyink --checkandruff checkon the changed files.