chore: Enhance a2a part converters

a. fix binary data conversion
b. support thoughts, code execution result, executable codes conversion

PiperOrigin-RevId: 775827259
This commit is contained in:
Xiang (Sean) Zhou
2025-06-25 13:58:49 -07:00
committed by Copybara-Service
parent 738d1a8b84
commit 832a633351
2 changed files with 403 additions and 45 deletions
+87 -19
View File
@@ -18,6 +18,7 @@ module containing utilities for conversion betwen A2A Part and Google GenAI Part
from __future__ import annotations
import base64
import json
import logging
import sys
@@ -43,8 +44,11 @@ from ...utils.feature_decorator import working_in_progress
logger = logging.getLogger('google_adk.' + __name__)
A2A_DATA_PART_METADATA_TYPE_KEY = 'type'
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = 'is_long_running'
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'
A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'
@working_in_progress
@@ -67,7 +71,8 @@ def convert_a2a_part_to_genai_part(
elif isinstance(part.file, a2a_types.FileWithBytes):
return genai_types.Part(
inline_data=genai_types.Blob(
data=part.file.bytes.encode('utf-8'), mime_type=part.file.mimeType
data=base64.b64decode(part.file.bytes),
mime_type=part.file.mimeType,
)
)
else:
@@ -84,7 +89,11 @@ def convert_a2a_part_to_genai_part(
# response.
# TODO once A2A defined how to suervice such information, migrate below
# logic accordinlgy
if part.metadata and A2A_DATA_PART_METADATA_TYPE_KEY in part.metadata:
if (
part.metadata
and _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
in part.metadata
):
if (
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
@@ -103,6 +112,24 @@ def convert_a2a_part_to_genai_part(
part.data, by_alias=True
)
)
if (
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
== A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
):
return genai_types.Part(
code_execution_result=genai_types.CodeExecutionResult.model_validate(
part.data, by_alias=True
)
)
if (
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
== A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
):
return genai_types.Part(
executable_code=genai_types.ExecutableCode.model_validate(
part.data, by_alias=True
)
)
return genai_types.Part(text=json.dumps(part.data))
logger.warning(
@@ -118,27 +145,40 @@ def convert_genai_part_to_a2a_part(
part: genai_types.Part,
) -> Optional[a2a_types.Part]:
"""Convert a Google GenAI Part to an A2A Part."""
if part.text:
return a2a_types.TextPart(text=part.text)
a2a_part = a2a_types.TextPart(text=part.text)
if part.thought is not None:
a2a_part.metadata = {_get_adk_metadata_key('thought'): part.thought}
return a2a_types.Part(root=a2a_part)
if part.file_data:
return a2a_types.FilePart(
file=a2a_types.FileWithUri(
uri=part.file_data.file_uri,
mimeType=part.file_data.mime_type,
return a2a_types.Part(
root=a2a_types.FilePart(
file=a2a_types.FileWithUri(
uri=part.file_data.file_uri,
mimeType=part.file_data.mime_type,
)
)
)
if part.inline_data:
return a2a_types.Part(
root=a2a_types.FilePart(
file=a2a_types.FileWithBytes(
bytes=part.inline_data.data,
mimeType=part.inline_data.mime_type,
)
a2a_part = a2a_types.FilePart(
file=a2a_types.FileWithBytes(
bytes=base64.b64encode(part.inline_data.data).decode('utf-8'),
mimeType=part.inline_data.mime_type,
)
)
if part.video_metadata:
a2a_part.metadata = {
_get_adk_metadata_key(
'video_metadata'
): part.video_metadata.model_dump(by_alias=True, exclude_none=True)
}
return a2a_types.Part(root=a2a_part)
# Conver the funcall and function reponse to A2A DataPart.
# This is mainly for converting human in the loop and auth request and
# response.
@@ -151,9 +191,9 @@ def convert_genai_part_to_a2a_part(
by_alias=True, exclude_none=True
),
metadata={
A2A_DATA_PART_METADATA_TYPE_KEY: (
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
)
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
},
)
)
@@ -165,9 +205,37 @@ def convert_genai_part_to_a2a_part(
by_alias=True, exclude_none=True
),
metadata={
A2A_DATA_PART_METADATA_TYPE_KEY: (
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
)
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
},
)
)
if part.code_execution_result:
return a2a_types.Part(
root=a2a_types.DataPart(
data=part.code_execution_result.model_dump(
by_alias=True, exclude_none=True
),
metadata={
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
},
)
)
if part.executable_code:
return a2a_types.Part(
root=a2a_types.DataPart(
data=part.executable_code.model_dump(
by_alias=True, exclude_none=True
),
metadata={
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
},
)
)