mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
- 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>
222 lines
7.5 KiB
Python
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"
|