From 55e431ec60658ab88f20ca668a8ef476eeae3b29 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Tue, 13 Oct 2026 05:52:03 +0000 Subject: [PATCH 1/2] Python: Treat a single string passed to Message as one text content Message(role, contents) iterated contents directly. A bare string is itself a sequence of strings, and the contents type hint accepts it, so Message("user", "Hello") produced one text content per character, which Chat Completions then sent as one user message per character. Wrap a bare string in a list so it becomes a single text content, the same as Message("user", ["Hello"]). --- .../packages/core/agent_framework/_types.py | 7 ++++++- python/packages/core/tests/core/test_types.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 6ffebae8700..8d8f444c146 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -2072,7 +2072,8 @@ def __init__( Args: role: The role of the author of the message (e.g., "user", "assistant", "system", "tool"). contents: A sequence of content items. Can be Content objects, strings (auto-converted - to TextContent), or dicts (parsed via Content.from_dict). Defaults to empty list. + to TextContent), or dicts (parsed via Content.from_dict). A single string is + treated as one text item. Defaults to empty list. Keyword Args: author_name: Optional name of the author of the message. @@ -2081,6 +2082,10 @@ def __init__( Additional properties are used within Agent Framework, they are not sent to services. raw_representation: Optional raw representation of the chat message. """ + if isinstance(contents, str): + # A string is itself a sequence of strings, so iterating it would create one text + # content per character. + contents = [contents] parsed_contents = [] if contents is None else _parse_content_list(contents) self.role: str = role diff --git a/python/packages/core/tests/core/test_types.py b/python/packages/core/tests/core/test_types.py index 2c41acd93b4..8e34ed33ea5 100644 --- a/python/packages/core/tests/core/test_types.py +++ b/python/packages/core/tests/core/test_types.py @@ -1167,6 +1167,25 @@ def test_chat_message_text(): assert isinstance(message.contents[0], Content) +@pytest.mark.parametrize( + "message", + [Message("user", "Hello, how are you?"), Message(role="user", contents="Hello, how are you?")], + ids=["positional", "keyword"], +) +def test_chat_message_with_single_string_contents(message: Message) -> None: + """A bare string is one text content, not a sequence of single-character contents.""" + assert len(message.contents) == 1 + assert message.contents[0].type == "text" + assert message.text == "Hello, how are you?" + + +def test_chat_message_with_empty_string_contents() -> None: + message = Message("user", "") + + assert len(message.contents) == 1 + assert message.text == "" + + def test_chat_message_contents(): """Test the Message class to ensure it initializes correctly with contents.""" # Create a Message with a role and multiple contents From e703de66ed69f7fd8a1954fd88c346b966d91bf8 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Tue, 13 Oct 2026 08:01:21 +0000 Subject: [PATCH 2/2] Python: Move bare-string handling into _parse_content_list Review feedback: keep all content-shape parsing in the existing helper instead of normalizing at the Message constructor. _parse_content_list now treats a bare string as one text item, and Message.__init__ calls it unchanged. Adds a helper-level test next to the existing ones. --- python/packages/core/agent_framework/_types.py | 11 ++++++----- python/packages/core/tests/core/test_types.py | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 8d8f444c146..9e054e0b360 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -56,11 +56,16 @@ def _parse_content_list(contents_data: Sequence[Any]) -> list[Content]: """Parse a list of content data into appropriate Content objects. Args: - contents_data: List of content data (strings, dicts, or already constructed objects) + contents_data: List of content data (strings, dicts, or already constructed objects). + A single string is treated as one text item. Returns: List of Content objects with unknown types logged and ignored """ + if isinstance(contents_data, str): + # A string is itself a sequence of strings, so iterating it would create one text + # content per character. + contents_data = [contents_data] contents: list[Content] = [] for content_data in contents_data: if content_data is None: @@ -2082,10 +2087,6 @@ def __init__( Additional properties are used within Agent Framework, they are not sent to services. raw_representation: Optional raw representation of the chat message. """ - if isinstance(contents, str): - # A string is itself a sequence of strings, so iterating it would create one text - # content per character. - contents = [contents] parsed_contents = [] if contents is None else _parse_content_list(contents) self.role: str = role diff --git a/python/packages/core/tests/core/test_types.py b/python/packages/core/tests/core/test_types.py index 8e34ed33ea5..b3e77dd3b9d 100644 --- a/python/packages/core/tests/core/test_types.py +++ b/python/packages/core/tests/core/test_types.py @@ -4572,6 +4572,15 @@ def test_parse_content_list_with_dicts(): assert result[0].text == "hello" +def test_parse_content_list_with_single_string(): + """Test _parse_content_list treats a bare string as one text item, not one per character.""" + result = _parse_content_list("hello") + + assert len(result) == 1 + assert result[0].type == "text" + assert result[0].text == "hello" + + def test_parse_content_list_with_mixed_content_and_dict(): """Test _parse_content_list handles a mix of Content objects and dicts.""" content = Content(type="text", text="hello")