From f232f11ad9d10dc69609d57bf5d6524ccef98fc0 Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Fri, 14 Mar 2025 10:31:18 -0400 Subject: [PATCH] getting ready --- src/crewai/agent.py | 1 - src/crewai/crew.py | 44 ++++---- src/crewai/tools/base_tool.py | 4 +- tests/crew_test.py | 199 ++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 24 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 7e150c016..c037164a8 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -227,7 +227,6 @@ class Agent(BaseAgent): task_prompt += crew_knowledge_context tools = tools or self.tools or [] - print(f"tools: {tools}") self.create_agent_executor(tools=tools, task=task) if self.crew and self.crew._train: diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 4934398a9..fba023340 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -739,14 +739,15 @@ class Crew(BaseModel): def _create_manager_agent(self): i18n = I18N(prompt_file=self.prompt_file) if self.manager_agent is not None: + # Ensure delegation is enabled for the manager agent self.manager_agent.allow_delegation = True - # Set the delegate_to property to all agents in the crew - self.manager_agent.delegate_to = self.agents - manager = self.manager_agent - # Instead, we ensure it has delegation tools - if not manager.allow_delegation: - manager.allow_delegation = True + # Set the delegate_to property to all agents in the crew + # If delegate_to is already set, it will be used instead of all agents + if self.manager_agent.delegate_to is None: + self.manager_agent.delegate_to = self.agents + + manager = self.manager_agent else: self.manager_llm = create_llm(self.manager_llm) # Create delegation tools @@ -802,11 +803,8 @@ class Crew(BaseModel): ) # Determine which tools to use - task tools take precedence over agent tools - tools_for_task: Sequence[BaseTool] = task.tools or agent_to_use.tools or [] - # Ensure tools_for_task is a Sequence[BaseTool] - if not isinstance(tools_for_task, Sequence): - tools_for_task = list(tools_for_task) if tools_for_task else [] - tools_for_task = self._prepare_tools(agent_to_use, task, tools_for_task) + initial_tools = task.tools or agent_to_use.tools or [] + prepared_tools = self._prepare_tools(agent_to_use, task, initial_tools) self._log_task_start(task, agent_to_use.role) @@ -825,7 +823,7 @@ class Crew(BaseModel): future = task.execute_async( agent=agent_to_use, context=context, - tools=tools_for_task, + tools=prepared_tools, ) futures.append((task, future, task_index)) else: @@ -837,7 +835,7 @@ class Crew(BaseModel): task_output = task.execute_sync( agent=agent_to_use, context=context, - tools=tools_for_task, + tools=prepared_tools, ) task_outputs.append(task_output) self._process_task_result(task, task_output) @@ -891,13 +889,15 @@ class Crew(BaseModel): tools = self._add_delegation_tools(task, tools) # Add code execution tools if agent allows code execution - if hasattr(agent, "allow_code_execution") and agent.allow_code_execution: + if hasattr(agent, "allow_code_execution") and getattr( + agent, "allow_code_execution", False + ): tools = self._add_code_execution_tools(agent, tools) - if hasattr(agent, "multimodal") and agent.multimodal: + if hasattr(agent, "multimodal") and getattr(agent, "multimodal", False): tools = self._add_multimodal_tools(agent, tools) - return list(tools) # Ensure we return a list, not just a Sequence + return list(tools) def _get_agent_to_use(self, task: Task) -> Optional[BaseAgent]: if self.process == Process.hierarchical: @@ -934,14 +934,18 @@ class Crew(BaseModel): def _add_multimodal_tools( self, agent: BaseAgent, tools: Sequence[BaseTool] ) -> Sequence[BaseTool]: - multimodal_tools = agent.get_multimodal_tools() - return self._merge_tools(tools, multimodal_tools) + if hasattr(agent, "get_multimodal_tools"): + multimodal_tools = getattr(agent, "get_multimodal_tools")() + return self._merge_tools(tools, multimodal_tools) + return tools def _add_code_execution_tools( self, agent: BaseAgent, tools: Sequence[BaseTool] ) -> Sequence[BaseTool]: - code_tools = agent.get_code_execution_tools() - return self._merge_tools(tools, code_tools) + if hasattr(agent, "get_code_execution_tools"): + code_tools = getattr(agent, "get_code_execution_tools")() + return self._merge_tools(tools, code_tools) + return tools def _add_delegation_tools( self, task: Task, tools: Sequence[BaseTool] diff --git a/src/crewai/tools/base_tool.py b/src/crewai/tools/base_tool.py index f4825fb1d..35eafe051 100644 --- a/src/crewai/tools/base_tool.py +++ b/src/crewai/tools/base_tool.py @@ -283,12 +283,10 @@ def tool(*args): return _make_tool if len(args) == 1 and callable(args[0]): - # Direct function decoration if isinstance(args[0], BaseTool): - return args[0] # Already a BaseTool, return as-is + return args[0] return _make_with_name(args[0].__name__)(args[0]) elif len(args) == 1 and isinstance(args[0], str): - # Name provided, return a decorator return _make_with_name(args[0]) else: raise ValueError("Invalid arguments") diff --git a/tests/crew_test.py b/tests/crew_test.py index cf9aac416..7e2b4d786 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -4270,3 +4270,202 @@ def test_crew_with_default_delegation(): assert "Researcher" in ask_tool.description assert "Writer" in ask_tool.description assert "Editor" in ask_tool.description + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_update_manager_tools_functionality(): + """Test that _update_manager_tools correctly adds delegation tools to the manager agent.""" + # Create agents + researcher = Agent( + role="Researcher", + goal="Research information", + backstory="You're an expert researcher", + ) + + writer = Agent( + role="Writer", + goal="Write content", + backstory="You're an expert writer", + ) + + # Create a manager agent + manager = Agent( + role="Manager", + goal="Manage the team", + backstory="You're an expert manager", + allow_delegation=True, + ) + + # Create a crew with the manager agent + crew = Crew( + agents=[researcher, writer], + manager_agent=manager, + process=Process.hierarchical, + ) + + # Ensure the manager agent is set up + crew._create_manager_agent() + + # Case 1: Task with an assigned agent + task_with_agent = Task( + description="Research a topic", + expected_output="Research results", + agent=researcher, + ) + + # Create an initial set of tools + from crewai.tools.base_tool import BaseTool + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool" + + def _run(self) -> str: + return "Tool executed" + + initial_tools = [TestTool()] + + # Test _update_manager_tools with a task that has an agent + updated_tools = crew._update_manager_tools(task_with_agent, initial_tools) + + # Verify that delegation tools for the task's agent were added + assert len(updated_tools) > len(initial_tools) + assert any( + f"Delegate a specific task to one of the following coworkers: {researcher.role}" + in tool.description + for tool in updated_tools + ) + assert any( + f"Ask a specific question to one of the following coworkers: {researcher.role}" + in tool.description + for tool in updated_tools + ) + + # Case 2: Task without an assigned agent + task_without_agent = Task( + description="General task", + expected_output="Task completed", + ) + + # Test _update_manager_tools with a task that doesn't have an agent + updated_tools = crew._update_manager_tools(task_without_agent, initial_tools) + + # Verify that delegation tools for all agents were added + assert len(updated_tools) > len(initial_tools) + assert any( + f"Delegate a specific task to one of the following coworkers: {researcher.role}, {writer.role}" + in tool.description + for tool in updated_tools + ) + assert any( + f"Ask a specific question to one of the following coworkers: {researcher.role}, {writer.role}" + in tool.description + for tool in updated_tools + ) + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_manager_tools_during_task_execution(): + """Test that manager tools are correctly added during task execution in a hierarchical process.""" + # Create agents + researcher = Agent( + role="Researcher", + goal="Research information", + backstory="You're an expert researcher", + ) + + writer = Agent( + role="Writer", + goal="Write content", + backstory="You're an expert writer", + ) + + # Create tasks + task_with_agent = Task( + description="Research a topic", + expected_output="Research results", + agent=researcher, + ) + + task_without_agent = Task( + description="General task", + expected_output="Task completed", + ) + + # Create a crew with hierarchical process + crew_with_agent_task = Crew( + agents=[researcher, writer], + tasks=[task_with_agent], + process=Process.hierarchical, + manager_llm="gpt-4o", + ) + + crew_without_agent_task = Crew( + agents=[researcher, writer], + tasks=[task_without_agent], + process=Process.hierarchical, + manager_llm="gpt-4o", + ) + + # Mock task execution to capture the tools + mock_task_output = TaskOutput( + description="Mock description", raw="mocked output", agent="mocked agent" + ) + + # Test case 1: Task with an assigned agent + with patch.object( + Task, "execute_sync", return_value=mock_task_output + ) as mock_execute_sync: + # Set the output attribute to avoid None errors + task_with_agent.output = mock_task_output + + # Execute the crew + crew_with_agent_task.kickoff() + + # Verify execute_sync was called + mock_execute_sync.assert_called_once() + + # Get the tools argument from the call + _, kwargs = mock_execute_sync.call_args + tools = kwargs["tools"] + + # Verify that delegation tools for the task's agent were added + assert any( + f"Delegate a specific task to one of the following coworkers: {researcher.role}" + in tool.description + for tool in tools + ) + assert any( + f"Ask a specific question to one of the following coworkers: {researcher.role}" + in tool.description + for tool in tools + ) + + # Test case 2: Task without an assigned agent + with patch.object( + Task, "execute_sync", return_value=mock_task_output + ) as mock_execute_sync: + # Set the output attribute to avoid None errors + task_without_agent.output = mock_task_output + + # Execute the crew + crew_without_agent_task.kickoff() + + # Verify execute_sync was called + mock_execute_sync.assert_called_once() + + # Get the tools argument from the call + _, kwargs = mock_execute_sync.call_args + tools = kwargs["tools"] + + # Verify that delegation tools for all agents were added + assert any( + f"Delegate a specific task to one of the following coworkers: {researcher.role}, {writer.role}" + in tool.description + for tool in tools + ) + assert any( + f"Ask a specific question to one of the following coworkers: {researcher.role}, {writer.role}" + in tool.description + for tool in tools + )