Compare commits

...

12 Commits

Author SHA1 Message Date
João Moura
70b04ab782 Merge branch 'main' into devin/1735586761-fix-hierarchical-delegation 2024-12-30 20:54:57 -03:00
Devin AI
1302c40607 Fix: Sort imports in test_hierarchical_delegation.py
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 20:29:33 +00:00
Devin AI
f9d11e5cd8 Fix: Update delegation tool handling in hierarchical mode for proper agent recognition
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 20:27:08 +00:00
Devin AI
c59c4afc16 Fix: Pass task context through delegation chain for proper coworker recognition
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 20:26:43 +00:00
devin-ai-integration[bot]
ba0965ef87 fix: add tiktoken as explicit dependency and document Rust requirement (#1826)
* feat: add tiktoken as explicit dependency and document Rust requirement

- Add tiktoken>=0.8.0 as explicit dependency to ensure pre-built wheels are used
- Document Rust compiler requirement as fallback in README.md
- Addresses issue #1824 tiktoken build failure

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: adjust tiktoken version to ~=0.7.0 for dependency compatibility

- Update tiktoken dependency to ~=0.7.0 to resolve conflict with embedchain
- Maintain compatibility with crewai-tools dependency chain
- Addresses CI build failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: add troubleshooting section and make tiktoken optional

Co-Authored-By: Joe Moura <joao@crewai.com>

* Update README.md

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
2024-12-30 17:10:56 -03:00
devin-ai-integration[bot]
d85898cf29 fix(manager_llm): handle coworker role name case/whitespace properly (#1820)
* fix(manager_llm): handle coworker role name case/whitespace properly

- Add .strip() to agent name and role comparisons in base_agent_tools.py
- Add test case for varied role name cases and whitespace
- Fix issue #1503 with manager LLM delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): improve error handling and add debug logging

- Add debug logging for better observability
- Add sanitize_agent_name helper method
- Enhance error messages with more context
- Add parameterized tests for edge cases:
  - Embedded quotes
  - Trailing newlines
  - Multiple whitespace
  - Case variations
  - None values
- Improve error handling with specific exceptions

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): improve whitespace normalization in role name matching

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in base_agent_tools and test_manager_llm_delegation

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix(manager_llm): add error message template for agent tool execution errors

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: fix import sorting in test_manager_llm_delegation.py

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
2024-12-30 16:58:18 -03:00
Devin AI
c08b4e5d9c Fix: Apply Ruff auto-fix for import formatting
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:39:18 +00:00
Devin AI
a9341b31f5 Fix: Add blank line between stdlib and third-party imports
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:35:05 +00:00
Devin AI
1153e8e498 Fix: Remove extra blank line in imports for test_hierarchical_delegation.py
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:33:19 +00:00
Devin AI
4764e114ad Fix: Alphabetize imports in test_hierarchical_delegation.py
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:31:02 +00:00
Devin AI
a59c13cb58 Fix: Sort imports in test_hierarchical_delegation.py
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:28:36 +00:00
Devin AI
0a137ca9dd Fix delegate coworker bug in hierarchical mode and add test
- Update _prepare_tools and _update_manager_tools to handle async execution in hierarchical mode
- Add test cases to verify delegation tool behavior
- Ensure proper tool updates during async task execution

Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-30 19:26:01 +00:00
11 changed files with 446 additions and 43 deletions

View File

@@ -85,7 +85,6 @@ First, install CrewAI:
```shell
pip install crewai
```
If you want to install the 'crewai' package along with its optional features that include additional tools for agents, you can do so by using the following command:
```shell
@@ -93,6 +92,22 @@ pip install 'crewai[tools]'
```
The command above installs the basic package and also adds extra components which require more dependencies to function.
### Troubleshooting Dependencies
If you encounter issues during installation or usage, here are some common solutions:
#### Common Issues
1. **ModuleNotFoundError: No module named 'tiktoken'**
- Install tiktoken explicitly: `pip install 'crewai[embeddings]'`
- If using embedchain or other tools: `pip install 'crewai[tools]'`
2. **Failed building wheel for tiktoken**
- Ensure Rust compiler is installed (see installation steps above)
- For Windows: Verify Visual C++ Build Tools are installed
- Try upgrading pip: `pip install --upgrade pip`
- If issues persist, use a pre-built wheel: `pip install tiktoken --prefer-binary`
### 2. Setting Up Your Crew with the YAML Configuration
To create a new CrewAI project, run the following CLI (Command Line Interface) command:

View File

@@ -8,27 +8,38 @@ authors = [
{ name = "Joao Moura", email = "joao@crewai.com" }
]
dependencies = [
# Core Dependencies
"pydantic>=2.4.2",
"openai>=1.13.3",
"litellm>=1.44.22",
"instructor>=1.3.3",
# Text Processing
"pdfplumber>=0.11.4",
"regex>=2024.9.11",
# Telemetry and Monitoring
"opentelemetry-api>=1.22.0",
"opentelemetry-sdk>=1.22.0",
"opentelemetry-exporter-otlp-proto-http>=1.22.0",
"instructor>=1.3.3",
"regex>=2024.9.11",
"click>=8.1.7",
# Data Handling
"chromadb>=0.5.23",
"openpyxl>=3.1.5",
"pyvis>=0.3.2",
# Authentication and Security
"auth0-python>=4.7.1",
"python-dotenv>=1.0.0",
# Configuration and Utils
"click>=8.1.7",
"appdirs>=1.4.4",
"jsonref>=1.1.0",
"json-repair>=0.25.2",
"auth0-python>=4.7.1",
"litellm>=1.44.22",
"pyvis>=0.3.2",
"uv>=0.4.25",
"tomli-w>=1.1.0",
"tomli>=2.0.2",
"chromadb>=0.5.23",
"pdfplumber>=0.11.4",
"openpyxl>=3.1.5",
"blinker>=1.9.0",
]
@@ -39,6 +50,9 @@ Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = ["crewai-tools>=0.17.0"]
embeddings = [
"tiktoken~=0.7.0"
]
agentops = ["agentops>=0.3.0"]
fastembed = ["fastembed>=0.4.1"]
pdfplumber = [

View File

@@ -406,8 +406,17 @@ class Agent(BaseAgent):
callbacks=[TokenCalcHandler(self._token_process)],
)
def get_delegation_tools(self, agents: List[BaseAgent]):
agent_tools = AgentTools(agents=agents)
def get_delegation_tools(self, agents: List[BaseAgent], task: Optional[Task] = None) -> List[BaseTool]:
"""Get the delegation tools for this agent.
Args:
agents: List of agents that can be delegated to
task: Optional task context for delegation
Returns:
List of delegation tools
"""
agent_tools = AgentTools(agents=agents, task=task, i18n=self.i18n)
tools = agent_tools.tools()
return tools

View File

@@ -232,7 +232,7 @@ class BaseAgent(ABC, BaseModel):
pass
@abstractmethod
def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[BaseTool]:
def get_delegation_tools(self, agents: List["BaseAgent"], task=None) -> List[BaseTool]:
"""Set the task tools that init BaseAgenTools class."""
pass

View File

@@ -798,15 +798,34 @@ class Crew(BaseModel):
return None
def _prepare_tools(self, agent: BaseAgent, task: Task, tools: List[Tool]) -> List[Tool]:
"""Prepare tools for an agent, including delegation tools if allowed.
Args:
agent: Agent that will receive the tools
task: Task being executed
tools: List of existing tools
Returns:
List of tools with delegation and other tools added
"""
# Add delegation tools if agent allows delegation
if agent.allow_delegation:
if self.process == Process.hierarchical:
if self.manager_agent:
tools = self._update_manager_tools(task, tools)
# For hierarchical process, handle both manager and regular agent tools
if agent == self.manager_agent:
# Manager can delegate to all regular agents
tools = self._inject_delegation_tools(tools, agent, [a for a in self.agents if a != agent], task=task)
elif task and task.async_execution and task.agent:
# For async tasks in hierarchical mode, only allow delegation to the task's assigned agent
tools = self._inject_delegation_tools(tools, agent, [task.agent], task=task)
else:
# Regular agents can delegate to manager and other agents
delegation_agents = [self.manager_agent] + [a for a in self.agents if a != agent]
tools = self._inject_delegation_tools(tools, agent, delegation_agents, task=task)
else:
raise ValueError("Manager agent is required for hierarchical process.")
elif agent and agent.allow_delegation:
else:
tools = self._add_delegation_tools(task, tools)
# Add code execution tools if agent allows code execution
@@ -839,8 +858,19 @@ class Crew(BaseModel):
return tools
def _inject_delegation_tools(self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]):
delegation_tools = task_agent.get_delegation_tools(agents)
def _inject_delegation_tools(self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent], task=None):
"""Inject delegation tools for the given agent.
Args:
tools: List of existing tools
task_agent: Agent that will receive the delegation tools
agents: List of agents that can be delegated to
task: Optional task context for delegation
Returns:
List of tools with delegation tools added
"""
delegation_tools = task_agent.get_delegation_tools(agents, task=task)
return self._merge_tools(tools, delegation_tools)
def _add_multimodal_tools(self, agent: BaseAgent, tools: List[Tool]):
@@ -852,8 +882,17 @@ class Crew(BaseModel):
return self._merge_tools(tools, code_tools)
def _add_delegation_tools(self, task: Task, tools: List[Tool]):
agents_for_delegation = [agent for agent in self.agents if agent != task.agent]
if len(self.agents) > 1 and len(agents_for_delegation) > 0 and task.agent:
agents_for_delegation = []
if self.process == Process.hierarchical and self.manager_agent:
# In hierarchical mode, allow delegation to manager if the task agent isn't the manager
if task.agent != self.manager_agent:
agents_for_delegation = [self.manager_agent]
# Also include regular agents for delegation
agents_for_delegation.extend([agent for agent in self.agents if agent != task.agent])
else:
agents_for_delegation = [agent for agent in self.agents if agent != task.agent]
if len(agents_for_delegation) > 0 and task.agent:
if not tools:
tools = []
tools = self._inject_delegation_tools(tools, task.agent, agents_for_delegation)
@@ -868,9 +907,23 @@ class Crew(BaseModel):
def _update_manager_tools(self, task: Task, tools: List[Tool]):
if self.manager_agent:
if task.agent:
tools = self._inject_delegation_tools(tools, task.agent, [task.agent])
# In hierarchical mode with async execution, only allow delegation to the task's assigned agent
if task.async_execution and self.process == Process.hierarchical:
tools = self._inject_delegation_tools(tools, task.agent, [task.agent] if task.agent else [])
else:
# In hierarchical mode, allow bidirectional delegation
if self.process == Process.hierarchical:
# For non-manager agents, allow delegation to manager and other agents
if task.agent != self.manager_agent:
delegation_agents = [self.manager_agent] + self.agents
tools = self._inject_delegation_tools(tools, task.agent, [a for a in delegation_agents if a != task.agent])
else:
# For manager, allow delegation to all agents
tools = self._inject_delegation_tools(tools, task.agent, [a for a in self.agents if a != task.agent])
else:
tools = self._inject_delegation_tools(tools, task.agent, self.agents, task=task)
else:
tools = self._inject_delegation_tools(tools, self.manager_agent, self.agents)
tools = self._inject_delegation_tools(tools, self.manager_agent, self.agents, task=task)
return tools
def _get_context(self, task: Task, task_outputs: List[TaskOutput]):

View File

@@ -9,24 +9,36 @@ from .delegate_work_tool import DelegateWorkTool
class AgentTools:
"""Manager class for agent-related tools"""
def __init__(self, agents: list[BaseAgent], i18n: I18N = I18N()):
def __init__(self, agents: list[BaseAgent], i18n: I18N = I18N(), task=None):
self.agents = agents
self.i18n = i18n
self.task = task
def tools(self) -> list[BaseTool]:
"""Get all available agent tools"""
coworkers = ", ".join([f"{agent.role}" for agent in self.agents])
# Format coworkers list based on agents and task context
if len(self.agents) == 1:
coworkers = self.agents[0].role
elif self.task and hasattr(self.task, 'async_execution') and self.task.async_execution and hasattr(self.task, 'agent') and self.task.agent:
# For async tasks with a specific agent, only show that agent
coworkers = self.task.agent.role
else:
# Show all agents for non-async tasks or when no specific agent is assigned
coworkers = ", ".join([agent.role for agent in self.agents])
# Ensure coworkers list doesn't have extra spaces or newlines
coworkers = coworkers.strip()
delegate_tool = DelegateWorkTool(
agents=self.agents,
i18n=self.i18n,
description=self.i18n.tools("delegate_work").format(coworkers=coworkers), # type: ignore
description=f"Delegate a specific task to one of the following coworkers: {coworkers}\n",
)
ask_tool = AskQuestionTool(
agents=self.agents,
i18n=self.i18n,
description=self.i18n.tools("ask_question").format(coworkers=coworkers), # type: ignore
description=f"Ask a specific question to one of the following coworkers: {coworkers}\n",
)
return [delegate_tool, ask_tool]

