Skip to content

Conversation

@filipecaixeta
Copy link

@filipecaixeta filipecaixeta commented Jan 22, 2026

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

#4242

2. Or, if no issue exists, describe the change:

Problem:

The part converter in part_converter.py had asymmetric handling of thought metadata:

  1. A2A → GenAI (convert_a2a_part_to_genai_part): Metadata was completely ignored, so thought was always None for all part types.

  2. GenAI → A2A (convert_genai_part_to_a2a_part): thought was only preserved for TextPart, but dropped for FilePart (file_data, inline_data) and all DataPart types (function_call, function_response, code_execution_result, executable_code).

This breaks multi-agent scenarios where reasoning/thought content needs to survive round-trip conversions between agents.

Solution:

  1. A2A → GenAI: Extract thought from part metadata at the beginning of convert_a2a_part_to_genai_part and pass it to all genai_types.Part() constructors.

  2. GenAI → A2A: Add thought metadata preservation to all part types in convert_genai_part_to_a2a_part:

    • FilePart with file_data
    • FilePart with inline_data (merged with existing video_metadata handling)
    • DataPart for function_call, function_response, code_execution_result, executable_code

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

Manual End-to-End (E2E) Tests:

The fix can be verified by setting up two agents communicating via A2A where the sub-agent generates content with thought=True. Before this fix, the parent agent would receive the content with thought=None. After this fix, thought=True is preserved through the full round-trip.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Files changed:

  • src/google/adk/a2a/converters/part_converter.py - Added bidirectional thought metadata preservation
  • tests/unittests/a2a/converters/test_part_converter.py - Added 17 new tests for thought preservation

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Jan 22, 2026
@ryanaiagent ryanaiagent self-assigned this Jan 23, 2026
@ryanaiagent
Copy link
Collaborator

/gemini review

Copy link
Contributor

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

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 aims to preserve thought metadata during A2A to GenAI part conversions. The changes correctly handle the A2A to GenAI direction by extracting thought from metadata and passing it to the genai_types.Part constructors. The accompanying unit tests verify this for several part types.

However, the fix is incomplete for a full round-trip conversion. The reverse conversion (GenAI to A2A) in convert_genai_part_to_a2a_part does not preserve the thought field for most part types, which contradicts the goal of the PR. I've added comments in the test files suggesting adding more comprehensive round-trip tests to expose and address this issue. Additionally, test coverage for the current changes could be improved by adding tests for all DataPart variants.

Comment on lines +651 to +665
def test_text_part_with_thought_round_trip(self):
"""Test round-trip conversion for text parts with thought=True."""
# Arrange - Start with GenAI part with thought=True
original_text = "I'm reasoning about this problem..."
genai_part = genai_types.Part(text=original_text, thought=True)

# Act - Round trip: GenAI -> A2A -> GenAI
a2a_part = convert_genai_part_to_a2a_part(genai_part)
result_genai_part = convert_a2a_part_to_genai_part(a2a_part)

# Assert
assert result_genai_part is not None
assert isinstance(result_genai_part, genai_types.Part)
assert result_genai_part.text == original_text
assert result_genai_part.thought is True
Copy link
Contributor

Choose a reason for hiding this comment

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

high

These round-trip tests for TextPart are great for ensuring thought is preserved. However, the fix for preserving thought seems incomplete for the full round-trip. The convert_genai_part_to_a2a_part function only handles thought for TextPart and will drop it for other part types like FilePart and DataPart.

I recommend adding similar round-trip tests for other part types (e.g., test_file_part_with_thought_round_trip) to expose this bug. Such a test would start with a genai_types.Part containing file_data and thought=True, convert it to A2A and back, and then assert that thought is still True in the final genai_types.Part. This would currently fail and help ensure the fix is complete for all scenarios.

Copy link
Collaborator

Choose a reason for hiding this comment

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

can you please address this.

Copy link
Author

Choose a reason for hiding this comment

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

Comment on lines +232 to +258
def test_convert_data_part_function_call_with_thought_metadata(self):
"""Test conversion of A2A DataPart with function call and thought metadata."""
# Arrange
function_call_data = {
"name": "test_function",
"args": {"param1": "value1"},
}
a2a_part = a2a_types.Part(
root=a2a_types.DataPart(
data=function_call_data,
metadata={
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL,
_get_adk_metadata_key("thought"): True,
},
)
)

# Act
result = convert_a2a_part_to_genai_part(a2a_part)

