diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index a08410dd6..9dc4cab5d 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -1,6 +1,7 @@ import ast import datetime import json +import re import time from difflib import SequenceMatcher from textwrap import dedent @@ -408,15 +409,21 @@ class ToolUsage: def _validate_tool_input(self, tool_input: str) -> Dict[str, Any]: try: - # Replace 'None' strings with null in the JSON string for proper parsing - tool_input = tool_input.replace('"None"', "null") + # Convert single quotes to double quotes for JSON compatibility + tool_input = tool_input.replace("'", '"') + + # Replace Python literals with JSON equivalents using word boundaries + replacements = { + r"\bNone\b": "null", + r"\bTrue\b": "true", + r"\bFalse\b": "false", + } + for pattern, replacement in replacements.items(): + tool_input = re.sub(pattern, replacement, tool_input) arguments = json.loads(tool_input) except json.JSONDecodeError: - # Fix common issues in the tool_input string - tool_input = tool_input.replace("'", '"') - tool_input = tool_input.replace('"None"', "null") - + # Attempt to repair JSON string repaired_input = repair_json(tool_input) try: arguments = json.loads(repaired_input) diff --git a/tests/tools/test_tool_usage.py b/tests/tools/test_tool_usage.py index 05b9b23af..952011339 100644 --- a/tests/tools/test_tool_usage.py +++ b/tests/tools/test_tool_usage.py @@ -121,3 +121,113 @@ def test_tool_usage_render(): "Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range" in rendered ) + + +def test_validate_tool_input_booleans_and_none(): + # Create a ToolUsage instance with mocks + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + original_tools=[], + tools_description="", + tools_names="", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + # Input with booleans and None + tool_input = '{"key1": True, "key2": False, "key3": None}' + expected_arguments = {"key1": True, "key2": False, "key3": None} + + arguments = tool_usage._validate_tool_input(tool_input) + assert arguments == expected_arguments + + +def test_validate_tool_input_mixed_types(): + # Create a ToolUsage instance with mocks + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + original_tools=[], + tools_description="", + tools_names="", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + # Input with mixed types + tool_input = '{"number": 123, "text": "Some text", "flag": True}' + expected_arguments = {"number": 123, "text": "Some text", "flag": True} + + arguments = tool_usage._validate_tool_input(tool_input) + assert arguments == expected_arguments + + +def test_validate_tool_input_single_quotes(): + # Create a ToolUsage instance with mocks + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + original_tools=[], + tools_description="", + tools_names="", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + # Input with single quotes instead of double quotes + tool_input = "{'key': 'value', 'flag': True}" + expected_arguments = {"key": "value", "flag": True} + + arguments = tool_usage._validate_tool_input(tool_input) + assert arguments == expected_arguments + + +def test_validate_tool_input_invalid_json_repairable(): + # Create a ToolUsage instance with mocks + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + original_tools=[], + tools_description="", + tools_names="", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + # Invalid JSON input that can be repaired + tool_input = '{"key": "value", "list": [1, 2, 3,]}' + expected_arguments = {"key": "value", "list": [1, 2, 3]} + + arguments = tool_usage._validate_tool_input(tool_input) + assert arguments == expected_arguments + + +def test_validate_tool_input_with_special_characters(): + # Create a ToolUsage instance with mocks + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[], + original_tools=[], + tools_description="", + tools_names="", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + # Input with special characters + tool_input = '{"message": "Hello, world! \u263A", "valid": True}' + expected_arguments = {"message": "Hello, world! ☺", "valid": True} + + arguments = tool_usage._validate_tool_input(tool_input) + assert arguments == expected_arguments