From aa1d04af419e661c326ca61f94bac5f15324a35d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:15:40 +0000 Subject: [PATCH] fix: allow CrewStructuredTool to be used with Task's tools parameter Co-Authored-By: Joe Moura --- src/crewai/task.py | 9 ++- src/crewai/utilities/agent_utils.py | 6 +- tests/tools/test_task_with_structured_tool.py | 62 +++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/tools/test_task_with_structured_tool.py diff --git a/src/crewai/task.py b/src/crewai/task.py index 9874b5100..1c3baa6d6 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -40,6 +40,7 @@ from crewai.tasks.guardrail_result import GuardrailResult from crewai.tasks.output_format import OutputFormat from crewai.tasks.task_output import TaskOutput from crewai.tools.base_tool import BaseTool +from crewai.tools.structured_tool import CrewStructuredTool from crewai.utilities.config import process_config from crewai.utilities.converter import Converter, convert_to_model from crewai.utilities.events import ( @@ -72,7 +73,11 @@ class Task(BaseModel): security_config: Security configuration including fingerprinting. tools: List of tools/resources limited for task execution. """ - + + model_config = { + "arbitrary_types_allowed": True, + } + __hash__ = object.__hash__ # type: ignore logger: ClassVar[logging.Logger] = logging.getLogger(__name__) used_tools: int = 0 @@ -118,7 +123,7 @@ class Task(BaseModel): output: Optional[TaskOutput] = Field( description="Task output, it's final result after being executed", default=None ) - tools: Optional[List[BaseTool]] = Field( + tools: Optional[List[Union[BaseTool, CrewStructuredTool]]] = Field( default_factory=list, description="Tools the agent is limited to use for this task.", ) diff --git a/src/crewai/utilities/agent_utils.py b/src/crewai/utilities/agent_utils.py index 8af665140..f2d54719d 100644 --- a/src/crewai/utilities/agent_utils.py +++ b/src/crewai/utilities/agent_utils.py @@ -21,15 +21,17 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( ) -def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]: +def parse_tools(tools: List[Union[BaseTool, CrewStructuredTool]]) -> List[CrewStructuredTool]: """Parse tools to be used for the task.""" tools_list = [] for tool in tools: if isinstance(tool, CrewAITool): tools_list.append(tool.to_structured_tool()) + elif isinstance(tool, CrewStructuredTool): + tools_list.append(tool) else: - raise ValueError("Tool is not a CrewStructuredTool or BaseTool") + raise ValueError("Tool must be an instance of BaseTool or CrewStructuredTool") return tools_list diff --git a/tests/tools/test_task_with_structured_tool.py b/tests/tools/test_task_with_structured_tool.py new file mode 100644 index 000000000..b3b0de245 --- /dev/null +++ b/tests/tools/test_task_with_structured_tool.py @@ -0,0 +1,62 @@ +import pytest +from pydantic import BaseModel, Field + +from crewai.task import Task +from crewai.tools.structured_tool import CrewStructuredTool + + +@pytest.fixture +def simple_tool_function(): + def test_func(param1: str, param2: int = 0) -> str: + """Test function with basic params.""" + return f"{param1} {param2}" + + return test_func + + +def test_task_with_structured_tool(simple_tool_function): + """Test that CrewStructuredTool can be used directly with Task.""" + tool = CrewStructuredTool.from_function( + func=simple_tool_function, + name="test_tool", + description="Test tool description" + ) + + task = Task( + description="Test task description", + expected_output="Expected output", + tools=[tool] + ) + + assert len(task.tools) == 1 + assert task.tools[0] == tool + + +def test_mixed_tool_types(simple_tool_function): + """Test that both BaseTool and CrewStructuredTool can be used together with Task.""" + from crewai.tools import BaseTool + + structured_tool = CrewStructuredTool.from_function( + func=simple_tool_function, + name="structured_tool", + description="Structured tool description" + ) + + class TestBaseTool(BaseTool): + name: str = "base_tool" + description: str = "Base tool description" + + def _run(self, query: str) -> str: + return f"Result for {query}" + + base_tool = TestBaseTool() + + task = Task( + description="Test task description", + expected_output="Expected output", + tools=[structured_tool, base_tool] + ) + + assert len(task.tools) == 2 + assert task.tools[0] == structured_tool + assert isinstance(task.tools[1], BaseTool)