From aa1b915209ebeb21098fcd5113f0f1d01c2ffc4a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 00:31:04 +0000 Subject: [PATCH] Implement review suggestions: improved error handling, type validation, and edge case tests Co-Authored-By: Joe Moura --- src/crewai/tools/structured_tool.py | 28 +++++++++++++-- .../tools/test_structured_tool_nested_dict.py | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/crewai/tools/structured_tool.py b/src/crewai/tools/structured_tool.py index 43bcc0d97..9e9b2f1a4 100644 --- a/src/crewai/tools/structured_tool.py +++ b/src/crewai/tools/structured_tool.py @@ -173,11 +173,18 @@ class CrewStructuredTool: def _parse_args(self, raw_args: Union[str, dict]) -> dict: """Parse and validate the input arguments against the schema. + This method handles different input formats from various LLM providers, + including nested dictionaries with 'value' fields that some providers use. + Args: - raw_args: The raw arguments to parse, either as a string or dict + raw_args: The raw arguments to parse, either as a string or dict. + Supports nested dictionaries with 'value' field for LLM provider compatibility. Returns: The validated arguments as a dictionary + + Raises: + ValueError: If argument parsing or validation fails """ if isinstance(raw_args, str): try: @@ -194,8 +201,23 @@ class CrewStructuredTool: for field_name, field_value in list(raw_args.items()): # Check if this field exists in the schema if field_name in schema_fields: - if (isinstance(field_value, dict) and 'value' in field_value): - raw_args[field_name] = field_value['value'] + # Handle nested dictionaries with 'value' field + if isinstance(field_value, dict): + if 'value' in field_value: + # Extract the value from the nested dictionary + value = field_value['value'] + self._logger.debug(f"Extracting value from nested dict for {field_name}") + + expected_type = schema_fields[field_name].annotation + + if expected_type in (str, int, float, bool) and not isinstance(value, expected_type): + self._logger.warning( + f"Type mismatch for {field_name}: expected {expected_type}, got {type(value)}" + ) + + raw_args[field_name] = value + else: + self._logger.debug(f"Nested dict for {field_name} has no 'value' key") try: validated_args = self.args_schema.model_validate(raw_args) diff --git a/tests/tools/test_structured_tool_nested_dict.py b/tests/tools/test_structured_tool_nested_dict.py index 93c7b618b..23a34e2c7 100644 --- a/tests/tools/test_structured_tool_nested_dict.py +++ b/tests/tools/test_structured_tool_nested_dict.py @@ -137,3 +137,37 @@ def test_nested_dict_without_value_key(): invalid_input = {"query": {"description": "A string input parameter", "other_key": "test"}} with pytest.raises(ValueError): tool._parse_args(invalid_input) + + +def test_empty_nested_dict(): + """Test handling of empty nested dictionaries.""" + def test_func(query: str) -> str: + return f"Processed: {query}" + + tool = CrewStructuredTool.from_function( + func=test_func, + name="StringTool", + description="A tool that processes string input" + ) + + # Test with empty nested dict + empty_dict_input = {"query": {}} + with pytest.raises(ValueError): + tool._parse_args(empty_dict_input) + + +def test_deeply_nested_structure(): + """Test handling of deeply nested structures.""" + def test_func(query: str) -> str: + return f"Processed: {query}" + + tool = CrewStructuredTool.from_function( + func=test_func, + name="StringTool", + description="A tool that processes string input" + ) + + # Test with deeply nested structure + deeply_nested = {"query": {"nested": {"deeper": {"value": "deep value"}}}} + with pytest.raises(ValueError): + tool._parse_args(deeply_nested)