mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-20 18:13:49 +00:00
fix(tools): fall back to CREWAI_API_URL/CREWAI_BEARER_TOKEN env vars
InvokeCrewAIAutomationTool required crew_api_url and crew_bearer_token as positional __init__ arguments with no environment-variable fallback, even though docs.crewai.com documents CREWAI_API_URL/CREWAI_BEARER_TOKEN as alternatives. It also could not be instantiated with zero arguments, which blocks CrewAI AMP Studio's "Invoke Amp Automation" internal tool: the Studio runtime resolves tools by class reference and instantiates them with no arguments. - crew_api_url/crew_bearer_token become optional, falling back to CREWAI_API_URL/CREWAI_BEARER_TOKEN (explicit args still win), mirroring the env-var pattern already used by GenerateCrewaiAutomationTool. - crew_name/crew_description become optional too, defaulting to the tool's existing generic name/description, so InvokeCrewAIAutomationTool() never raises at construction time. - Declare env_vars: list[EnvVar] so the tool catalog surfaces the two env vars, matching the sibling tool. - Raise a clear ValueError at use time when crew_api_url/crew_bearer_token are still missing, instead of failing inside `requests` or with the previous confusing TypeError about positional arguments. - Update the tool's README and the edge docs page (docs/edge/en/tools/integration/crewaiautomationtool.mdx) so the "Tool Arguments" table matches the corrected code. Fully backward compatible: existing positional/keyword constructor calls are unchanged. tool.specs.json is left untouched; the "Generate Tool Specifications" CI workflow regenerates and commits it automatically once this is pushed. crewAIInc/crewAI-tools (the previous home of this tool) is archived and can no longer receive pushes, so this fix targets the actively maintained copy of the tool under lib/crewai-tools/ instead.
This commit is contained in:
@@ -68,13 +68,17 @@ print(result)
|
||||
|
||||
| Argument | Type | Required | Default | Description |
|
||||
|:---------|:-----|:---------|:--------|:------------|
|
||||
| **crew_api_url** | `str` | Yes | None | Base URL of the CrewAI Platform automation API |
|
||||
| **crew_bearer_token** | `str` | Yes | None | Bearer token for API authentication |
|
||||
| **crew_name** | `str` | Yes | None | Name of the crew automation |
|
||||
| **crew_description** | `str` | Yes | None | Description of what the crew automation does |
|
||||
| **crew_api_url** | `str` | No | `None` | Base URL of the CrewAI Platform automation API. Falls back to the `CREWAI_API_URL` environment variable when omitted. |
|
||||
| **crew_bearer_token** | `str` | No | `None` | Bearer token for API authentication. Falls back to the `CREWAI_BEARER_TOKEN` environment variable when omitted. |
|
||||
| **crew_name** | `str` | No | generic name | Name of the crew automation. Set explicitly outside CrewAI AMP Studio so the LLM sees a meaningful tool name. |
|
||||
| **crew_description** | `str` | No | generic description | Description of what the crew automation does. Set explicitly for the same reason as `crew_name`. |
|
||||
| **max_polling_time** | `int` | No | 600 | Maximum time in seconds to wait for task completion |
|
||||
| **crew_inputs** | `dict` | No | None | Dictionary defining custom input schema fields |
|
||||
|
||||
If `crew_api_url`/`crew_bearer_token` are still missing at run time (neither passed
|
||||
explicitly nor available via the environment variables below), the tool raises a clear
|
||||
error explaining what's missing.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
|
||||
@@ -39,6 +39,28 @@ tool = InvokeCrewAIAutomationTool(
|
||||
result = tool.run()
|
||||
```
|
||||
|
||||
### Using Environment Variables
|
||||
|
||||
`crew_api_url` and `crew_bearer_token` can also be provided via environment variables
|
||||
instead of constructor arguments, which is convenient for keeping credentials out of code:
|
||||
|
||||
```shell
|
||||
export CREWAI_API_URL="https://data-analysis-crew-[...].crewai.com"
|
||||
export CREWAI_BEARER_TOKEN="your_bearer_token_here"
|
||||
```
|
||||
|
||||
```python
|
||||
from crewai_tools import InvokeCrewAIAutomationTool
|
||||
|
||||
# crew_api_url/crew_bearer_token are read from CREWAI_API_URL/CREWAI_BEARER_TOKEN
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_name="Data Analysis Crew",
|
||||
crew_description="Analyzes data and generates insights"
|
||||
)
|
||||
```
|
||||
|
||||
Explicit constructor arguments always take precedence over the environment variables.
|
||||
|
||||
### Advanced Usage with Custom Inputs
|
||||
|
||||
```python
|
||||
@@ -109,18 +131,29 @@ result = crew.kickoff()
|
||||
|
||||
## Arguments
|
||||
|
||||
### Required Parameters
|
||||
|
||||
- `crew_api_url` (str): Base URL of the CrewAI Platform automation API
|
||||
- `crew_bearer_token` (str): Bearer token for API authentication
|
||||
- `crew_name` (str): Name of the crew automation
|
||||
- `crew_description` (str): Description of what the crew automation does
|
||||
|
||||
### Optional Parameters
|
||||
|
||||
- `crew_api_url` (str): Base URL of the CrewAI Platform automation API. Falls back to the
|
||||
`CREWAI_API_URL` environment variable when omitted.
|
||||
- `crew_bearer_token` (str): Bearer token for API authentication. Falls back to the
|
||||
`CREWAI_BEARER_TOKEN` environment variable when omitted.
|
||||
- `crew_name` (str): Name of the crew automation. Defaults to a generic tool name; set it
|
||||
explicitly so the LLM sees a meaningful tool name and description in its tool list.
|
||||
- `crew_description` (str): Description of what the crew automation does. Defaults to a
|
||||
generic description for the same reason as `crew_name`.
|
||||
- `max_polling_time` (int): Maximum time in seconds to wait for task completion (default: 600 seconds = 10 minutes)
|
||||
- `crew_inputs` (dict): Dictionary defining custom input schema fields using Pydantic Field objects
|
||||
|
||||
All of the above are optional at construction time, so `InvokeCrewAIAutomationTool()` never
|
||||
fails to instantiate. If `crew_api_url`/`crew_bearer_token` are still missing (neither passed
|
||||
explicitly nor available via `CREWAI_API_URL`/`CREWAI_BEARER_TOKEN`) when the tool is actually
|
||||
run, it raises a clear `ValueError` explaining what is missing.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `CREWAI_API_URL`: Alternative to passing `crew_api_url`.
|
||||
- `CREWAI_BEARER_TOKEN`: Alternative to passing `crew_bearer_token`.
|
||||
|
||||
## Custom Input Schema
|
||||
|
||||
When defining `crew_inputs`, use Pydantic Field objects to specify the input parameters. These have to be compatible with the crew automation you are invoking:
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from crewai.tools import BaseTool
|
||||
from crewai.tools import BaseTool, EnvVar
|
||||
from pydantic import BaseModel, Field, create_model
|
||||
import requests
|
||||
|
||||
|
||||
# Generic fallbacks used when the tool is instantiated without a crew_name /
|
||||
# crew_description, e.g. by a runtime that resolves tools by class reference
|
||||
# and instantiates them with no arguments (see the "Zero-argument
|
||||
# instantiation" note in the class docstring below).
|
||||
DEFAULT_TOOL_NAME = "invoke_amp_automation"
|
||||
DEFAULT_TOOL_DESCRIPTION = "Invokes an CrewAI Platform Automation using API"
|
||||
|
||||
|
||||
class InvokeCrewAIAutomationInput(BaseModel):
|
||||
"""Input schema for InvokeCrewAIAutomationTool."""
|
||||
|
||||
@@ -59,32 +68,72 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
... },
|
||||
... )
|
||||
... ]
|
||||
|
||||
Configuring the API url and bearer token via environment variables, instead of
|
||||
passing them as constructor arguments:
|
||||
>>> import os
|
||||
>>> os.environ["CREWAI_API_URL"] = "https://canary-crew-[...].crewai.com"
|
||||
>>> os.environ["CREWAI_BEARER_TOKEN"] = "[Your token: abcdef012345]"
|
||||
>>> tool = InvokeCrewAIAutomationTool(
|
||||
... crew_name="State of AI Report",
|
||||
... crew_description="Retrieves a report on state of AI for a given year.",
|
||||
... )
|
||||
|
||||
Zero-argument instantiation:
|
||||
`crew_api_url`/`crew_bearer_token` fall back to the `CREWAI_API_URL` /
|
||||
`CREWAI_BEARER_TOKEN` environment variables, and `crew_name` /
|
||||
`crew_description` fall back to a generic name/description, so
|
||||
`InvokeCrewAIAutomationTool()` never raises at construction time. This
|
||||
matters because some runtimes (e.g. the CrewAI AMP Studio "Invoke Amp
|
||||
Automation" internal tool) resolve tools by class reference and instantiate
|
||||
them with no arguments. If the tool is still unconfigured (no explicit
|
||||
arguments and no environment variables) when it is actually invoked, it
|
||||
raises a clear `ValueError` instead of attempting the HTTP request.
|
||||
"""
|
||||
|
||||
name: str = "invoke_amp_automation"
|
||||
description: str = "Invokes an CrewAI Platform Automation using API"
|
||||
name: str = DEFAULT_TOOL_NAME
|
||||
description: str = DEFAULT_TOOL_DESCRIPTION
|
||||
args_schema: type[BaseModel] = InvokeCrewAIAutomationInput
|
||||
|
||||
crew_api_url: str
|
||||
crew_bearer_token: str
|
||||
crew_api_url: str | None = None
|
||||
crew_bearer_token: str | None = None
|
||||
max_polling_time: int = 10 * 60
|
||||
env_vars: list[EnvVar] = Field(
|
||||
default_factory=lambda: [
|
||||
EnvVar(
|
||||
name="CREWAI_API_URL",
|
||||
description="Base URL of the crew/flow API to invoke. Alternative to passing crew_api_url.",
|
||||
required=True,
|
||||
),
|
||||
EnvVar(
|
||||
name="CREWAI_BEARER_TOKEN",
|
||||
description="Bearer token used to authenticate against crew_api_url. Alternative to passing crew_bearer_token.",
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
crew_api_url: str,
|
||||
crew_bearer_token: str,
|
||||
crew_name: str,
|
||||
crew_description: str,
|
||||
crew_api_url: str | None = None,
|
||||
crew_bearer_token: str | None = None,
|
||||
crew_name: str = DEFAULT_TOOL_NAME,
|
||||
crew_description: str = DEFAULT_TOOL_DESCRIPTION,
|
||||
max_polling_time: int = 10 * 60,
|
||||
crew_inputs: dict[str, Any] | None = None,
|
||||
):
|
||||
"""Initialize the InvokeCrewAIAutomationTool.
|
||||
|
||||
Args:
|
||||
crew_api_url: Base URL of the crew API service
|
||||
crew_bearer_token: Bearer token for API authentication
|
||||
crew_name: Name of the crew to invoke
|
||||
crew_description: Description of the crew to invoke
|
||||
crew_api_url: Base URL of the crew API service. If omitted, falls back to
|
||||
the CREWAI_API_URL environment variable.
|
||||
crew_bearer_token: Bearer token for API authentication. If omitted, falls
|
||||
back to the CREWAI_BEARER_TOKEN environment variable.
|
||||
crew_name: Name of the crew to invoke. Defaults to a generic tool name so
|
||||
the tool can be instantiated without arguments; set it explicitly so
|
||||
the LLM sees a meaningful tool name in its tool list.
|
||||
crew_description: Description of the crew to invoke. Defaults to a generic
|
||||
description for the same reason as crew_name.
|
||||
max_polling_time: Maximum time in seconds to wait for task completion (default: 600 seconds = 10 minutes)
|
||||
crew_inputs: Optional dictionary defining custom input schema fields
|
||||
"""
|
||||
@@ -102,15 +151,45 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
else:
|
||||
args_schema = InvokeCrewAIAutomationInput
|
||||
|
||||
# Explicit constructor arguments win over the environment variables.
|
||||
resolved_api_url = crew_api_url or os.getenv("CREWAI_API_URL")
|
||||
resolved_bearer_token = crew_bearer_token or os.getenv("CREWAI_BEARER_TOKEN")
|
||||
|
||||
super().__init__(
|
||||
name=crew_name,
|
||||
description=crew_description,
|
||||
name=crew_name or DEFAULT_TOOL_NAME,
|
||||
description=crew_description or DEFAULT_TOOL_DESCRIPTION,
|
||||
args_schema=args_schema,
|
||||
crew_api_url=crew_api_url,
|
||||
crew_bearer_token=crew_bearer_token,
|
||||
crew_api_url=resolved_api_url,
|
||||
crew_bearer_token=resolved_bearer_token,
|
||||
max_polling_time=max_polling_time,
|
||||
)
|
||||
|
||||
def _ensure_configured(self) -> None:
|
||||
"""Raise a clear, actionable error if the tool has no API url/token.
|
||||
|
||||
crew_api_url and crew_bearer_token are optional at construction time (they
|
||||
may be resolved later from the environment, or simply not set yet when the
|
||||
tool is instantiated with no arguments). This is checked once, right before
|
||||
the tool is actually used, so a missing configuration produces an explicit
|
||||
error message instead of a confusing failure deep inside `requests` (e.g. an
|
||||
"Invalid URL 'None/kickoff'" error) or a bare 401 from the API.
|
||||
"""
|
||||
missing = [
|
||||
env_name
|
||||
for value, env_name in (
|
||||
(self.crew_api_url, "CREWAI_API_URL"),
|
||||
(self.crew_bearer_token, "CREWAI_BEARER_TOKEN"),
|
||||
)
|
||||
if not value
|
||||
]
|
||||
if missing:
|
||||
raise ValueError(
|
||||
"InvokeCrewAIAutomationTool is not configured: missing "
|
||||
f"{' and '.join(missing)}. Pass crew_api_url/crew_bearer_token "
|
||||
"explicitly when creating the tool, or set the "
|
||||
f"{' and '.join(missing)} environment variable(s)."
|
||||
)
|
||||
|
||||
def _kickoff_crew(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Start a new crew task.
|
||||
|
||||
@@ -154,6 +233,8 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
|
||||
def _run(self, **kwargs: Any) -> str:
|
||||
"""Execute the crew invocation tool."""
|
||||
self._ensure_configured()
|
||||
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from crewai_tools.tools.invoke_crewai_automation_tool.invoke_crewai_automation_tool import (
|
||||
DEFAULT_TOOL_DESCRIPTION,
|
||||
DEFAULT_TOOL_NAME,
|
||||
InvokeCrewAIAutomationTool,
|
||||
)
|
||||
from pydantic import Field
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_env():
|
||||
"""Ensure CREWAI_API_URL/CREWAI_BEARER_TOKEN never leak between tests."""
|
||||
with patch.dict(os.environ, {}, clear=False):
|
||||
os.environ.pop("CREWAI_API_URL", None)
|
||||
os.environ.pop("CREWAI_BEARER_TOKEN", None)
|
||||
yield
|
||||
|
||||
|
||||
def test_zero_argument_instantiation_does_not_raise():
|
||||
"""A runtime that resolves tools by class reference (e.g. CrewAI AMP Studio's
|
||||
"Invoke Amp Automation" internal tool) instantiates the tool with no arguments.
|
||||
This must never raise a TypeError about missing positional arguments."""
|
||||
tool = InvokeCrewAIAutomationTool()
|
||||
|
||||
assert tool.name == DEFAULT_TOOL_NAME
|
||||
assert DEFAULT_TOOL_DESCRIPTION in tool.description
|
||||
assert tool.crew_api_url is None
|
||||
assert tool.crew_bearer_token is None
|
||||
|
||||
|
||||
def test_existing_positional_keyword_call_shape_still_works():
|
||||
"""Backward compatibility: the documented/previous required-arguments call shape
|
||||
keeps working unchanged."""
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
"https://api.example.com",
|
||||
"explicit_token",
|
||||
"My Crew",
|
||||
"Description of what the crew does",
|
||||
)
|
||||
|
||||
assert tool.crew_api_url == "https://api.example.com"
|
||||
assert tool.crew_bearer_token == "explicit_token"
|
||||
assert tool.name == "My Crew"
|
||||
assert "Description of what the crew does" in tool.description
|
||||
|
||||
kwarg_tool = InvokeCrewAIAutomationTool(
|
||||
crew_api_url="https://api.example.com",
|
||||
crew_bearer_token="explicit_token",
|
||||
crew_name="My Crew",
|
||||
crew_description="Description of what the crew does",
|
||||
max_polling_time=120,
|
||||
)
|
||||
assert kwarg_tool.crew_api_url == "https://api.example.com"
|
||||
assert kwarg_tool.crew_bearer_token == "explicit_token"
|
||||
assert kwarg_tool.max_polling_time == 120
|
||||
|
||||
|
||||
def test_env_var_fallback_for_url_and_token():
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"CREWAI_API_URL": "https://from-env.crewai.com",
|
||||
"CREWAI_BEARER_TOKEN": "env_token",
|
||||
},
|
||||
):
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_name="My Crew", crew_description="Does things"
|
||||
)
|
||||
|
||||
assert tool.crew_api_url == "https://from-env.crewai.com"
|
||||
assert tool.crew_bearer_token == "env_token"
|
||||
assert tool.name == "My Crew"
|
||||
assert "Does things" in tool.description
|
||||
|
||||
|
||||
def test_explicit_arguments_take_precedence_over_env_vars():
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"CREWAI_API_URL": "https://from-env.crewai.com",
|
||||
"CREWAI_BEARER_TOKEN": "env_token",
|
||||
},
|
||||
):
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_api_url="https://explicit.crewai.com",
|
||||
crew_bearer_token="explicit_token",
|
||||
crew_name="My Crew",
|
||||
crew_description="Does things",
|
||||
)
|
||||
|
||||
assert tool.crew_api_url == "https://explicit.crewai.com"
|
||||
assert tool.crew_bearer_token == "explicit_token"
|
||||
|
||||
|
||||
def test_missing_configuration_raises_clear_error_on_use():
|
||||
"""No explicit args and no env vars: construction succeeds, but running the
|
||||
tool must raise a clear, actionable error instead of failing deep inside
|
||||
`requests` or the CrewAI Platform API with a bare 401."""
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_name="My Crew", crew_description="Does things"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
tool.run(prompt="hello")
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "CREWAI_API_URL" in message
|
||||
assert "CREWAI_BEARER_TOKEN" in message
|
||||
|
||||
|
||||
def test_partial_configuration_raises_clear_error_on_use():
|
||||
"""Only one of the two required values is configured."""
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_api_url="https://api.example.com",
|
||||
crew_name="My Crew",
|
||||
crew_description="Does things",
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
tool.run(prompt="hello")
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "CREWAI_BEARER_TOKEN" in message
|
||||
assert "CREWAI_API_URL" not in message
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
@patch("requests.post")
|
||||
def test_successful_run_with_env_var_configuration(mock_post, mock_get):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"CREWAI_API_URL": "https://from-env.crewai.com",
|
||||
"CREWAI_BEARER_TOKEN": "env_token",
|
||||
},
|
||||
):
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_name="My Crew", crew_description="Does things"
|
||||
)
|
||||
|
||||
kickoff_response = MagicMock()
|
||||
kickoff_response.json.return_value = {"kickoff_id": "kickoff-123"}
|
||||
mock_post.return_value = kickoff_response
|
||||
|
||||
status_response = MagicMock()
|
||||
status_response.json.return_value = {"state": "success", "result": "42"}
|
||||
mock_get.return_value = status_response
|
||||
|
||||
result = tool.run(prompt="hello")
|
||||
|
||||
assert result == "42"
|
||||
mock_post.assert_called_once_with(
|
||||
"https://from-env.crewai.com/kickoff",
|
||||
headers={
|
||||
"Authorization": "Bearer env_token",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={"inputs": {"prompt": "hello"}},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
|
||||
def test_dynamic_crew_inputs_schema_still_works():
|
||||
custom_inputs = {
|
||||
"year": Field(..., description="Year to retrieve the report for (integer)"),
|
||||
"region": Field(default="global", description="Geographic region"),
|
||||
}
|
||||
|
||||
tool = InvokeCrewAIAutomationTool(
|
||||
crew_api_url="https://api.example.com",
|
||||
crew_bearer_token="token",
|
||||
crew_name="State of AI Report",
|
||||
crew_description="Retrieves a report on state of AI for a given year.",
|
||||
crew_inputs=custom_inputs,
|
||||
)
|
||||
|
||||
schema_fields = tool.args_schema.model_fields
|
||||
assert "year" in schema_fields
|
||||
assert "region" in schema_fields
|
||||
Reference in New Issue
Block a user