diff --git a/src/mistralai/extra/struct_chat.py b/src/mistralai/extra/struct_chat.py index 364b450f..66571858 100644 --- a/src/mistralai/extra/struct_chat.py +++ b/src/mistralai/extra/struct_chat.py @@ -24,7 +24,17 @@ def convert_to_parsed_chat_completion_response(response: ChatCompletionResponse, parsed=None ) if isinstance(parsed_message.content, str): - parsed_message.parsed = pydantic_model_from_json(json.loads(parsed_message.content), response_format) + # Validate content is non-empty and valid JSON before parsing + content = parsed_message.content.strip() + if content: + try: + parsed_message.parsed = pydantic_model_from_json(json.loads(content), response_format) + except json.JSONDecodeError: + # Handle invalid JSON gracefully - treat as unparseable content + parsed_message.parsed = None + else: + # Empty or whitespace-only content + parsed_message.parsed = None elif parsed_message.content is None: parsed_message.parsed = None else: diff --git a/src/mistralai/extra/tests/test_struct_chat.py b/src/mistralai/extra/tests/test_struct_chat.py index dd529ba5..685737e0 100644 --- a/src/mistralai/extra/tests/test_struct_chat.py +++ b/src/mistralai/extra/tests/test_struct_chat.py @@ -98,6 +98,117 @@ def test_convert_to_parsed_chat_completion_response(self): ) self.assertEqual(output, expected_response) + def test_empty_string_content(self): + """Test that empty string content is handled gracefully (issue #282)""" + response = ChatCompletionResponse( + id="test-empty", + object="chat.completion", + model="codestral-latest", + usage=UsageInfo(prompt_tokens=10, completion_tokens=0, total_tokens=10), + created=1234567890, + choices=[ + ChatCompletionChoice( + index=0, + message=AssistantMessage( + content="", # Empty string + tool_calls=None, + prefix=False, + role="assistant", + ), + finish_reason="stop", + ) + ], + ) + + # Should not raise JSONDecodeError + result = convert_to_parsed_chat_completion_response(response, MathDemonstration) + self.assertIsNotNone(result) + self.assertEqual(len(result.choices), 1) + self.assertIsNone(result.choices[0].message.parsed) + + def test_whitespace_only_content(self): + """Test that whitespace-only content is handled gracefully (issue #282)""" + response = ChatCompletionResponse( + id="test-whitespace", + object="chat.completion", + model="codestral-latest", + usage=UsageInfo(prompt_tokens=10, completion_tokens=0, total_tokens=10), + created=1234567890, + choices=[ + ChatCompletionChoice( + index=0, + message=AssistantMessage( + content=" ", # Whitespace only + tool_calls=None, + prefix=False, + role="assistant", + ), + finish_reason="stop", + ) + ], + ) + + # Should not raise JSONDecodeError + result = convert_to_parsed_chat_completion_response(response, MathDemonstration) + self.assertIsNotNone(result) + self.assertEqual(len(result.choices), 1) + self.assertIsNone(result.choices[0].message.parsed) + + def test_invalid_json_content(self): + """Test that invalid JSON content is handled gracefully (issue #282)""" + response = ChatCompletionResponse( + id="test-invalid-json", + object="chat.completion", + model="codestral-latest", + usage=UsageInfo(prompt_tokens=10, completion_tokens=20, total_tokens=30), + created=1234567890, + choices=[ + ChatCompletionChoice( + index=0, + message=AssistantMessage( + content="This is not JSON at all", # Invalid JSON + tool_calls=None, + prefix=False, + role="assistant", + ), + finish_reason="stop", + ) + ], + ) + + # Should not raise JSONDecodeError + result = convert_to_parsed_chat_completion_response(response, MathDemonstration) + self.assertIsNotNone(result) + self.assertEqual(len(result.choices), 1) + self.assertIsNone(result.choices[0].message.parsed) + + def test_none_content(self): + """Test that None content is handled correctly""" + response = ChatCompletionResponse( + id="test-none", + object="chat.completion", + model="codestral-latest", + usage=UsageInfo(prompt_tokens=10, completion_tokens=0, total_tokens=10), + created=1234567890, + choices=[ + ChatCompletionChoice( + index=0, + message=AssistantMessage( + content=None, # None + tool_calls=None, + prefix=False, + role="assistant", + ), + finish_reason="stop", + ) + ], + ) + + result = convert_to_parsed_chat_completion_response(response, MathDemonstration) + self.assertIsNotNone(result) + self.assertEqual(len(result.choices), 1) + self.assertIsNone(result.choices[0].message.parsed) + if __name__ == "__main__": unittest.main()