mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-13 01:58:30 +00:00
feat: add apps & actions attributes to Agent (#3504)
* feat: add app attributes to Agent * feat: add actions attribute to Agent * chore: resolve linter issues * refactor: merge the apps and actions parameters into a single one * fix: remove unnecessary print * feat: logging error when CrewaiPlatformTools fails * chore: export CrewaiPlatformTools directly from crewai_tools * style: resolver linter issues * test: fix broken tests * style: solve linter issues * fix: fix broken test
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
@@ -12,7 +12,7 @@ from crewai.utilities.token_counter_callback import TokenProcess
|
||||
# Concrete implementation for testing
|
||||
class ConcreteAgentAdapter(BaseAgentAdapter):
|
||||
def configure_tools(
|
||||
self, tools: Optional[List[BaseTool]] = None, **kwargs: Any
|
||||
self, tools: list[BaseTool] | None = None, **kwargs: Any
|
||||
) -> None:
|
||||
# Simple implementation for testing
|
||||
self.tools = tools or []
|
||||
@@ -20,19 +20,19 @@ class ConcreteAgentAdapter(BaseAgentAdapter):
|
||||
def execute_task(
|
||||
self,
|
||||
task: Any,
|
||||
context: Optional[str] = None,
|
||||
tools: Optional[List[Any]] = None,
|
||||
context: str | None = None,
|
||||
tools: list[Any] | None = None,
|
||||
) -> str:
|
||||
# Dummy implementation needed due to BaseAgent inheritance
|
||||
return "Task executed"
|
||||
|
||||
def create_agent_executor(self, tools: Optional[List[BaseTool]] = None) -> Any:
|
||||
def create_agent_executor(self, tools: list[BaseTool] | None = None) -> Any:
|
||||
# Dummy implementation
|
||||
return None
|
||||
|
||||
def get_delegation_tools(
|
||||
self, tools: List[BaseTool], tool_map: Optional[Dict[str, BaseTool]]
|
||||
) -> List[BaseTool]:
|
||||
self, tools: list[BaseTool], tool_map: dict[str, BaseTool] | None
|
||||
) -> list[BaseTool]:
|
||||
# Dummy implementation
|
||||
return []
|
||||
|
||||
@@ -40,10 +40,14 @@ class ConcreteAgentAdapter(BaseAgentAdapter):
|
||||
# Dummy implementation
|
||||
pass
|
||||
|
||||
def get_output_converter(self, tools: Optional[List[BaseTool]] = None) -> Any:
|
||||
def get_output_converter(self, tools: list[BaseTool] | None = None) -> Any:
|
||||
# Dummy implementation
|
||||
return None
|
||||
|
||||
def get_platform_tools(self, apps: Any) -> list[BaseTool]:
|
||||
# Dummy implementation
|
||||
return []
|
||||
|
||||
|
||||
def test_base_agent_adapter_initialization():
|
||||
"""Test initialization of the concrete agent adapter."""
|
||||
@@ -95,7 +99,6 @@ def test_configure_structured_output_method_exists():
|
||||
adapter.configure_structured_output(structured_output)
|
||||
# Add assertions here if configure_structured_output modifies state
|
||||
# For now, just ensuring it runs without error is sufficient
|
||||
pass
|
||||
|
||||
|
||||
def test_base_agent_adapter_inherits_base_agent():
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import hashlib
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -11,14 +11,16 @@ class MockAgent(BaseAgent):
|
||||
def execute_task(
|
||||
self,
|
||||
task: Any,
|
||||
context: Optional[str] = None,
|
||||
tools: Optional[List[BaseTool]] = None,
|
||||
context: str | None = None,
|
||||
tools: list[BaseTool] | None = None,
|
||||
) -> str:
|
||||
return ""
|
||||
|
||||
def create_agent_executor(self, tools=None) -> None: ...
|
||||
|
||||
def get_delegation_tools(self, agents: List["BaseAgent"]): ...
|
||||
def get_delegation_tools(self, agents: list["BaseAgent"]): ...
|
||||
|
||||
def get_platform_tools(self, apps: list[Any]): ...
|
||||
|
||||
def get_output_converter(
|
||||
self, llm: Any, text: str, model: type[BaseModel] | None, instructions: str
|
||||
@@ -31,5 +33,5 @@ def test_key():
|
||||
goal="test goal",
|
||||
backstory="test backstory",
|
||||
)
|
||||
hash = hashlib.md5("test role|test goal|test backstory".encode()).hexdigest()
|
||||
hash = hashlib.md5("test role|test goal|test backstory".encode(), usedforsecurity=False).hexdigest()
|
||||
assert agent.key == hash
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Test Agent creation and execution basic functionality."""
|
||||
|
||||
# ruff: noqa: S106
|
||||
import os
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -2368,7 +2369,7 @@ def test_agent_from_repository(mock_get_agent, mock_get_auth_token):
|
||||
tool_action = EnterpriseActionTool(
|
||||
name="test_name",
|
||||
description="test_description",
|
||||
enterprise_action_token="test_token", # noqa: S106
|
||||
enterprise_action_token="test_token",
|
||||
action_name="test_action_name",
|
||||
action_schema={"test": "test"},
|
||||
)
|
||||
@@ -2522,3 +2523,132 @@ def test_agent_from_repository_without_org_set(
|
||||
"No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.",
|
||||
style="yellow",
|
||||
)
|
||||
|
||||
def test_agent_apps_consolidated_functionality():
|
||||
agent = Agent(
|
||||
role="Platform Agent",
|
||||
goal="Use platform tools",
|
||||
backstory="Platform specialist",
|
||||
apps=["gmail/create_task", "slack/update_status", "hubspot"]
|
||||
)
|
||||
expected = {"gmail/create_task", "slack/update_status", "hubspot"}
|
||||
assert set(agent.apps) == expected
|
||||
|
||||
agent_apps_only = Agent(
|
||||
role="App Agent",
|
||||
goal="Use apps",
|
||||
backstory="App specialist",
|
||||
apps=["gmail", "slack"]
|
||||
)
|
||||
assert set(agent_apps_only.apps) == {"gmail", "slack"}
|
||||
|
||||
agent_default = Agent(
|
||||
role="Regular Agent",
|
||||
goal="Regular tasks",
|
||||
backstory="Regular agent"
|
||||
)
|
||||
assert agent_default.apps is None
|
||||
|
||||
|
||||
def test_agent_apps_validation():
|
||||
agent = Agent(
|
||||
role="Custom Agent",
|
||||
goal="Test validation",
|
||||
backstory="Test agent",
|
||||
apps=["custom_app", "another_app/action"]
|
||||
)
|
||||
assert set(agent.apps) == {"custom_app", "another_app/action"}
|
||||
|
||||
with pytest.raises(ValueError, match=r"Invalid app format.*Apps can only have one '/' for app/action format"):
|
||||
Agent(
|
||||
role="Invalid Agent",
|
||||
goal="Test validation",
|
||||
backstory="Test agent",
|
||||
apps=["app/action/invalid"]
|
||||
)
|
||||
|
||||
|
||||
@patch.object(Agent, 'get_platform_tools')
|
||||
def test_app_actions_propagated_to_platform_tools(mock_get_platform_tools):
|
||||
from crewai.tools import tool
|
||||
|
||||
@tool
|
||||
def action_tool() -> str:
|
||||
"""Mock action platform tool."""
|
||||
return "action tool result"
|
||||
|
||||
mock_get_platform_tools.return_value = [action_tool]
|
||||
|
||||
agent = Agent(
|
||||
role="Action Agent",
|
||||
goal="Execute actions",
|
||||
backstory="Action specialist",
|
||||
apps=["gmail/send_email", "slack/update_status"]
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
tools = crew._prepare_tools(agent, task, [])
|
||||
|
||||
mock_get_platform_tools.assert_called_once()
|
||||
call_args = mock_get_platform_tools.call_args[1]
|
||||
assert set(call_args["apps"]) == {"gmail/send_email", "slack/update_status"}
|
||||
assert len(tools) >= 1
|
||||
|
||||
|
||||
@patch.object(Agent, 'get_platform_tools')
|
||||
def test_mixed_apps_and_actions_propagated(mock_get_platform_tools):
|
||||
from crewai.tools import tool
|
||||
|
||||
@tool
|
||||
def combined_tool() -> str:
|
||||
"""Mock combined platform tool."""
|
||||
return "combined tool result"
|
||||
|
||||
mock_get_platform_tools.return_value = [combined_tool]
|
||||
|
||||
agent = Agent(
|
||||
role="Combined Agent",
|
||||
goal="Use apps and actions",
|
||||
backstory="Platform specialist",
|
||||
apps=["gmail", "slack", "gmail/create_task", "slack/update_status"]
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
tools = crew._prepare_tools(agent, task, [])
|
||||
|
||||
mock_get_platform_tools.assert_called_once()
|
||||
call_args = mock_get_platform_tools.call_args[1]
|
||||
expected_apps = {"gmail", "slack", "gmail/create_task", "slack/update_status"}
|
||||
assert set(call_args["apps"]) == expected_apps
|
||||
assert len(tools) >= 1
|
||||
|
||||
def test_agent_without_apps_no_platform_tools():
|
||||
"""Test that agents without apps don't trigger platform tools integration."""
|
||||
agent = Agent(
|
||||
role="Regular Agent",
|
||||
goal="Regular tasks",
|
||||
backstory="Regular agent"
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
agent=agent
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
|
||||
tools = crew._prepare_tools(agent, task, [])
|
||||
assert tools == []
|
||||
|
||||
Reference in New Issue
Block a user