# Assert
assert result is not None
assert result.function_call is not None
assert result.function_call.name == "test_function"
assert result.thought is True
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This is a good test for function_call with thought metadata. However, the changes in part_converter.py also add thought preservation for other DataPart types like function_response, code_execution_result, and executable_code. To ensure full test coverage for the changes, please consider adding similar tests for these other DataPart types.

@ryanaiagent ryanaiagent added a2a [Component] This issue is related a2a support inside ADK. and removed core [Component] This issue is related to the core interface and implementation labels Jan 23, 2026
@ryanaiagent
Copy link
Collaborator

Hi @filipecaixeta , Thank you for your contribution! We appreciate you taking the time to submit this pull request.
Could you also address the recommendations before we can proceed with the review.

@ryanaiagent ryanaiagent added the request clarification [Status] The maintainer need clarification or more information from the author label Jan 23, 2026
@filipecaixeta filipecaixeta changed the title fix(a2a): Preserve thought metadata in A2A to GenAI part conversion fix(a2a): Preserve thought metadata in bidirectional part conversions Jan 23, 2026
@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Jan 23, 2026
@ryanaiagent
Copy link
Collaborator

/gemini review

Copy link
Contributor

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

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 effectively addresses the issue of preserving thought metadata during bidirectional conversions between A2A and GenAI parts. The changes are comprehensive, covering all relevant part types, and the logic is sound. The addition of a thorough suite of unit tests is commendable, as it ensures the correctness of the implementation and protects against future regressions. I have one suggestion to improve code maintainability by refactoring a section with duplicated logic.

Comment on lines 229 to 295
if part.function_call:
metadata = {
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
}
if part.thought is not None:
metadata[_get_adk_metadata_key('thought')] = part.thought
return a2a_types.Part(
root=a2a_types.DataPart(
data=part.function_call.model_dump(
by_alias=True, exclude_none=True
),
metadata={
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
},
metadata=metadata,
)
)

if part.function_response:
metadata = {
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
}
if part.thought is not None:
metadata[_get_adk_metadata_key('thought')] = part.thought
return a2a_types.Part(
root=a2a_types.DataPart(
data=part.function_response.model_dump(
by_alias=True, exclude_none=True
),
metadata={
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
},
metadata=metadata,
)
)

if part.code_execution_result:
metadata = {
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
}
if part.thought is not None:
metadata[_get_adk_metadata_key('thought')] = part.thought
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
},
metadata=metadata,
)
)

if part.executable_code:
metadata = {
_get_adk_metadata_key(
A2A_DATA_PART_METADATA_TYPE_KEY
): A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
}
if part.thought is not None:
metadata[_get_adk_metadata_key('thought')] = part.thought
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
},
metadata=metadata,
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic for handling function_call, function_response, code_execution_result, and executable_code parts is very similar and has been repeated four times. This code duplication can make the code harder to read and maintain. To improve this, you could refactor this section to eliminate the repetition. A good approach would be to use a loop over a data structure (like a dictionary) that maps the part attributes to their corresponding metadata types. This would make the code more concise and easier to maintain.

  data_part_mapping = {
      'function_call': (
          part.function_call,
          A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL,
      ),
      'function_response': (
          part.function_response,
          A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE,
      ),
      'code_execution_result': (
          part.code_execution_result,
          A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT,
      ),
      'executable_code': (
          part.executable_code,
          A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE,
      ),
  }

  for data_obj, metadata_type in data_part_mapping.values():
    if data_obj:
      metadata = {
          _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY): metadata_type
      }
      if part.thought is not None:
        metadata[_get_adk_metadata_key('thought')] = part.thought
      return a2a_types.Part(
          root=a2a_types.DataPart(
              data=data_obj.model_dump(by_alias=True, exclude_none=True),
              metadata=metadata,
          )
      )

@ryanaiagent ryanaiagent added needs review [Status] The PR/issue is awaiting review from the maintainer and removed core [Component] This issue is related to the core interface and implementation request clarification [Status] The maintainer need clarification or more information from the author labels Jan 26, 2026
@ryanaiagent
Copy link
Collaborator

Hi @filipecaixeta , Your PR has been received by the team and is currently under review. We will provide feedback as soon as we have an update to share.

@ryanaiagent
Copy link
Collaborator

Hi @GWeale , can you please review this.

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

Labels

a2a [Component] This issue is related a2a support inside ADK. needs review [Status] The PR/issue is awaiting review from the maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants