Files
crewAI/tests/test_custom_tools.py
Devin AI c06478afcc Fix lint and type-checker issues
- Add 'cast' import to fix mypy type compatibility error
- Remove unused imports to fix lint warnings
- Add assertions to reproduction script to use agent variables
- All custom tool patterns now work correctly

Co-Authored-By: João <joao@crewai.com>
2025-07-27 01:54:23 +00:00

222 lines
7.5 KiB
Python

"""
Test custom tool registration patterns to ensure all documented patterns work correctly.
This addresses issue #3226 where custom tool registration was broken in CrewAI 0.150.0.
"""
import pytest
from crewai import Agent
from crewai.tools import BaseTool, tool
class TestCustomToolPatterns:
"""Test all custom tool patterns mentioned in issue #3226."""
def test_function_tool_with_decorator(self):
"""Test function tool with @tool decorator."""
@tool
def fetch_logs(query: str) -> str:
"""Fetch logs from New Relic based on query"""
return f"Logs for query: {query}"
agent = Agent(
role='CrashFetcher',
goal='Extract logs',
backstory='An agent that fetches logs',
tools=[fetch_logs],
allow_delegation=False
)
assert len(agent.tools) == 1
assert agent.tools[0].name == "fetch_logs"
assert "Fetch logs from New Relic" in agent.tools[0].description
def test_dict_based_tool(self):
"""Test dict-based tool definition."""
def fetch_logs_func(query: str) -> str:
return f"Logs for query: {query}"
fetch_logs_dict = {
'name': 'fetch_logs',
'description': 'Fetch logs from New Relic',
'func': fetch_logs_func
}
agent = Agent(
role='CrashFetcher',
goal='Extract logs',
backstory='An agent that fetches logs',
tools=[fetch_logs_dict],
allow_delegation=False
)
assert len(agent.tools) == 1
assert agent.tools[0].name == "fetch_logs"
assert "Fetch logs from New Relic" in agent.tools[0].description
def test_basetool_class_inheritance(self):
"""Test BaseTool class inheritance."""
class FetchLogsTool(BaseTool):
name: str = "fetch_logs"
description: str = "Fetch logs from New Relic based on query"
def _run(self, query: str) -> str:
return f"Logs for query: {query}"
agent = Agent(
role='CrashFetcher',
goal='Extract logs',
backstory='An agent that fetches logs',
tools=[FetchLogsTool()],
allow_delegation=False
)
assert len(agent.tools) == 1
assert agent.tools[0].name == "fetch_logs"
assert "Fetch logs from New Relic" in agent.tools[0].description
def test_direct_function_assignment(self):
"""Test direct function assignment."""
def fetch_logs(query: str) -> str:
"""Fetch logs from New Relic based on query"""
return f"Logs for query: {query}"
agent = Agent(
role='CrashFetcher',
goal='Extract logs',
backstory='An agent that fetches logs',
tools=[fetch_logs],
allow_delegation=False
)
assert len(agent.tools) == 1
assert agent.tools[0].name == "fetch_logs"
assert "Fetch logs from New Relic" in agent.tools[0].description
def test_mixed_tool_types(self):
"""Test mixing different tool types in the same agent."""
@tool
def decorated_tool(query: str) -> str:
"""A decorated tool"""
return f"Decorated: {query}"
class ClassTool(BaseTool):
name: str = "class_tool"
description: str = "A class-based tool"
def _run(self, query: str) -> str:
return f"Class: {query}"
def function_tool(query: str) -> str:
"""A function tool"""
return f"Function: {query}"
dict_tool = {
'name': 'dict_tool',
'description': 'A dict-based tool',
'func': lambda query: f"Dict: {query}"
}
agent = Agent(
role='MultiTool',
goal='Use multiple tool types',
backstory='An agent with various tools',
tools=[decorated_tool, ClassTool(), function_tool, dict_tool],
allow_delegation=False
)
assert len(agent.tools) == 4
tool_names = [tool.name for tool in agent.tools]
assert "decorated_tool" in tool_names
assert "class_tool" in tool_names
assert "function_tool" in tool_names
assert "dict_tool" in tool_names
def test_invalid_tool_types(self):
"""Test that invalid tool types raise appropriate errors."""
with pytest.raises(ValueError, match="Invalid tool type"):
Agent(
role='Test',
goal='Test invalid tools',
backstory='Testing',
tools=["invalid_string_tool"],
allow_delegation=False
)
with pytest.raises(ValueError, match="Invalid tool type"):
Agent(
role='Test',
goal='Test invalid tools',
backstory='Testing',
tools=[123],
allow_delegation=False
)
def test_function_without_docstring_fails(self):
"""Test that functions without docstrings fail validation."""
def no_docstring_func(query: str) -> str:
return f"No docstring: {query}"
with pytest.raises(ValueError, match="must have a docstring"):
Agent(
role='Test',
goal='Test function without docstring',
backstory='Testing',
tools=[no_docstring_func],
allow_delegation=False
)
def test_incomplete_dict_tool_fails(self):
"""Test that dict tools missing required keys fail validation."""
incomplete_dict = {
'name': 'incomplete',
'description': 'Missing func key'
}
with pytest.raises(ValueError, match="Dict tool must contain keys"):
Agent(
role='Test',
goal='Test incomplete dict tool',
backstory='Testing',
tools=[incomplete_dict],
allow_delegation=False
)
def test_tool_execution(self):
"""Test that tools can actually be executed."""
@tool
def test_execution_tool(message: str) -> str:
"""A tool for testing execution"""
return f"Executed: {message}"
agent = Agent(
role='Executor',
goal='Execute tools',
backstory='An agent that executes tools',
tools=[test_execution_tool],
allow_delegation=False
)
tool_instance = agent.tools[0]
result = tool_instance.run(message="test")
assert result == "Executed: test"
def test_tool_with_multiple_parameters(self):
"""Test tools with multiple parameters work correctly."""
@tool
def multi_param_tool(param1: str, param2: int, param3: bool = True) -> str:
"""A tool with multiple parameters"""
return f"p1={param1}, p2={param2}, p3={param3}"
agent = Agent(
role='MultiParam',
goal='Use multi-parameter tools',
backstory='An agent with complex tools',
tools=[multi_param_tool],
allow_delegation=False
)
assert len(agent.tools) == 1
tool_instance = agent.tools[0]
result = tool_instance.run(param1="test", param2=42, param3=False)
assert result == "p1=test, p2=42, p3=False"