feat(agent_sdk): add A2UIOutputMode enum for unified prompt generation#1466
feat(agent_sdk): add A2UIOutputMode enum for unified prompt generation#1466jswortz wants to merge 1 commit into
Conversation
Introduces A2UIOutputMode.TEXT and A2UIOutputMode.TOOL to generate_system_prompt(), preventing accidental double-injection of schemas when using SendA2uiToClientToolset. Addresses google#1289
|
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
This pull request introduces an output_mode parameter to A2uiSchemaManager.generate_system_prompt, enabling a TOOL mode that uses specialized workflow rules and avoids redundant schema injection. The changes include a new A2UIOutputMode enum and comprehensive unit tests. Review feedback recommends defining the tool name as a constant for maintainability, addressing an API signature mismatch with the InferenceStrategy base class, and reducing package coupling by removing specific class references from docstrings and log messages.
| TOOL_WORKFLOW_RULES = """ | ||
| The generated response MUST follow these rules: | ||
| - To send UI to the user, call the `send_a2ui_json_to_client` tool with valid A2UI JSON. | ||
| - Each response SHOULD include at least one tool call to render UI components. | ||
| - You may include brief conversational text alongside tool calls for context. | ||
| - The JSON payload passed to the tool MUST be a single, raw JSON object (usually a list of A2UI messages) and MUST validate against the provided A2UI JSON SCHEMA. | ||
| - Top-Down Component Ordering: Within the `components` list of a message: | ||
| - The 'root' component MUST be the FIRST element. | ||
| - Parent components MUST appear before their child components. | ||
| This specific ordering allows the streaming parser to yield and render the UI incrementally as it arrives. | ||
| """ |
There was a problem hiding this comment.
The tool name send_a2ui_json_to_client is hardcoded within the workflow rules string. Defining this as a constant improves maintainability and ensures consistency with DEFAULT_WORKFLOW_RULES, which already uses constants for its tags. This also makes it easier for other components to reference the tool name without re-typing the string.
| TOOL_WORKFLOW_RULES = """ | |
| The generated response MUST follow these rules: | |
| - To send UI to the user, call the `send_a2ui_json_to_client` tool with valid A2UI JSON. | |
| - Each response SHOULD include at least one tool call to render UI components. | |
| - You may include brief conversational text alongside tool calls for context. | |
| - The JSON payload passed to the tool MUST be a single, raw JSON object (usually a list of A2UI messages) and MUST validate against the provided A2UI JSON SCHEMA. | |
| - Top-Down Component Ordering: Within the `components` list of a message: | |
| - The 'root' component MUST be the FIRST element. | |
| - Parent components MUST appear before their child components. | |
| This specific ordering allows the streaming parser to yield and render the UI incrementally as it arrives. | |
| """ | |
| A2UI_TOOL_NAME = "send_a2ui_json_to_client" | |
| TOOL_WORKFLOW_RULES = f""" | |
| The generated response MUST follow these rules: | |
| - To send UI to the user, call the `{A2UI_TOOL_NAME}` tool with valid A2UI JSON. | |
| - Each response SHOULD include at least one tool call to render UI components. | |
| - You may include brief conversational text alongside tool calls for context. | |
| - The JSON payload passed to the tool MUST be a single, raw JSON object (usually a list of A2UI messages) and MUST validate against the provided A2UI JSON SCHEMA. | |
| - Top-Down Component Ordering: Within the `components` list of a message: | |
| - The 'root' component MUST be the FIRST element. | |
| - Parent components MUST appear before their child components. | |
| This specific ordering allows the streaming parser to yield and render the UI incrementally as it arrives. | |
| """ |
| include_schema: bool = False, | ||
| include_examples: bool = False, | ||
| validate_examples: bool = False, | ||
| output_mode: A2UIOutputMode = A2UIOutputMode.TEXT, |
There was a problem hiding this comment.
The addition of the output_mode parameter to generate_system_prompt causes a signature mismatch with the InferenceStrategy abstract base class. While Python's default arguments prevent immediate runtime breakage, it creates an inconsistency in the abstract interface. It is recommended to update the InferenceStrategy base class to include this parameter (or a generic **kwargs) to maintain a consistent API across all strategy implementations.
| output_mode: Controls how A2UI instructions are delivered. TEXT (default) | ||
| injects the schema into the system prompt. TOOL skips schema and | ||
| example injection (handled by SendA2uiToClientToolset) and uses | ||
| tool-oriented workflow rules. |
There was a problem hiding this comment.
The docstring for output_mode explicitly references SendA2uiToClientToolset, which creates a conceptual dependency on the adk package. To maintain better modularity, consider a more generic description of the mode's behavior.
| output_mode: Controls how A2UI instructions are delivered. TEXT (default) | |
| injects the schema into the system prompt. TOOL skips schema and | |
| example injection (handled by SendA2uiToClientToolset) and uses | |
| tool-oriented workflow rules. | |
| output_mode: Controls how A2UI instructions are delivered. TEXT (default) | |
| injects the schema into the system prompt. TOOL skips schema and | |
| example injection (as they are expected to be handled by the toolset) | |
| and uses tool-oriented workflow rules. |
| logger.warning( | ||
| "output_mode=TOOL overrides include_schema and include_examples " | ||
| "to False. Schema and examples are injected by " | ||
| "SendA2uiToClientToolset.process_llm_request() instead." | ||
| ) |
There was a problem hiding this comment.
This warning message explicitly references SendA2uiToClientToolset.process_llm_request(), creating a tight coupling between the a2ui.schema package and the a2ui.adk package. The schema package should ideally remain independent of specific ADK implementations. Consider using a more generic message.
| logger.warning( | |
| "output_mode=TOOL overrides include_schema and include_examples " | |
| "to False. Schema and examples are injected by " | |
| "SendA2uiToClientToolset.process_llm_request() instead." | |
| ) | |
| logger.warning( | |
| "output_mode=TOOL overrides include_schema and include_examples " | |
| "to False. In this mode, schema and examples are expected to be " | |
| "provided by the toolset implementation (e.g., via tool-specific " | |
| "instructions)." | |
| ) |
| TOOL: Schema is provided via SendA2uiToClientToolset's | ||
| process_llm_request(). The LLM calls the send_a2ui_json_to_client | ||
| tool to deliver A2UI JSON. When using this mode, | ||
| generate_system_prompt() automatically skips schema and example | ||
| injection to prevent double-injection. |
There was a problem hiding this comment.
The docstring for TOOL mode references specific classes in the a2ui.adk package. To maintain package independence and follow clean architecture principles, consider describing the behavior more generically in terms of how the prompt is constructed and how the LLM is expected to respond.
| TOOL: Schema is provided via SendA2uiToClientToolset's | |
| process_llm_request(). The LLM calls the send_a2ui_json_to_client | |
| tool to deliver A2UI JSON. When using this mode, | |
| generate_system_prompt() automatically skips schema and example | |
| injection to prevent double-injection. | |
| TOOL: Schema and examples are omitted from the system prompt, as they are | |
| expected to be provided by the toolset (e.g., via tool-specific | |
| instructions). The LLM is instructed to use a tool to deliver A2UI JSON. | |
| When using this mode, generate_system_prompt() automatically skips | |
| schema and example injection to prevent double-injection. |
This comment was marked as resolved.
This comment was marked as resolved.
|
@jswortz Thats for the PR! The implementation is solid and the test coverage is comprehensive. After reviewing both #1466 and #1465 together, I want to propose a design refinement that addresses the parameter naming issue and consolidates both concerns cleanly. Current State
These solve two orthogonal problems:
Issue with
|
Summary
A2UIOutputModeenum (TEXT,TOOL) to unify system instruction generationoutput_modeparameter toA2uiSchemaManager.generate_system_prompt()TOOL_WORKFLOW_RULESconstant with tool-calling oriented workflow rulesAddresses #1289
Motivation
Developers using
SendA2uiToClientToolset(tool-based output) often accidentally double-inject the A2UI schema by also passinginclude_schema=Truetogenerate_system_prompt(). The toolset'sprocess_llm_request()already injects schema and examples — having them in the system prompt too causes:<a2ui-json>text withsend_a2ui_json_to_clienttool callsThere's currently no API-level way to express "I'm using tool-based output, handle the schema for me."
Approach
A2UIOutputModemakes the output path explicit:TEXT(default)DEFAULT_WORKFLOW_RULES<a2ui-json>tags in textTOOLSendA2uiToClientToolsetTOOL_WORKFLOW_RULESsend_a2ui_json_to_clientcallsWhen
output_mode=TOOL:include_schemaandinclude_examplesare automatically set toFalseTruefor theseTOOL_WORKFLOW_RULESinstruct the LLM to use the tool functionAll existing behavior is unchanged when
output_modeis not specified (defaults toTEXT).Changes
agent_sdks/python/src/a2ui/schema/output_mode.pyA2UIOutputModeenumagent_sdks/python/src/a2ui/schema/constants.pyTOOL_WORKFLOW_RULESconstantagent_sdks/python/src/a2ui/schema/manager.pyoutput_modeparameter togenerate_system_prompt()agent_sdks/python/tests/schema/test_schema_manager.pyUsage
Test plan
test_schema_manager.pytests pass (backward compatibility)output_mode) produces identical output to beforeTEXTmode includes schema and examples when requestedTOOLmode excludes schema and examples even when requestedTOOLmode usesTOOL_WORKFLOW_RULESreferencingsend_a2ui_json_to_clientTOOLmode preserves role_description, ui_description, workflow_descriptionTOOL_WORKFLOW_RULESincludes top-down ordering requirement./scripts/fix_format.shpasses (Pyink formatting)Relationship to #1465
This PR is complementary to #1465 (
strict_outputmode). They control orthogonal concerns:output_modecontrols where the schema goes (system prompt vs tool)strict_outputcontrols how aggressively to enforce A2UI-first outputBoth can be used independently and will compose cleanly when merged together.