View File

@@ -1,3 +1,4 @@
import logging
from typing import Optional, Union
from pydantic import Field
@@ -7,6 +8,8 @@ from crewai.task import Task
from crewai.tools.base_tool import BaseTool
from crewai.utilities import I18N
logger = logging.getLogger(__name__)
class BaseAgentTool(BaseTool):
"""Base class for agent-related tools"""
@@ -16,6 +19,25 @@ class BaseAgentTool(BaseTool):
default_factory=I18N, description="Internationalization settings"
)
def sanitize_agent_name(self, name: str) -> str:
"""
Sanitize agent role name by normalizing whitespace and setting to lowercase.
Converts all whitespace (including newlines) to single spaces and removes quotes.
Args:
name (str): The agent role name to sanitize
Returns:
str: The sanitized agent role name, with whitespace normalized,
converted to lowercase, and quotes removed
"""
if not name:
return ""
# Normalize all whitespace (including newlines) to single spaces
normalized = " ".join(name.split())
# Remove quotes and convert to lowercase
return normalized.replace('"', "").casefold()
def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
if coworker:
@@ -25,11 +47,27 @@ class BaseAgentTool(BaseTool):
return coworker
def _execute(
self, agent_name: Union[str, None], task: str, context: Union[str, None]
self,
agent_name: Optional[str],
task: str,
context: Optional[str] = None
) -> str:
"""
Execute delegation to an agent with case-insensitive and whitespace-tolerant matching.
Args:
agent_name: Name/role of the agent to delegate to (case-insensitive)
task: The specific question or task to delegate
context: Optional additional context for the task execution
Returns:
str: The execution result from the delegated agent or an error message
if the agent cannot be found
"""
try:
if agent_name is None:
agent_name = ""
logger.debug("No agent name provided, using empty string")
# It is important to remove the quotes from the agent name.
# The reason we have to do this is because less-powerful LLM's
@@ -38,31 +76,49 @@ class BaseAgentTool(BaseTool):
# {"task": "....", "coworker": "....
# when it should look like this:
# {"task": "....", "coworker": "...."}
agent_name = agent_name.casefold().replace('"', "").replace("\n", "")
sanitized_name = self.sanitize_agent_name(agent_name)
logger.debug(f"Sanitized agent name from '{agent_name}' to '{sanitized_name}'")
available_agents = [agent.role for agent in self.agents]
logger.debug(f"Available agents: {available_agents}")
agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
available_agent
for available_agent in self.agents
if available_agent.role.casefold().replace("\n", "") == agent_name
if self.sanitize_agent_name(available_agent.role) == sanitized_name
]
except Exception as _:
logger.debug(f"Found {len(agent)} matching agents for role '{sanitized_name}'")
except (AttributeError, ValueError) as e:
# Handle specific exceptions that might occur during role name processing
return self.i18n.errors("agent_tool_unexisting_coworker").format(
coworkers="\n".join(
[f"- {agent.role.casefold()}" for agent in self.agents]
)
[f"- {self.sanitize_agent_name(agent.role)}" for agent in self.agents]
),
error=str(e)
)
if not agent:
# No matching agent found after sanitization
return self.i18n.errors("agent_tool_unexisting_coworker").format(
coworkers="\n".join(
[f"- {agent.role.casefold()}" for agent in self.agents]
)
[f"- {self.sanitize_agent_name(agent.role)}" for agent in self.agents]
),
error=f"No agent found with role '{sanitized_name}'"
)
agent = agent[0]
task_with_assigned_agent = Task( # type: ignore # Incompatible types in assignment (expression has type "Task", variable has type "str")
description=task,
agent=agent,
expected_output=agent.i18n.slice("manager_request"),
i18n=agent.i18n,
)
return agent.execute_task(task_with_assigned_agent, context)
try:
task_with_assigned_agent = Task(
description=task,
agent=agent,
expected_output=agent.i18n.slice("manager_request"),
i18n=agent.i18n,
)
logger.debug(f"Created task for agent '{self.sanitize_agent_name(agent.role)}': {task}")
return agent.execute_task(task_with_assigned_agent, context)
except Exception as e:
# Handle task creation or execution errors
return self.i18n.errors("agent_tool_execution_error").format(
agent_role=self.sanitize_agent_name(agent.role),
error=str(e)
)

