Skip to content

Commit 139c930

Browse files
Updates the PartConverter to support versioning. (#1534)
* Updates the PartConverter to support versioning. 1. Adds a version parameter to the PartConverter constructor, defaulting to "0.8", the version used by Gemini Enterprise and most existing agents. 2. Propagates the version to calls to create_a2ui_part and parse_response_to_parts. * Update agent_sdks/python/src/a2ui/adk/a2a/part_converter.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Suyang Wang <suyangw@google.com> * Fix unit tests --------- Signed-off-by: Suyang Wang <suyangw@google.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent b1c8951 commit 139c930

2 files changed

Lines changed: 59 additions & 7 deletions

File tree

agent_sdks/python/src/a2ui/adk/a2a/part_converter.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,25 @@ class A2uiPartConverter:
5555
This converter handles both tool-based A2UI (via `send_a2ui_json_to_client`)
5656
and text-based A2UI (via A2UI delimiter tags). It uses the provided
5757
catalog to validate and fix JSON payloads.
58+
59+
Args:
60+
a2ui_catalog: The A2UI catalog.
61+
bypass_tool_check: If True, bypass tool validation.
62+
fallback_text: Optional text to fall back on if parsing fails.
63+
version: The A2UI version to use. Defaults to "0.8".
5864
"""
5965

6066
def __init__(
6167
self,
6268
a2ui_catalog: A2uiCatalog,
6369
bypass_tool_check: bool = False,
6470
fallback_text: Optional[str] = None,
71+
version: str = constants.VERSION_0_8,
6572
):
6673
self._catalog = a2ui_catalog
6774
self._bypass_tool_check = bypass_tool_check
6875
self._fallback_text = fallback_text
76+
self._version = version
6977

7078
def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:
7179
"""Converts a GenAI part to A2A parts, with A2UI validation.
@@ -97,7 +105,10 @@ def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:
97105
):
98106
json_data = response_dict.get(constants.A2UI_VALIDATED_JSON_KEY)
99107
if json_data:
100-
return [create_a2ui_part(message) for message in json_data]
108+
return [
109+
create_a2ui_part(message, version=self._version)
110+
for message in json_data
111+
]
101112

102113
if is_send_a2ui_json_to_client_response:
103114
logger.info("No result in A2UI tool response")
@@ -111,6 +122,7 @@ def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:
111122
result,
112123
validator=self._catalog.validator,
113124
fallback_text=self._fallback_text,
125+
version=self._version,
114126
)
115127

116128
# 2. Handle Tool Calls (FunctionCall) - Skip sending to client
@@ -126,6 +138,7 @@ def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:
126138
text,
127139
validator=self._catalog.validator,
128140
fallback_text=self._fallback_text,
141+
version=self._version,
129142
)
130143

131144
# 4. Default conversion for other parts

agent_sdks/python/tests/adk/a2a/test_part_converter.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from a2ui.adk.a2a.part_converter import A2uiPartConverter
2525
from a2ui.adk.send_a2ui_to_client_toolset import SendA2uiToClientToolset
2626
from a2ui.schema.catalog import A2uiCatalog
27-
from a2ui.schema.constants import A2UI_CLOSE_TAG, A2UI_OPEN_TAG
27+
from a2ui.schema.constants import A2UI_CLOSE_TAG, A2UI_OPEN_TAG, VERSION_0_8, VERSION_0_9_1
2828
from google.genai import types as genai_types
2929

3030

@@ -45,7 +45,27 @@ def test_converter_class_convert_valid_tool_response():
4545

4646
a2a_parts = converter.convert(part)
4747
assert len(a2a_parts) == 1
48-
assert a2a_parts[0] == create_a2ui_part(valid_a2ui)
48+
assert a2a_parts[0] == create_a2ui_part(valid_a2ui, version=VERSION_0_8)
49+
50+
51+
def test_converter_class_convert_valid_tool_response_v0_9_1():
52+
catalog_mock = MagicMock(spec=A2uiCatalog)
53+
converter = A2uiPartConverter(catalog_mock, version=VERSION_0_9_1)
54+
55+
valid_a2ui = {"type": "Text", "text": "Hello"}
56+
function_response = genai_types.FunctionResponse(
57+
name=SendA2uiToClientToolset._SendA2uiJsonToClientTool.TOOL_NAME,
58+
response={
59+
SendA2uiToClientToolset._SendA2uiJsonToClientTool.VALIDATED_A2UI_JSON_KEY: [
60+
valid_a2ui
61+
]
62+
},
63+
)
64+
part = genai_types.Part(function_response=function_response)
65+
66+
a2a_parts = converter.convert(part)
67+
assert len(a2a_parts) == 1
68+
assert a2a_parts[0] == create_a2ui_part(valid_a2ui, version=VERSION_0_9_1)
4969

5070

5171
def test_converter_class_convert_tool_error_response():
@@ -107,7 +127,26 @@ def test_converter_class_convert_text_with_a2ui():
107127
# Expect 2 parts: TextPart and A2UI DataPart
108128
assert len(a2a_parts) == 2
109129
assert a2a_parts[0].root.text == "Here is the UI:"
110-
assert a2a_parts[1] == create_a2ui_part(valid_a2ui[0])
130+
assert a2a_parts[1] == create_a2ui_part(valid_a2ui[0], version=VERSION_0_8)
131+
catalog_mock.validator.validate.assert_called_once_with(valid_a2ui)
132+
133+
134+
def test_converter_class_convert_text_with_a2ui_v0_9_1():
135+
catalog_mock = MagicMock(spec=A2uiCatalog)
136+
converter = A2uiPartConverter(catalog_mock, version=VERSION_0_9_1)
137+
138+
valid_a2ui = [{"type": "Text", "text": "Hello"}]
139+
catalog_mock.validator.validate.return_value = None
140+
141+
text = f"Here is the UI:\n{A2UI_OPEN_TAG}\n{json.dumps(valid_a2ui)}\n{A2UI_CLOSE_TAG}"
142+
part = genai_types.Part(text=text)
143+
144+
a2a_parts = converter.convert(part)
145+
146+
# Expect 2 parts: TextPart and A2UI DataPart
147+
assert len(a2a_parts) == 2
148+
assert a2a_parts[0].root.text == "Here is the UI:"
149+
assert a2a_parts[1] == create_a2ui_part(valid_a2ui[0], version=VERSION_0_9_1)
111150
catalog_mock.validator.validate.assert_called_once_with(valid_a2ui)
112151

113152

@@ -123,7 +162,7 @@ def test_converter_class_convert_text_empty_leading():
123162
a2a_parts = converter.convert(part)
124163

125164
assert len(a2a_parts) == 1
126-
assert a2a_parts[0] == create_a2ui_part(ui[0])
165+
assert a2a_parts[0] == create_a2ui_part(ui[0], version=VERSION_0_8)
127166

128167

129168
def test_converter_class_convert_text_markdown_wrapped():
@@ -140,7 +179,7 @@ def test_converter_class_convert_text_markdown_wrapped():
140179

141180
assert len(a2a_parts) == 2
142181
assert a2a_parts[0].root.text == "Behold:"
143-
assert a2a_parts[1] == create_a2ui_part(ui[0])
182+
assert a2a_parts[1] == create_a2ui_part(ui[0], version=VERSION_0_8)
144183
catalog_mock.validator.validate.assert_called_once_with(ui)
145184

146185

@@ -197,7 +236,7 @@ def test_converter_class_convert_tool_response_with_result_containing_a2ui():
197236
# Expect 2 parts: TextPart and A2UI DataPart
198237
assert len(a2a_parts) == 2
199238
assert a2a_parts[0].root.text == "Here is the result:"
200-
assert a2a_parts[1] == create_a2ui_part(valid_a2ui[0])
239+
assert a2a_parts[1] == create_a2ui_part(valid_a2ui[0], version=VERSION_0_8)
201240
catalog_mock.validator.validate.assert_called_once_with(valid_a2ui)
202241

203242

0 commit comments

Comments
 (0)