mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-17 08:22:39 +00:00
Compare commits
2 Commits
lg-attempt
...
devin/1750
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f28e3e0be8 | ||
|
|
10a55bd210 |
@@ -285,25 +285,32 @@ Watch this video tutorial for a step-by-step demonstration of deploying your cre
|
||||
|
||||
### 11. API Keys
|
||||
|
||||
When running ```crewai create crew``` command, the CLI will first show you the top 5 most common LLM providers and ask you to select one.
|
||||
When running ```crewai create crew``` command, the CLI will show you a list of available LLM providers to choose from, followed by model selection for your chosen provider.
|
||||
|
||||
Once you've selected an LLM provider, you will be prompted for API keys.
|
||||
Once you've selected an LLM provider and model, you will be prompted for API keys.
|
||||
|
||||
#### Initial API key providers
|
||||
#### Available LLM Providers
|
||||
|
||||
The CLI will initially prompt for API keys for the following services:
|
||||
The CLI will show you the following LLM providers to choose from:
|
||||
|
||||
* OpenAI
|
||||
* Groq
|
||||
* Anthropic
|
||||
* Google Gemini
|
||||
* NVIDIA NIM
|
||||
* Groq
|
||||
* Hugging Face
|
||||
* Ollama
|
||||
* Watson
|
||||
* AWS Bedrock
|
||||
* Azure
|
||||
* Cerebras
|
||||
* SambaNova
|
||||
|
||||
When you select a provider, the CLI will prompt you to enter your API key.
|
||||
When you select a provider, the CLI will then show you available models for that provider and prompt you to enter your API key.
|
||||
|
||||
#### Other Options
|
||||
|
||||
If you select option 6, you will be able to select from a list of LiteLLM supported providers.
|
||||
If you select "other", you will be able to select from a list of LiteLLM supported providers.
|
||||
|
||||
When you select a provider, the CLI will prompt you to enter the Key name and the API key.
|
||||
|
||||
|
||||
@@ -94,18 +94,17 @@ def _get_project_attribute(
|
||||
|
||||
attribute = _get_nested_value(pyproject_content, keys)
|
||||
except FileNotFoundError:
|
||||
console.print(f"Error: {pyproject_path} not found.", style="bold red")
|
||||
print(f"Error: {pyproject_path} not found.")
|
||||
except KeyError:
|
||||
console.print(f"Error: {pyproject_path} is not a valid pyproject.toml file.", style="bold red")
|
||||
print(f"Error: {pyproject_path} is not a valid pyproject.toml file.")
|
||||
except tomllib.TOMLDecodeError if sys.version_info >= (3, 11) else Exception as e: # type: ignore
|
||||
console.print(
|
||||
print(
|
||||
f"Error: {pyproject_path} is not a valid TOML file."
|
||||
if sys.version_info >= (3, 11)
|
||||
else f"Error reading the pyproject.toml file: {e}",
|
||||
style="bold red",
|
||||
else f"Error reading the pyproject.toml file: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
console.print(f"Error reading the pyproject.toml file: {e}", style="bold red")
|
||||
print(f"Error reading the pyproject.toml file: {e}")
|
||||
|
||||
if require and not attribute:
|
||||
console.print(
|
||||
@@ -138,9 +137,9 @@ def fetch_and_json_env_file(env_file_path: str = ".env") -> dict:
|
||||
return env_dict
|
||||
|
||||
except FileNotFoundError:
|
||||
console.print(f"Error: {env_file_path} not found.", style="bold red")
|
||||
print(f"Error: {env_file_path} not found.")
|
||||
except Exception as e:
|
||||
console.print(f"Error reading the .env file: {e}", style="bold red")
|
||||
print(f"Error reading the .env file: {e}")
|
||||
|
||||
return {}
|
||||
|
||||
@@ -256,69 +255,50 @@ def write_env_file(folder_path, env_vars):
|
||||
|
||||
|
||||
def get_crews(crew_path: str = "crew.py", require: bool = False) -> list[Crew]:
|
||||
"""Get the crew instances from a file."""
|
||||
"""Get the crew instances from the a file."""
|
||||
crew_instances = []
|
||||
try:
|
||||
import importlib.util
|
||||
|
||||
# Add the current directory to sys.path to ensure imports resolve correctly
|
||||
current_dir = os.getcwd()
|
||||
if current_dir not in sys.path:
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
# If we're not in src directory but there's a src directory, add it to path
|
||||
src_dir = os.path.join(current_dir, "src")
|
||||
if os.path.isdir(src_dir) and src_dir not in sys.path:
|
||||
sys.path.insert(0, src_dir)
|
||||
|
||||
# Search in both current directory and src directory if it exists
|
||||
search_paths = [".", "src"] if os.path.isdir("src") else ["."]
|
||||
|
||||
for search_path in search_paths:
|
||||
for root, _, files in os.walk(search_path):
|
||||
if crew_path in files and "cli/templates" not in root:
|
||||
crew_os_path = os.path.join(root, crew_path)
|
||||
for root, _, files in os.walk("."):
|
||||
if crew_path in files:
|
||||
crew_os_path = os.path.join(root, crew_path)
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"crew_module", crew_os_path
|
||||
)
|
||||
if not spec or not spec.loader:
|
||||
continue
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"crew_module", crew_os_path
|
||||
)
|
||||
if not spec or not spec.loader:
|
||||
continue
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
for attr_name in dir(module):
|
||||
module_attr = getattr(module, attr_name)
|
||||
|
||||
for attr_name in dir(module):
|
||||
module_attr = getattr(module, attr_name)
|
||||
try:
|
||||
crew_instances.extend(fetch_crews(module_attr))
|
||||
except Exception as e:
|
||||
console.print(f"Error processing attribute {attr_name}: {e}", style="bold red")
|
||||
continue
|
||||
try:
|
||||
crew_instances.extend(fetch_crews(module_attr))
|
||||
except Exception as e:
|
||||
print(f"Error processing attribute {attr_name}: {e}")
|
||||
continue
|
||||
|
||||
# If we found crew instances, break out of the loop
|
||||
if crew_instances:
|
||||
break
|
||||
except Exception as exec_error:
|
||||
print(f"Error executing module: {exec_error}")
|
||||
import traceback
|
||||
|
||||
except Exception as exec_error:
|
||||
console.print(f"Error executing module: {exec_error}", style="bold red")
|
||||
|
||||
except (ImportError, AttributeError) as e:
|
||||
if require:
|
||||
console.print(
|
||||
f"Error importing crew from {crew_path}: {str(e)}",
|
||||
style="bold red",
|
||||
)
|
||||
print(f"Traceback: {traceback.format_exc()}")
|
||||
except (ImportError, AttributeError) as e:
|
||||
if require:
|
||||
console.print(
|
||||
f"Error importing crew from {crew_path}: {str(e)}",
|
||||
style="bold red",
|
||||
)
|
||||
continue
|
||||
|
||||
# If we found crew instances in this search path, break out of the search paths loop
|
||||
if crew_instances:
|
||||
break
|
||||
|
||||
if require and not crew_instances:
|
||||
if require:
|
||||
console.print("No valid Crew instance found in crew.py", style="bold red")
|
||||
raise SystemExit
|
||||
|
||||
@@ -338,15 +318,11 @@ def get_crew_instance(module_attr) -> Crew | None:
|
||||
and module_attr.is_crew_class
|
||||
):
|
||||
return module_attr().crew()
|
||||
try:
|
||||
if (ismethod(module_attr) or isfunction(module_attr)) and get_type_hints(
|
||||
module_attr
|
||||
).get("return") is Crew:
|
||||
return module_attr()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
if isinstance(module_attr, Crew):
|
||||
if (ismethod(module_attr) or isfunction(module_attr)) and get_type_hints(
|
||||
module_attr
|
||||
).get("return") is Crew:
|
||||
return module_attr()
|
||||
elif isinstance(module_attr, Crew):
|
||||
return module_attr
|
||||
else:
|
||||
return None
|
||||
@@ -426,8 +402,7 @@ def _load_tools_from_init(init_file: Path) -> list[dict[str, Any]]:
|
||||
|
||||
if not hasattr(module, "__all__"):
|
||||
console.print(
|
||||
f"Warning: No __all__ defined in {init_file}",
|
||||
style="bold yellow",
|
||||
f"[bold yellow]Warning: No __all__ defined in {init_file}[/bold yellow]"
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
@@ -261,104 +261,3 @@ __all__ = ['MyTool']
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert "was never closed" in captured.out
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_crew():
|
||||
from crewai.crew import Crew
|
||||
|
||||
class MockCrew(Crew):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
return MockCrew()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_crew_project():
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
old_cwd = os.getcwd()
|
||||
os.chdir(temp_dir)
|
||||
|
||||
crew_content = """
|
||||
from crewai.crew import Crew
|
||||
from crewai.agent import Agent
|
||||
|
||||
def create_crew() -> Crew:
|
||||
agent = Agent(role="test", goal="test", backstory="test")
|
||||
return Crew(agents=[agent], tasks=[])
|
||||
|
||||
# Direct crew instance
|
||||
direct_crew = Crew(agents=[], tasks=[])
|
||||
"""
|
||||
|
||||
with open("crew.py", "w") as f:
|
||||
f.write(crew_content)
|
||||
|
||||
os.makedirs("src", exist_ok=True)
|
||||
with open(os.path.join("src", "crew.py"), "w") as f:
|
||||
f.write(crew_content)
|
||||
|
||||
# Create a src/templates directory that should be ignored
|
||||
os.makedirs(os.path.join("src", "templates"), exist_ok=True)
|
||||
with open(os.path.join("src", "templates", "crew.py"), "w") as f:
|
||||
f.write("# This should be ignored")
|
||||
|
||||
yield temp_dir
|
||||
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
def test_get_crews_finds_valid_crews(temp_crew_project, monkeypatch, mock_crew):
|
||||
def mock_fetch_crews(module_attr):
|
||||
return [mock_crew]
|
||||
|
||||
monkeypatch.setattr(utils, "fetch_crews", mock_fetch_crews)
|
||||
|
||||
crews = utils.get_crews()
|
||||
|
||||
assert len(crews) > 0
|
||||
assert mock_crew in crews
|
||||
|
||||
|
||||
def test_get_crews_with_nonexistent_file(temp_crew_project):
|
||||
crews = utils.get_crews(crew_path="nonexistent.py", require=False)
|
||||
assert len(crews) == 0
|
||||
|
||||
|
||||
def test_get_crews_with_required_nonexistent_file(temp_crew_project, capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
utils.get_crews(crew_path="nonexistent.py", require=True)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "No valid Crew instance found" in captured.out
|
||||
|
||||
|
||||
def test_get_crews_with_invalid_module(temp_crew_project, capsys):
|
||||
with open("crew.py", "w") as f:
|
||||
f.write("import nonexistent_module\n")
|
||||
|
||||
crews = utils.get_crews(crew_path="crew.py", require=False)
|
||||
assert len(crews) == 0
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
utils.get_crews(crew_path="crew.py", require=True)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Error" in captured.out
|
||||
|
||||
|
||||
def test_get_crews_ignores_template_directories(temp_crew_project, monkeypatch, mock_crew):
|
||||
template_crew_detected = False
|
||||
|
||||
def mock_fetch_crews(module_attr):
|
||||
nonlocal template_crew_detected
|
||||
if hasattr(module_attr, "__file__") and "templates" in module_attr.__file__:
|
||||
template_crew_detected = True
|
||||
return [mock_crew]
|
||||
|
||||
monkeypatch.setattr(utils, "fetch_crews", mock_fetch_crews)
|
||||
|
||||
utils.get_crews()
|
||||
|
||||
assert not template_crew_detected
|
||||
|
||||
43
tests/test_cli_documentation_sync.py
Normal file
43
tests/test_cli_documentation_sync.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from pathlib import Path
|
||||
|
||||
from crewai.cli.constants import PROVIDERS
|
||||
|
||||
|
||||
def test_cli_documentation_matches_providers():
|
||||
"""Test that CLI documentation accurately reflects the available providers."""
|
||||
docs_path = Path(__file__).parent.parent / "docs" / "concepts" / "cli.mdx"
|
||||
with open(docs_path, 'r') as f:
|
||||
docs_content = f.read()
|
||||
|
||||
assert "top 5" not in docs_content.lower(), "Documentation should not mention 'top 5' providers"
|
||||
assert "5 most common" not in docs_content.lower(), "Documentation should not mention '5 most common' providers"
|
||||
|
||||
assert "list of available LLM providers" in docs_content or "following LLM providers" in docs_content, \
|
||||
"Documentation should mention the availability of multiple LLM providers"
|
||||
|
||||
assert len(PROVIDERS) > 5, f"Expected more than 5 providers, but found {len(PROVIDERS)}"
|
||||
|
||||
key_providers = ["OpenAI", "Anthropic", "Gemini"]
|
||||
for provider in key_providers:
|
||||
assert provider in docs_content, f"Key provider {provider} should be mentioned in documentation"
|
||||
|
||||
|
||||
def test_providers_list_matches_constants():
|
||||
"""Test that the actual PROVIDERS list has the expected providers."""
|
||||
expected_providers = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"gemini",
|
||||
"nvidia_nim",
|
||||
"groq",
|
||||
"huggingface",
|
||||
"ollama",
|
||||
"watson",
|
||||
"bedrock",
|
||||
"azure",
|
||||
"cerebras",
|
||||
"sambanova",
|
||||
]
|
||||
|
||||
assert PROVIDERS == expected_providers, f"PROVIDERS list has changed. Expected {expected_providers}, got {PROVIDERS}"
|
||||
assert len(PROVIDERS) == 12, f"Expected 12 providers, but found {len(PROVIDERS)}"
|
||||
Reference in New Issue
Block a user