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:
Joao Moura
2026-07-07 00:15:40 -07:00
parent 799ab0f548
commit 0039376e7b
2 changed files with 72 additions and 2 deletions

View File

@@ -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.

View File

@@ -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(