Add CrewAIPlatformTools (#449)

* chore: add deprecation warning in CrewaiEnterpriseTools

* feat: add CrewAI Platform Tool

* feat: drop support to oldest env-var token
This commit is contained in:
Lucas Gomide
2025-09-12 13:04:26 -03:00
committed by GitHub
parent 6f2301c945
commit f9925887aa
11 changed files with 917 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
import unittest
from unittest.mock import patch, Mock
import pytest
from crewai_tools.tools.crewai_platform_tools import CrewAIPlatformActionTool
class TestCrewAIPlatformActionTool(unittest.TestCase):
@pytest.fixture
def sample_action_schema(self):
return {
"function": {
"name": "test_action",
"description": "Test action for unit testing",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Message to send"
},
"priority": {
"type": "integer",
"description": "Priority level"
}
},
"required": ["message"]
}
}
}
@pytest.fixture
def platform_action_tool(self, sample_action_schema):
return CrewAIPlatformActionTool(
description="Test Action Tool\nTest description",
action_name="test_action",
action_schema=sample_action_schema
)
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
def test_run_success(self, mock_post):
schema = {
"function": {
"name": "test_action",
"description": "Test action",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Message"}
},
"required": ["message"]
}
}
}
tool = CrewAIPlatformActionTool(
description="Test tool",
action_name="test_action",
action_schema=schema
)
mock_response = Mock()
mock_response.ok = True
mock_response.json.return_value = {"result": "success", "data": "test_data"}
mock_post.return_value = mock_response
result = tool._run(message="test message")
mock_post.assert_called_once()
_, kwargs = mock_post.call_args
assert "test_action/execute" in kwargs["url"]
assert kwargs["headers"]["Authorization"] == "Bearer test_token"
assert kwargs["json"]["message"] == "test message"
assert "success" in result
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
def test_run_api_error(self, mock_post):
schema = {
"function": {
"name": "test_action",
"description": "Test action",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Message"}
},
"required": ["message"]
}
}
}
tool = CrewAIPlatformActionTool(
description="Test tool",
action_name="test_action",
action_schema=schema
)
mock_response = Mock()
mock_response.ok = False
mock_response.json.return_value = {"error": {"message": "Invalid request"}}
mock_post.return_value = mock_response
result = tool._run(message="test message")
assert "API request failed" in result
assert "Invalid request" in result
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
def test_run_exception(self, mock_post):
schema = {
"function": {
"name": "test_action",
"description": "Test action",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Message"}
},
"required": ["message"]
}
}
}
tool = CrewAIPlatformActionTool(
description="Test tool",
action_name="test_action",
action_schema=schema
)
mock_post.side_effect = Exception("Network error")
result = tool._run(message="test message")
assert "Error executing action test_action: Network error" in result
def test_run_without_token(self):
schema = {
"function": {
"name": "test_action",
"description": "Test action",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Message"}
},
"required": ["message"]
}
}
}
tool = CrewAIPlatformActionTool(
description="Test tool",
action_name="test_action",
action_schema=schema
)
with patch.dict("os.environ", {}, clear=True):
result = tool._run(message="test message")
assert "Error executing action test_action:" in result
assert "No platform integration token found" in result

View File

@@ -0,0 +1,223 @@
import unittest
from unittest.mock import patch, Mock
import pytest
from crewai_tools.tools.crewai_platform_tools import CrewaiPlatformToolBuilder, CrewAIPlatformActionTool
class TestCrewaiPlatformToolBuilder(unittest.TestCase):
@pytest.fixture
def platform_tool_builder(self):
"""Create a CrewaiPlatformToolBuilder instance for testing"""
return CrewaiPlatformToolBuilder(apps=["github", "slack"])
@pytest.fixture
def mock_api_response(self):
return {
"actions": {
"github": [
{
"name": "create_issue",
"description": "Create a GitHub issue",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Issue title"},
"body": {"type": "string", "description": "Issue body"}
},
"required": ["title"]
}
}
],
"slack": [
{
"name": "send_message",
"description": "Send a Slack message",
"parameters": {
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Channel name"},
"text": {"type": "string", "description": "Message text"}
},
"required": ["channel", "text"]
}
}
]
}
}
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get")
def test_fetch_actions_success(self, mock_get):
mock_api_response = {
"actions": {
"github": [
{
"name": "create_issue",
"description": "Create a GitHub issue",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Issue title"}
},
"required": ["title"]
}
}
]
}
}
builder = CrewaiPlatformToolBuilder(apps=["github", "slack/send_message"])
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = mock_api_response
mock_get.return_value = mock_response
builder._fetch_actions()
mock_get.assert_called_once()
args, kwargs = mock_get.call_args
assert "/actions" in args[0]
assert kwargs["headers"]["Authorization"] == "Bearer test_token"
assert kwargs["params"]["apps"] == "github,slack/send_message"
assert "create_issue" in builder._actions_schema
assert builder._actions_schema["create_issue"]["function"]["name"] == "create_issue"
def test_fetch_actions_no_token(self):
builder = CrewaiPlatformToolBuilder(apps=["github"])
with patch.dict("os.environ", {}, clear=True):
with self.assertRaises(ValueError) as context:
builder._fetch_actions()
assert "No platform integration token found" in str(context.exception)
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get")
def test_create_tools(self, mock_get):
mock_api_response = {
"actions": {
"github": [
{
"name": "create_issue",
"description": "Create a GitHub issue",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Issue title"}
},
"required": ["title"]
}
}
],
"slack": [
{
"name": "send_message",
"description": "Send a Slack message",
"parameters": {
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Channel name"}
},
"required": ["channel"]
}
}
]
}
}
builder = CrewaiPlatformToolBuilder(apps=["github", "slack"])
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = mock_api_response
mock_get.return_value = mock_response
tools = builder.tools()
assert len(tools) == 2
assert all(isinstance(tool, CrewAIPlatformActionTool) for tool in tools)
tool_names = [tool.action_name for tool in tools]
assert "create_issue" in tool_names
assert "send_message" in tool_names
github_tool = next((t for t in tools if t.action_name == "create_issue"), None)
slack_tool = next((t for t in tools if t.action_name == "send_message"), None)
assert github_tool is not None
assert slack_tool is not None
assert "Create a GitHub issue" in github_tool.description
assert "Send a Slack message" in slack_tool.description
def test_tools_caching(self):
builder = CrewaiPlatformToolBuilder(apps=["github"])
cached_tools = []
def mock_create_tools():
builder._tools = cached_tools
with patch.object(builder, '_fetch_actions') as mock_fetch, \
patch.object(builder, '_create_tools', side_effect=mock_create_tools) as mock_create:
tools1 = builder.tools()
assert mock_fetch.call_count == 1
assert mock_create.call_count == 1
tools2 = builder.tools()
assert mock_fetch.call_count == 1
assert mock_create.call_count == 1
assert tools1 is tools2
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
def test_empty_apps_list(self):
builder = CrewaiPlatformToolBuilder(apps=[])
with patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get") as mock_get:
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"actions": {}}
mock_get.return_value = mock_response
tools = builder.tools()
assert isinstance(tools, list)
assert len(tools) == 0
_, kwargs = mock_get.call_args
assert kwargs["params"]["apps"] == ""
def test_detailed_description_generation(self):
builder = CrewaiPlatformToolBuilder(apps=["test"])
complex_schema = {
"type": "object",
"properties": {
"simple_string": {"type": "string", "description": "A simple string"},
"nested_object": {
"type": "object",
"properties": {
"inner_prop": {"type": "integer", "description": "Inner property"}
},
"description": "Nested object"
},
"array_prop": {
"type": "array",
"items": {"type": "string"},
"description": "Array of strings"
}
}
}
descriptions = builder._generate_detailed_description(complex_schema)
assert isinstance(descriptions, list)
assert len(descriptions) > 0
description_text = "\n".join(descriptions)
assert "simple_string" in description_text
assert "nested_object" in description_text
assert "array_prop" in description_text

View File

@@ -0,0 +1,95 @@
import unittest
from unittest.mock import patch, Mock
from crewai_tools.tools.crewai_platform_tools import CrewaiPlatformTools
class TestCrewaiPlatformTools(unittest.TestCase):
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get")
def test_crewai_platform_tools_basic(self, mock_get):
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"actions": {"github": []}}
mock_get.return_value = mock_response
tools = CrewaiPlatformTools(apps=["github"])
assert tools is not None
assert isinstance(tools, list)
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get")
def test_crewai_platform_tools_multiple_apps(self, mock_get):
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {
"actions": {
"github": [
{
"name": "create_issue",
"description": "Create a GitHub issue",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Issue title"},
"body": {"type": "string", "description": "Issue body"}
},
"required": ["title"]
}
}
],
"slack": [
{
"name": "send_message",
"description": "Send a Slack message",
"parameters": {
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Channel to send to"},
"text": {"type": "string", "description": "Message text"}
},
"required": ["channel", "text"]
}
}
]
}
}
mock_get.return_value = mock_response
tools = CrewaiPlatformTools(apps=["github", "slack"])
assert tools is not None
assert isinstance(tools, list)
assert len(tools) == 2
mock_get.assert_called_once()
args, kwargs = mock_get.call_args
assert "apps=github,slack" in args[0] or kwargs.get("params", {}).get("apps") == "github,slack"
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
def test_crewai_platform_tools_empty_apps(self):
with patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get") as mock_get:
mock_response = Mock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"actions": {}}
mock_get.return_value = mock_response
tools = CrewaiPlatformTools(apps=[])
assert tools is not None
assert isinstance(tools, list)
assert len(tools) == 0
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"})
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get")
def test_crewai_platform_tools_api_error_handling(self, mock_get):
mock_get.side_effect = Exception("API Error")
tools = CrewaiPlatformTools(apps=["github"])
assert tools is not None
assert isinstance(tools, list)
assert len(tools) == 0
def test_crewai_platform_tools_no_token(self):
with patch.dict("os.environ", {}, clear=True):
with self.assertRaises(ValueError) as context:
CrewaiPlatformTools(apps=["github"])
assert "No platform integration token found" in str(context.exception)