@@ -379,6 +379,101 @@ def test_tool_call_conversion():
379379 assert tool_call ["function" ]["arguments" ] == function_call ["arguments" ] # type: ignore
380380
381381
382+ def test_tool_call_with_valid_json_arguments ():
383+ """
384+ Test that valid JSON arguments pass through unchanged.
385+ """
386+ function_call = ResponseFunctionToolCallParam (
387+ id = "tool1" ,
388+ call_id = "abc" ,
389+ name = "test_tool" ,
390+ arguments = '{"key": "value", "number": 42}' ,
391+ type = "function_call" ,
392+ )
393+
394+ messages = Converter .items_to_messages ([function_call ])
395+ tool_call = messages [0 ]["tool_calls" ][0 ] # type: ignore
396+ assert tool_call ["function" ]["arguments" ] == '{"key": "value", "number": 42}' # type: ignore
397+
398+
399+ def test_tool_call_with_invalid_json_arguments ():
400+ """
401+ Test that invalid JSON arguments are sanitized to "{}".
402+ This prevents API errors when invalid JSON is stored in session history
403+ and later sent to APIs that strictly validate JSON (like Anthropic via litellm).
404+ """
405+ # Test with missing closing brace (common case)
406+ function_call = ResponseFunctionToolCallParam (
407+ id = "tool1" ,
408+ call_id = "abc" ,
409+ name = "test_tool" ,
410+ arguments = '{"key": "value"' , # Missing closing brace
411+ type = "function_call" ,
412+ )
413+
414+ messages = Converter .items_to_messages ([function_call ])
415+ tool_call = messages [0 ]["tool_calls" ][0 ] # type: ignore
416+ # Invalid JSON should be sanitized to "{}"
417+ assert tool_call ["function" ]["arguments" ] == "{}" # type: ignore
418+
419+ # Test with None
420+ function_call_none = ResponseFunctionToolCallParam (
421+ id = "tool2" ,
422+ call_id = "def" ,
423+ name = "test_tool" ,
424+ arguments = None , # type: ignore
425+ type = "function_call" ,
426+ )
427+
428+ messages_none = Converter .items_to_messages ([function_call_none ])
429+ tool_call_none = messages_none [0 ]["tool_calls" ][0 ] # type: ignore
430+ assert tool_call_none ["function" ]["arguments" ] == "{}" # type: ignore
431+
432+ # Test with empty string
433+ function_call_empty = ResponseFunctionToolCallParam (
434+ id = "tool3" ,
435+ call_id = "ghi" ,
436+ name = "test_tool" ,
437+ arguments = "" ,
438+ type = "function_call" ,
439+ )
440+
441+ messages_empty = Converter .items_to_messages ([function_call_empty ])
442+ tool_call_empty = messages_empty [0 ]["tool_calls" ][0 ] # type: ignore
443+ assert tool_call_empty ["function" ]["arguments" ] == "{}" # type: ignore
444+
445+
446+ def test_tool_call_with_malformed_json_variants ():
447+ """
448+ Test various malformed JSON cases are all sanitized correctly.
449+ """
450+ malformed_cases = [
451+ '{"key": "value"' , # Missing closing brace
452+ '{"key": "value"}}' , # Extra closing brace
453+ '{"key": "value",}' , # Trailing comma
454+ '{"key":}' , # Missing value
455+ '{key: "value"}' , # Unquoted key
456+ '{"key": "value"' , # Missing closing brace (different position)
457+ "not json at all" , # Not JSON at all
458+ ]
459+
460+ for i , malformed_json in enumerate (malformed_cases ):
461+ function_call = ResponseFunctionToolCallParam (
462+ id = f"tool{ i } " ,
463+ call_id = f"call{ i } " ,
464+ name = "test_tool" ,
465+ arguments = malformed_json ,
466+ type = "function_call" ,
467+ )
468+
469+ messages = Converter .items_to_messages ([function_call ])
470+ tool_call = messages [0 ]["tool_calls" ][0 ] # type: ignore
471+ # All malformed JSON should be sanitized to "{}"
472+ assert tool_call ["function" ]["arguments" ] == "{}" , ( # type: ignore
473+ f"Malformed JSON '{ malformed_json } ' should be sanitized to '{{}}'"
474+ )
475+
476+
382477@pytest .mark .parametrize ("role" , ["user" , "system" , "developer" ])
383478def test_input_message_with_all_roles (role : str ):
384479 """
0 commit comments