mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-07 07:59:29 +00:00
fix(agent): warn when non-empty apps resolve to zero platform tools
When an agent declares `apps` (enterprise integrations) but they resolve to no tools — unknown/misspelled app, app not connected, no actions, or a missing CREWAI_PLATFORM_INTEGRATION_TOKEN — the agent silently runs without them and can't do what those apps were for. CrewaiPlatformTools returns an empty list in all those cases, and both resolution sites dropped it silently (agent kickoff `if platform_tools:` and crew `_inject_platform_tools` merge), while the only existing signal is a verbose-gated Logger.log. Emit a loud UserWarning from Agent.get_platform_tools() — the shared choke point for the agent-kickoff path (core.py:_prepare_kickoff), the crew task path (crew.py:_inject_platform_tools), and declarative flows (agent actions route through the same kickoff). The warning names the unresolved apps and the likely causes. Tests: warns on zero-tool resolution (direct + crew-prepare paths), stays quiet when tools resolve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
This commit is contained in:
@@ -1129,15 +1129,32 @@ class Agent(BaseAgent):
|
||||
return agent_tools.tools()
|
||||
|
||||
def get_platform_tools(self, apps: list[PlatformAppOrAction]) -> list[BaseTool]:
|
||||
platform_tools: list[BaseTool] = []
|
||||
try:
|
||||
from crewai_tools import (
|
||||
CrewaiPlatformTools,
|
||||
)
|
||||
|
||||
return CrewaiPlatformTools(apps=apps)
|
||||
platform_tools = CrewaiPlatformTools(apps=apps)
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Error getting platform tools: {e!s}")
|
||||
return []
|
||||
|
||||
if apps and not platform_tools:
|
||||
# A non-empty `apps` that resolves to zero tools leaves the agent
|
||||
# unable to do what those apps were for — surface it loudly (the
|
||||
# underlying resolver only logs, and Logger.log is verbose-gated) so
|
||||
# it isn't discovered as a mysterious runtime failure.
|
||||
app_names = ", ".join(str(app) for app in apps)
|
||||
warnings.warn(
|
||||
f"Agent '{self.role}' declares apps [{app_names}] but none "
|
||||
"resolved to any tools; the agent will run without them and may "
|
||||
"be unable to complete its goal. Check that these apps exist and "
|
||||
"are connected, and that CREWAI_PLATFORM_INTEGRATION_TOKEN is set.",
|
||||
UserWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
return platform_tools
|
||||
|
||||
def get_mcp_tools(self, mcps: list[str | MCPServerConfig]) -> list[BaseTool]:
|
||||
"""Convert MCP server references/configs to CrewAI tools.
|
||||
|
||||
@@ -2595,6 +2595,59 @@ def test_agent_without_apps_no_platform_tools():
|
||||
assert tools == []
|
||||
|
||||
|
||||
def test_get_platform_tools_warns_when_apps_resolve_to_zero_tools(monkeypatch):
|
||||
"""A non-empty `apps` that resolves to no tools must warn loudly."""
|
||||
import crewai_tools
|
||||
|
||||
monkeypatch.setattr(crewai_tools, "CrewaiPlatformTools", lambda apps: [])
|
||||
agent = Agent(
|
||||
role="Empty Apps Agent",
|
||||
goal="Use apps",
|
||||
backstory="b",
|
||||
apps=["gmail", "slack"],
|
||||
)
|
||||
|
||||
with pytest.warns(UserWarning, match="resolved to any tools"):
|
||||
result = agent.get_platform_tools(agent.apps)
|
||||
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_get_platform_tools_quiet_when_tools_resolve(monkeypatch):
|
||||
"""No warning when the apps resolve to at least one tool."""
|
||||
from crewai.tools import tool
|
||||
|
||||
@tool
|
||||
def platform_tool() -> str:
|
||||
"""A resolved platform tool."""
|
||||
return "ok"
|
||||
|
||||
import crewai_tools
|
||||
|
||||
monkeypatch.setattr(crewai_tools, "CrewaiPlatformTools", lambda apps: [platform_tool])
|
||||
agent = Agent(role="A", goal="g", backstory="b", apps=["gmail"])
|
||||
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
result = agent.get_platform_tools(agent.apps)
|
||||
|
||||
assert result == [platform_tool]
|
||||
assert not any("resolved to any tools" in str(w.message) for w in caught)
|
||||
|
||||
|
||||
def test_crew_prepare_tools_warns_on_empty_apps(monkeypatch):
|
||||
"""The warning also fires through the crew tool-injection path."""
|
||||
import crewai_tools
|
||||
|
||||
monkeypatch.setattr(crewai_tools, "CrewaiPlatformTools", lambda apps: [])
|
||||
agent = Agent(role="A", goal="g", backstory="b", apps=["gmail"])
|
||||
task = Task(description="d", expected_output="o", agent=agent)
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
|
||||
with pytest.warns(UserWarning, match="resolved to any tools"):
|
||||
crew._prepare_tools(agent, task, [])
|
||||
|
||||
|
||||
def test_agent_mcps_accepts_slug_with_specific_tool():
|
||||
"""Agent(mcps=["notion#get_page"]) must pass validation (_SLUG_RE)."""
|
||||
agent = Agent(
|
||||
|
||||
Reference in New Issue
Block a user