View File

@@ -33,7 +33,8 @@
"tool_usage_error": "I encountered an error: {error}",
"tool_arguments_error": "Error: the Action Input is not a valid key, value dictionary.",
"wrong_tool_name": "You tried to use the tool {tool}, but it doesn't exist. You must use one of the following tools, use one at time: {tools}.",
"tool_usage_exception": "I encountered an error while trying to use the tool. This was the error: {error}.\n Tool {tool} accepts these inputs: {tool_inputs}"
"tool_usage_exception": "I encountered an error while trying to use the tool. This was the error: {error}.\n Tool {tool} accepts these inputs: {tool_inputs}",
"agent_tool_execution_error": "Error executing task with agent '{agent_role}'. Error: {error}"
},
"tools": {
"delegate_work": "Delegate a specific task to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the task you want them to do, and ALL necessary context to execute the task, they know nothing about the task, so share absolute everything you know, don't reference things but instead explain them.",

View File

@@ -43,7 +43,7 @@ writer = Agent(
role="Senior Writer",
goal="Write the best content about AI and AI agents.",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False,
allow_delegation=True,
)
@@ -391,6 +391,71 @@ def test_manager_agent_delegating_to_all_agents():
)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_manager_agent_delegates_with_varied_role_cases():
"""
Test that the manager agent can delegate to agents regardless of case or whitespace variations in role names.
This test verifies the fix for issue #1503 where role matching was too strict.
"""
# Create agents with varied case and whitespace in roles
researcher_spaced = Agent(
role=" Researcher ", # Extra spaces
goal="Research with spaces in role",
backstory="A researcher with spaces in role name",
allow_delegation=False,
)
writer_caps = Agent(
role="SENIOR WRITER", # All caps
goal="Write with caps in role",
backstory="A writer with caps in role name",
allow_delegation=False,
)
task = Task(
description="Research and write about AI. The researcher should do the research, and the writer should write it up.",
expected_output="A well-researched article about AI.",
agent=researcher_spaced, # Assign to researcher with spaces
)
crew = Crew(
agents=[researcher_spaced, writer_caps],
process=Process.hierarchical,
manager_llm="gpt-4o",
tasks=[task],
)
mock_task_output = TaskOutput(
description="Mock description",
raw="mocked output",
agent="mocked agent"
)
task.output = mock_task_output
with patch.object(Task, 'execute_sync', return_value=mock_task_output) as mock_execute_sync:
crew.kickoff()
# Verify execute_sync was called once
mock_execute_sync.assert_called_once()
# Get the tools argument from the call
_, kwargs = mock_execute_sync.call_args
tools = kwargs['tools']
# Verify the delegation tools were passed correctly and can handle case/whitespace variations
assert len(tools) == 2
# Check delegation tool descriptions (should work despite case/whitespace differences)
delegation_tool = tools[0]
question_tool = tools[1]
assert "Delegate a specific task to one of the following coworkers:" in delegation_tool.description
assert " Researcher " in delegation_tool.description or "SENIOR WRITER" in delegation_tool.description
assert "Ask a specific question to one of the following coworkers:" in question_tool.description
assert " Researcher " in question_tool.description or "SENIOR WRITER" in question_tool.description
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents():
tasks = [

View File

@@ -0,0 +1,123 @@
from unittest.mock import MagicMock
import pytest
from langchain_core.language_models.base import BaseLanguageModel
from crewai import Agent, Crew, Process, Task
def test_hierarchical_delegation_tool_availability():
"""Test that all agents are available for delegation in hierarchical mode."""
# Mock LLM to avoid actual API calls
mock_llm = MagicMock(spec=BaseLanguageModel)
# Create agents
manager = Agent(
role="Manager",
goal="Manage the team",
backstory="I am a manager",
allow_delegation=True,
llm=mock_llm
)
kb_agent = Agent(
role="kb_retriever_agent",
goal="Retrieve knowledge",
backstory="I am a knowledge retrieval specialist",
allow_delegation=True,
llm=mock_llm
)
worker = Agent(
role="Worker",
goal="Do the work",
backstory="I am a worker",
allow_delegation=True,
llm=mock_llm
)
# Create a task assigned to the manager
task = Task(
description="Complex task requiring delegation",
expected_output="Task completion status",
agent=manager
)
# Create the crew with hierarchical process
crew = Crew(
agents=[kb_agent, worker], # Manager should not be in agents list when using hierarchical process
tasks=[task],
process=Process.hierarchical,
manager_agent=manager # Explicitly set the manager agent for hierarchical process
)
# Get the manager's tools
tools_for_task = task.tools or manager.tools or []
manager_tools = crew._prepare_tools(manager, task, tools_for_task)
# Find delegation tools
delegation_tools = [tool for tool in manager_tools if tool.name == "Delegate work to coworker"]
assert len(delegation_tools) > 0, "Delegation tool should be present"
# Get the delegation tool description
delegate_tool = delegation_tools[0]
tool_description = str(delegate_tool.description)
# Verify all agents are available for delegation
assert "kb_retriever_agent" in tool_description, "kb_retriever_agent should be available for delegation"
assert "Worker" in tool_description, "Worker should be available for delegation"
def test_hierarchical_delegation_tool_updates():
"""Test that delegation tools are properly updated when task agent changes."""
mock_llm = MagicMock(spec=BaseLanguageModel)
manager = Agent(
role="Manager",
goal="Manage the team",
backstory="I am a manager",
allow_delegation=True,
llm=mock_llm
)
kb_agent = Agent(
role="kb_retriever_agent",
goal="Retrieve knowledge",
backstory="I am a knowledge retrieval specialist",
allow_delegation=True,
llm=mock_llm
)
# Create tasks for different agents
manager_task = Task(
description="Manager task",
expected_output="Manager task completion status",
agent=manager
)
kb_task = Task(
description="KB task",
expected_output="KB task completion status",
agent=kb_agent
)
# Create crew
crew = Crew(
agents=[kb_agent], # Manager should not be in agents list when using hierarchical process
tasks=[manager_task, kb_task],
process=Process.hierarchical,
manager_agent=manager # Explicitly set the manager agent for hierarchical process
)
# Test manager's tools
manager_tools_for_task = manager_task.tools or manager.tools or []
manager_tools = crew._prepare_tools(manager, manager_task, manager_tools_for_task)
manager_delegation = [t for t in manager_tools if t.name == "Delegate work to coworker"]
assert len(manager_delegation) > 0, "Manager should have delegation tool"
assert "kb_retriever_agent" in str(manager_delegation[0].description), "Manager should see kb_retriever_agent"
# Test kb_agent's tools
kb_tools_for_task = kb_task.tools or kb_agent.tools or []
kb_tools = crew._prepare_tools(kb_agent, kb_task, kb_tools_for_task)
kb_delegation = [t for t in kb_tools if t.name == "Delegate work to coworker"]
assert len(kb_delegation) > 0, "KB agent should have delegation tool"
assert "Manager" in str(kb_delegation[0].description), "KB agent should see manager"

View File

@@ -0,0 +1,55 @@
from unittest.mock import MagicMock
import pytest
from crewai import Agent, Task
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
class TestAgentTool(BaseAgentTool):
"""Concrete implementation of BaseAgentTool for testing."""
def _run(self, *args, **kwargs):
"""Implement required _run method."""
return "Test response"
@pytest.mark.parametrize("role_name,should_match", [
('Futel Official Infopoint', True), # exact match
(' "Futel Official Infopoint" ', True), # extra quotes and spaces
('Futel Official Infopoint\n', True), # trailing newline
('"Futel Official Infopoint"', True), # embedded quotes
(' FUTEL\nOFFICIAL INFOPOINT ', True), # multiple whitespace and newline
('futel official infopoint', True), # lowercase
('FUTEL OFFICIAL INFOPOINT', True), # uppercase
('Non Existent Agent', False), # non-existent agent
(None, False), # None agent name
])
def test_agent_tool_role_matching(role_name, should_match):
"""Test that agent tools can match roles regardless of case, whitespace, and special characters."""
# Create test agent
test_agent = Agent(
role='Futel Official Infopoint',
goal='Answer questions about Futel',
backstory='Futel Football Club info',
allow_delegation=False
)
# Create test agent tool
agent_tool = TestAgentTool(
name="test_tool",
description="Test tool",
agents=[test_agent]
)
# Test role matching
result = agent_tool._execute(
agent_name=role_name,
task='Test task',
context=None
)
if should_match:
assert "coworker mentioned not found" not in result.lower(), \
f"Should find agent with role name: {role_name}"
else:
assert "coworker mentioned not found" in result.lower(), \
f"Should not find agent with role name: {role_name}"