diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 5199964d92..7481d13e13 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -589,10 +589,10 @@ def __init__( self.consent_link = consent_link def __deepcopy__(self, memo: dict[int, Any]) -> Content: - """Create a deep copy, preserving ``_SHALLOW_COPY_FIELDS`` by reference. + """Create a deep copy, discarding non-``None`` ``_SHALLOW_COPY_FIELDS``. Fields listed in ``_SHALLOW_COPY_FIELDS`` may contain LLM SDK objects - (e.g., proto/gRPC responses) that are not safe to deep-copy. + (e.g., proto/gRPC responses) that are not safe to deep-copy or share. """ cls = type(self) result = cls.__new__(cls) @@ -600,7 +600,9 @@ def __deepcopy__(self, memo: dict[int, Any]) -> Content: shallow = cls._SHALLOW_COPY_FIELDS for k, v in self.__dict__.items(): if k in shallow: - object.__setattr__(result, k, v) + if v is not None: + logger.debug("Discarding field '%s' while deep-copying Content.", k) + object.__setattr__(result, k, None) else: object.__setattr__(result, k, deepcopy(v, memo)) return result diff --git a/python/packages/core/tests/core/test_types.py b/python/packages/core/tests/core/test_types.py index a873b48ae3..23902d17b9 100644 --- a/python/packages/core/tests/core/test_types.py +++ b/python/packages/core/tests/core/test_types.py @@ -1197,7 +1197,7 @@ def test_chat_response_updates_to_chat_response_multiple_multiple(): assert len(chat_response.messages) == 1 assert isinstance(chat_response.messages[0], Message) assert chat_response.messages[0].message_id == "1" - assert chat_response.messages[0].contents[0].raw_representation is not None + assert chat_response.messages[0].contents[0].raw_representation is None assert len(chat_response.messages[0].contents) == 3 assert chat_response.messages[0].contents[0].type == "text" @@ -1796,17 +1796,17 @@ def test_response_update_propagates_fields_and_metadata(): assert resp.messages[0].message_id == "mid" -def test_text_coalescing_preserves_first_properties(): +def test_text_coalescing_preserves_first_serializable_properties(): t1 = Content.from_text("A", raw_representation={"r": 1}, additional_properties={"p": 1}) t2 = Content.from_text("B") upd1 = ChatResponseUpdate(contents=[t1], message_id="x") upd2 = ChatResponseUpdate(contents=[t2], message_id="x") resp = ChatResponse.from_updates([upd1, upd2]) - # After coalescing there should be a single TextContent with merged text and preserved props from first + # After coalescing there should be a single TextContent with merged text and serializable props from first. items = [c for c in resp.messages[0].contents if c.type == "text"] assert len(items) >= 1 assert items[0].text == "AB" - assert items[0].raw_representation == {"r": 1} + assert items[0].raw_representation is None assert items[0].additional_properties == {"p": 1} @@ -2500,18 +2500,20 @@ def __deepcopy__(self, memo: dict) -> Any: raise TypeError("Cannot deepcopy this object") -def test_content_deepcopy_preserves_raw_representation(): - """Test that deepcopy of Content keeps raw_representation by reference.""" +def test_content_deepcopy_discards_raw_representation(caplog: pytest.LogCaptureFixture) -> None: + """Test that deepcopy of Content discards raw_representation and logs at debug level.""" import copy raw = _NonCopyableRaw() content = Content.from_text("hello", raw_representation=raw) - cloned = copy.deepcopy(content) + with caplog.at_level("DEBUG", logger="agent_framework"): + cloned = copy.deepcopy(content) assert cloned.text == "hello" - assert cloned.raw_representation is raw + assert cloned.raw_representation is None assert cloned.additional_properties is not content.additional_properties + assert caplog.messages == ["Discarding field 'raw_representation' while deep-copying Content."] def test_message_deepcopy_preserves_raw_representation(): @@ -2617,21 +2619,20 @@ def test_nested_deepcopy_preserves_raw_representation(): assert cloned.text == "hello" -def test_content_deepcopy_shallow_copy_fields_identity(): - """Test that Content._SHALLOW_COPY_FIELDS fields are identity-preserved while others are deep-copied.""" +def test_content_deepcopy_does_not_log_for_empty_shallow_copy_fields(caplog: pytest.LogCaptureFixture) -> None: + """Test that empty shallow-copy fields do not emit discard logs.""" import copy - raw = _NonCopyableRaw() - content = Content.from_text("hello", raw_representation=raw) + content = Content.from_text("hello") content.additional_properties["key"] = "value" - cloned = copy.deepcopy(content) + with caplog.at_level("DEBUG", logger="agent_framework"): + cloned = copy.deepcopy(content) - # _SHALLOW_COPY_FIELDS (raw_representation) should be same object - assert cloned.raw_representation is raw - # Non-shallow fields should be independent deep copies + assert cloned.raw_representation is None assert cloned.additional_properties is not content.additional_properties assert cloned.additional_properties == {"key": "value"} + assert caplog.messages == [] def test_chat_response_deepcopy_deep_copies_additional_properties():