Skip to content

refactor(samples): per-request processors in the Gemini Enterprise samples - #3273

Open
nan-yu wants to merge 11 commits into
docs/python-agent-renderer-statefrom
refactor/samples-gemini-enterprise-per-request-processors
Open

nan-yu wants to merge 11 commits into
docs/python-agent-renderer-statefrom
refactor/samples-gemini-enterprise-per-request-processors

Conversation

@nan-yu

@nan-yu nan-yu commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #3271. The unkeyed v0.8 capabilities need #3265, which has merged.

The v0.8 Agent Engine and Cloud Run samples and the v0.9 sample now follow the per-request model from #3269 and #3270, as the core samples do.

  1. Each request gets its own A2uiRequestProcessor, negotiated from the capabilities the client sent, passed through unchanged. Gemini Enterprise doesn't name the catalogs it renders, so a request without capabilities gets GEMINI_ENTERPRISE_CAPABILITIES. In the v0.8 samples these use the unkeyed shape the v0.8 specification defines.
  2. The system prompt is built from that processor, and responses are read with open_stream or parse_response 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. It doesn't retry an empty response, or a streamed response that already sent parts, and sends an error message instead. In v0_9/agent.py the final item holds the whole response, also when streaming, because Gemini Enterprise renders only the final message's parts, and the executor sends that item's parts.
  3. The v0.9 sample builds a Material catalog and the Gemini Enterprise composite catalog in catalogs.py, instead of using the basic catalog. Its examples use the Material catalog. The README explains how the agent uses the SDK.
  4. The Agent Engine executor activates the A2UI extension when the client requests it and otherwise answers with v0.8, as the v0.9 executor does, instead of a hardcoded version.
  5. The pins move to a2ui-agent-sdk>0.9.0 and a2ui-a2a>0.1.0, since 0.9.0 and 0.1.0 have no open_stream or to_a2a_parts. a2ui-core is listed directly, since the samples import it. The unused jsonschema dependency is dropped.

Deployment

These SDK releases aren't on PyPI yet (a2ui-a2a has no release there at all), so the samples run on the SDKs in this repository through [tool.uv.sources] path entries, and the v0.9 uv.lock records them as editable paths. A deployment can't reach those paths, so:

  1. build_sdk_wheels.sh builds wheels of a2ui-core, a2ui-agent-sdk and a2ui-a2a. With --use-wheels it points a project's sources at them and relocks it.
  2. Both deploy.sh scripts (v0.9, v0.8 Cloud Run) deploy a temporary copy of the sample that holds the wheels, with its sources pointed at them and a fresh uv.lock.
  3. The Agent Engine deploy.py builds the wheels into wheels/ and lists them in both requirements and extra_packages. It also requires a2a-sdk[http-server], since the A2A agent template's set_up() needs sse-starlette.

Once the SDKs are released, the path sources and the wheel step can go.

Testing

  1. uv sync --all-packages, and import agent, agent_executor in each sample. uv run pytest samples/agent/adk/tests passes. ruff (0.15.20) and pyink are clean on the changed Python files.
  2. A fake ADK runner (no LLM) replays scripted output through the v0.9 agent and the real executor: an empty response sends the error text without a retry, a streamed response that fails after a valid block isn't retried and sends no duplicate createSurface, the SSE aggregated event isn't read twice, and all 13 examples come back as valid parts, streaming and not.
  3. Deploy packaging, offline: the staged Cloud Run copy of each sample, copied outside the repository, passes uv lock --check, uv sync --locked and uv export plus pip install into fresh venvs, and imports the agent with the local SDK wheels. For Agent Engine, deploy.main() runs with Vertex AI faked out. Its extra_packages are packed the way the Vertex AI SDK packs them, and its requirements install with pip into a fresh Python 3.13 venv, where the agent imports.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Gemini Enterprise A2UI agent samples (v0.8 and v0.9) to integrate with the latest a2ui-agent-sdk and a2ui-a2a libraries, refactoring the agent, executor, and prompt building logic to support dynamic catalog negotiation and validation. The review feedback highlights critical issues that need to be addressed: consecutive user messages in the session history during retries can violate Gemini API role requirements and cause 400 errors; direct dictionary lookups on unsupported UI versions can trigger KeyErrors; and the retry loops do not catch general exceptions, which could crash the handlers on transient runner or API errors.

Comment thread samples/community/agent/adk/gemini_enterprise/v0_8/agent_engine/agent.py Outdated
Comment thread samples/community/agent/adk/gemini_enterprise/v0_9/agent.py
Comment thread samples/community/agent/adk/gemini_enterprise/v0_9/agent.py
@nan-yu
nan-yu force-pushed the docs/python-agent-renderer-state branch from 0df8f19 to cf5db13 Compare October 16, 2026 19:34
@nan-yu
nan-yu force-pushed the refactor/samples-gemini-enterprise-per-request-processors branch from 084621d to 2c45a66 Compare October 16, 2026 19:34
@nan-yu
nan-yu force-pushed the docs/python-agent-renderer-state branch from cf5db13 to 6e471f6 Compare October 16, 2026 19:51
@nan-yu
nan-yu force-pushed the refactor/samples-gemini-enterprise-per-request-processors branch 2 times, most recently from 4ea0308 to e956965 Compare October 16, 2026 19:53
@nan-yu
nan-yu force-pushed the docs/python-agent-renderer-state branch from 6e471f6 to 74fb3d8 Compare October 16, 2026 19:53
@nan-yu
nan-yu force-pushed the refactor/samples-gemini-enterprise-per-request-processors branch 3 times, most recently from f9f8e20 to 3a1b670 Compare October 16, 2026 20:23
@nan-yu
nan-yu force-pushed the docs/python-agent-renderer-state branch from 229788b to 7b4baed Compare October 16, 2026 22:57
@nan-yu
nan-yu force-pushed the refactor/samples-gemini-enterprise-per-request-processors branch from 5dae2a5 to 7d075de Compare October 16, 2026 22:57
…mples

The v0.8 Agent Engine and Cloud Run samples and the v0.9 sample now
follow the SDK's per-request model, as the core samples do.

1. Each request gets its own `A2uiRequestProcessor`, negotiated from the
   capabilities the client sent, passed through unchanged. Gemini
   Enterprise doesn't name the catalogs it renders, so a request without
   capabilities gets `GEMINI_ENTERPRISE_CAPABILITIES`. In the v0.8
   samples these use the unkeyed shape the v0.8 specification defines.
2. The system prompt is built from that processor, and responses are read
   with `open_stream` or `parse_response` 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.
3. The v0.9 sample builds a Material catalog and the Gemini Enterprise
   composite catalog in `catalogs.py`, instead of using the basic
   catalog. Its examples use the Material catalog.
4. The pins move to `a2ui-agent-sdk>=0.9.0` and `a2ui-a2a>=0.1.0`, in
   `pyproject.toml` and the Agent Engine `deploy.py`, and the unused
   `jsonschema` dependency is dropped.

Requires the SDK to read unkeyed v0.8 capabilities.

Signed-off-by: Nan Yu <nanyu@google.com>
When the model returns no text, the v0.8 samples sent another user
message to retry. That adds a second user turn with no model turn
between them, so they now stop and send the text-only error instead.

Signed-off-by: Nan Yu <nanyu@google.com>
…epository

The samples now use a2ui-a2a and APIs of a2ui-agent-sdk that aren't on
PyPI yet, so their dependencies didn't resolve. Each sample points
a2ui-a2a, a2ui-agent-sdk and a2ui-core at the packages in this
repository with [tool.uv.sources] until they are published. The v0.9
lock file follows.

Signed-off-by: Nan Yu <nanyu@google.com>
@nan-yu
nan-yu force-pushed the docs/python-agent-renderer-state branch from 7b4baed to eebbda5 Compare October 16, 2026 23:07
@nan-yu
nan-yu force-pushed the refactor/samples-gemini-enterprise-per-request-processors branch from 7d075de to 106b29a Compare October 16, 2026 23:07
…' into refactor/samples-gemini-enterprise-per-request-processors

Signed-off-by: Nan Yu <nanyu@google.com>
- An empty model response no longer becomes a final message without
  parts. The agent stops without retrying and sends the error text, as
  the v0.8 samples do.
- The agent's final item holds the whole response again, also when
  streaming, since Gemini Enterprise renders only the parts of the final
  message. The executor sends the final item's parts instead of
  collecting the parts of every item, so the parts of a failed streamed
  attempt aren't sent along with the retry.
- A streamed response that fails after sending parts isn't retried,
  since the client may already show those parts.
- In SSE mode, the final aggregated ADK event, which repeats the
  partial text, is skipped.
- The examples are read from the sample directory rather than the
  working directory, in the agent and in `prompt_builder.py`.
- The executor logs the protocol version string instead of the enum
  repr.

Signed-off-by: Nan Yu <nanyu@google.com>
…ecutor

The executor activates the A2UI extension when the client requests it,
and otherwise answers with A2UI v0.8, as the v0.9 executor does, instead
of a hardcoded version. This removes the unused imports, and the unused
`contact_agent` in deploy.py.

Signed-off-by: Nan Yu <nanyu@google.com>
… SDKs

The samples need SDK releases that aren't on PyPI yet. Their
`[tool.uv.sources]` path entries point outside the sample directory,
which Cloud Run's `--source .` doesn't upload, and the Agent Engine
deploy.py installed `a2ui-a2a` from PyPI, where it doesn't exist.

- `build_sdk_wheels.sh` builds wheels of a2ui-core, a2ui-agent-sdk and
  a2ui-a2a from this repository. With `--use-wheels` it also points a
  project's sources at them and relocks it.
- Both deploy.sh scripts deploy a temporary copy of the sample with the
  wheels, its sources pointed at them and a fresh uv.lock. The path
  sources stay for local development.
- deploy.py builds the wheels into `wheels/` and lists them in both
  `requirements` and `extra_packages`.
- The pins move to `a2ui-agent-sdk>0.9.0` and `a2ui-a2a>0.1.0`. 0.9.0
  and 0.1.0 have no `open_stream` or `to_a2a_parts`, and the
  `>` pins also admit this repository's dev builds. `a2ui-core` is
  listed directly, since the samples import it and uv only applies the
  source of a direct dependency.
- The Agent Engine sample requires `a2a-sdk[http-server]`, locally and
  on the engine, since the A2A agent template's `set_up()` needs
  `sse-starlette`.
- The Agent Engine README names deploy.py instead of main.py and runs
  `uv sync` instead of `uv sync --locked`, since the sample has no
  uv.lock.

Signed-off-by: Nan Yu <nanyu@google.com>
The v0.9 README says when the agent doesn't retry and that its final
item holds the whole response. The overlong lines in the v0.8 and v0.9
READMEs are rewrapped to 80 columns.

Signed-off-by: Nan Yu <nanyu@google.com>
…' into refactor/samples-gemini-enterprise-per-request-processors

Signed-off-by: Nan Yu <nanyu@google.com>
…ls.sh

Signed-off-by: Nan Yu <nanyu@google.com>
…-gemini-enterprise-per-request-processors

Signed-off-by: Nan Yu <nanyu@google.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant