Skip to content

Commit 6bcc539

Browse files
seanzhougooglecopybara-github
authored andcommitted
chore: Enhance a2a part converters
a. fix binary data conversion b. support thoughts, code execution result, executable codes conversion PiperOrigin-RevId: 775827259
1 parent d126b29 commit 6bcc539

File tree

2 files changed

+403
-45
lines changed

2 files changed

+403
-45
lines changed

src/google/adk/a2a/converters/part_converter.py

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from __future__ import annotations
2020

21+
import base64
2122
import json
2223
import logging
2324
import sys
@@ -43,8 +44,11 @@
4344
logger = logging.getLogger('google_adk.' + __name__)
4445

4546
A2A_DATA_PART_METADATA_TYPE_KEY = 'type'
47+
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = 'is_long_running'
4648
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'
4749
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
50+
A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'
51+
A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'
4852

4953

5054
@working_in_progress
@@ -67,7 +71,8 @@ def convert_a2a_part_to_genai_part(
6771
elif isinstance(part.file, a2a_types.FileWithBytes):
6872
return genai_types.Part(
6973
inline_data=genai_types.Blob(
70-
data=part.file.bytes.encode('utf-8'), mime_type=part.file.mimeType
74+
data=base64.b64decode(part.file.bytes),
75+
mime_type=part.file.mimeType,
7176
)
7277
)
7378
else:
@@ -84,7 +89,11 @@ def convert_a2a_part_to_genai_part(
8489
# response.
8590
# TODO once A2A defined how to suervice such information, migrate below
8691
# logic accordinlgy
87-
if part.metadata and A2A_DATA_PART_METADATA_TYPE_KEY in part.metadata:
92+
if (
93+
part.metadata
94+
and _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
95+
in part.metadata
96+
):
8897
if (
8998
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
9099
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
@@ -103,6 +112,24 @@ def convert_a2a_part_to_genai_part(
103112
part.data, by_alias=True
104113
)
105114
)
115+
if (
116+
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
117+
== A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
118+
):
119+
return genai_types.Part(
120+
code_execution_result=genai_types.CodeExecutionResult.model_validate(
121+
part.data, by_alias=True
122+
)
123+
)
124+
if (
125+
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
126+
== A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
127+
):
128+
return genai_types.Part(
129+
executable_code=genai_types.ExecutableCode.model_validate(
130+
part.data, by_alias=True
131+
)
132+
)
106133
return genai_types.Part(text=json.dumps(part.data))
107134

108135
logger.warning(
@@ -118,27 +145,40 @@ def convert_genai_part_to_a2a_part(
118145
part: genai_types.Part,
119146
) -> Optional[a2a_types.Part]:
120147
"""Convert a Google GenAI Part to an A2A Part."""
148+
121149
if part.text:
122-
return a2a_types.TextPart(text=part.text)
150+
a2a_part = a2a_types.TextPart(text=part.text)
151+
if part.thought is not None:
152+
a2a_part.metadata = {_get_adk_metadata_key('thought'): part.thought}
153+
return a2a_types.Part(root=a2a_part)
123154

124155
if part.file_data:
125-
return a2a_types.FilePart(
126-
file=a2a_types.FileWithUri(
127-
uri=part.file_data.file_uri,
128-
mimeType=part.file_data.mime_type,
156+
return a2a_types.Part(
157+
root=a2a_types.FilePart(
158+
file=a2a_types.FileWithUri(
159+
uri=part.file_data.file_uri,
160+
mimeType=part.file_data.mime_type,
161+
)
129162
)
130163
)
131164

132165
if part.inline_data:
133-
return a2a_types.Part(
134-
root=a2a_types.FilePart(
135-
file=a2a_types.FileWithBytes(
136-
bytes=part.inline_data.data,
137-
mimeType=part.inline_data.mime_type,
138-
)
166+
a2a_part = a2a_types.FilePart(
167+
file=a2a_types.FileWithBytes(
168+
bytes=base64.b64encode(part.inline_data.data).decode('utf-8'),
169+
mimeType=part.inline_data.mime_type,
139170
)
140171
)
141172

173+
if part.video_metadata:
174+
a2a_part.metadata = {
175+
_get_adk_metadata_key(
176+
'video_metadata'
177+
): part.video_metadata.model_dump(by_alias=True, exclude_none=True)
178+
}
179+
180+
return a2a_types.Part(root=a2a_part)
181+
142182
# Conver the funcall and function reponse to A2A DataPart.
143183
# This is mainly for converting human in the loop and auth request and
144184
# response.
@@ -151,9 +191,9 @@ def convert_genai_part_to_a2a_part(
151191
by_alias=True, exclude_none=True
152192
),
153193
metadata={
154-
A2A_DATA_PART_METADATA_TYPE_KEY: (
155-
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
156-
)
194+
_get_adk_metadata_key(
195+
A2A_DATA_PART_METADATA_TYPE_KEY
196+
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
157197
},
158198
)
159199
)
@@ -165,9 +205,37 @@ def convert_genai_part_to_a2a_part(
165205
by_alias=True, exclude_none=True
166206
),
167207
metadata={
168-
A2A_DATA_PART_METADATA_TYPE_KEY: (
169-
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
170-
)
208+
_get_adk_metadata_key(
209+
A2A_DATA_PART_METADATA_TYPE_KEY
210+
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
211+
},
212+
)
213+
)
214+
215+
if part.code_execution_result:
216+
return a2a_types.Part(
217+
root=a2a_types.DataPart(
218+
data=part.code_execution_result.model_dump(
219+
by_alias=True, exclude_none=True
220+
),
221+
metadata={
222+
_get_adk_metadata_key(
223+
A2A_DATA_PART_METADATA_TYPE_KEY
224+
): A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
225+
},
226+
)
227+
)
228+
229+
if part.executable_code:
230+
return a2a_types.Part(
231+
root=a2a_types.DataPart(
232+
data=part.executable_code.model_dump(
233+
by_alias=True, exclude_none=True
234+
),
235+
metadata={
236+
_get_adk_metadata_key(
237+
A2A_DATA_PART_METADATA_TYPE_KEY
238+
): A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
171239
},
172240
)
173241
)

0 commit comments

Comments
 (0)