tests: assistant msg validation error (#6536)
* add regression test for dict content in AssistantMessage
Tests the fix for pydantic validation error when send_message tool
returns dict content like {'tofu': 1, 'mofu': 1, 'bofu': 1}.
The test verifies that dict content is properly serialized to JSON
string before creating AssistantMessage.
* improve type annotation for validate_function_response
Changed return type from Any to str | dict[str, Any] to match actual
behavior. This enables static type checkers (pyright, mypy) to catch
type mismatches like the AssistantMessage bug.
With proper type annotations, pyright would have caught:
error: Argument of type "str | dict[str, Any]" cannot be assigned
to parameter "content" of type "str"
This prevents future bugs where dict is passed to string-only fields.
* add regression test for dict content in AssistantMessage
Moved test into existing test_message_manager.py suite alongside other
message conversion tests.
Tests the fix for pydantic validation error when send_message tool
returns dict content like {'tofu': 1, 'mofu': 1, 'bofu': 1}.
The test verifies that dict content is properly serialized to JSON
string before creating AssistantMessage.
This commit is contained in:
@@ -854,7 +854,9 @@ def parse_json(string) -> dict:
|
||||
raise e
|
||||
|
||||
|
||||
def validate_function_response(function_response: Any, return_char_limit: int, strict: bool = False, truncate: bool = True) -> Any:
|
||||
def validate_function_response(
|
||||
function_response: Any, return_char_limit: int, strict: bool = False, truncate: bool = True
|
||||
) -> str | dict[str, Any]:
|
||||
"""Check to make sure that a function used by Letta returned a valid response. Truncates to return_char_limit if necessary.
|
||||
|
||||
This makes sure that we can coerce the function_response into a string or dict that meets our criteria. We handle some soft coercion.
|
||||
|
||||
@@ -1019,3 +1019,70 @@ async def test_convert_tool_calls_only_assistant_tools(server: SyncServer, sarah
|
||||
# check assistant messages content (they appear in reverse order)
|
||||
assert letta_messages[0].content == "Second message"
|
||||
assert letta_messages[1].content == "First message"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_convert_assistant_message_with_dict_content(server: SyncServer, sarah_agent, default_user):
|
||||
"""Test that send_message with dict content is properly serialized to JSON string
|
||||
|
||||
Regression test for bug where dict content like {'tofu': 1, 'mofu': 1, 'bofu': 1}
|
||||
caused pydantic validation error because AssistantMessage.content expects a string.
|
||||
"""
|
||||
import json
|
||||
|
||||
# Test case 1: Simple dict as message content
|
||||
tool_calls = [
|
||||
OpenAIToolCall(
|
||||
id="call_1",
|
||||
type="function",
|
||||
function=OpenAIFunction(name="send_message", arguments='{"message": {"tofu": 1, "mofu": 1, "bofu": 1}}'),
|
||||
),
|
||||
]
|
||||
|
||||
message = PydanticMessage(
|
||||
agent_id=sarah_agent.id,
|
||||
role=MessageRole.assistant,
|
||||
content=[TextContent(text="Sending structured data...")],
|
||||
tool_calls=tool_calls,
|
||||
)
|
||||
|
||||
# convert with assistant mode - should not raise validation error
|
||||
letta_messages = message.to_letta_messages(use_assistant_message=True)
|
||||
|
||||
assert len(letta_messages) == 2
|
||||
assert letta_messages[0].message_type == "assistant_message"
|
||||
assert letta_messages[1].message_type == "reasoning_message"
|
||||
|
||||
# check that dict was serialized to JSON string
|
||||
assistant_msg = letta_messages[0]
|
||||
assert isinstance(assistant_msg.content, str)
|
||||
|
||||
# verify the JSON-serialized content can be parsed back
|
||||
parsed_content = json.loads(assistant_msg.content)
|
||||
assert parsed_content == {"tofu": 1, "mofu": 1, "bofu": 1}
|
||||
|
||||
# Test case 2: Nested dict with various types
|
||||
tool_calls_nested = [
|
||||
OpenAIToolCall(
|
||||
id="call_2",
|
||||
type="function",
|
||||
function=OpenAIFunction(
|
||||
name="send_message",
|
||||
arguments='{"message": {"status": "success", "data": {"count": 42, "items": ["a", "b"]}, "meta": null}}',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
message_nested = PydanticMessage(
|
||||
agent_id=sarah_agent.id,
|
||||
role=MessageRole.assistant,
|
||||
content=[TextContent(text="Sending complex data...")],
|
||||
tool_calls=tool_calls_nested,
|
||||
)
|
||||
|
||||
letta_messages_nested = message_nested.to_letta_messages(use_assistant_message=True)
|
||||
assistant_msg_nested = letta_messages_nested[0]
|
||||
|
||||
assert isinstance(assistant_msg_nested.content, str)
|
||||
parsed_nested = json.loads(assistant_msg_nested.content)
|
||||
assert parsed_nested == {"status": "success", "data": {"count": 42, "items": ["a", "b"]}, "meta": None}
|
||||
|
||||
Reference in New Issue
Block a user