Compare commits

..

5 Commits

Author SHA1 Message Date
Rip&Tear
16524ccfa8 Update md5 to sha3256 2024-10-27 18:16:03 +08:00
Rip&Tear
263544524d ruff updates 2024-10-23 17:20:05 +08:00
Rip&Tear
098a4312ab allow user to bypass api key entry + incorect number selected logic + ruff formatting 2024-10-23 17:17:05 +08:00
Rip&Tear
c724c0af70 Minor doc updates 2024-10-22 09:04:32 +08:00
Rip&Tear
f6f430b26a Added docs for new CLI provider + fixed missing API prompt 2024-10-18 10:23:34 +08:00
11 changed files with 185 additions and 65 deletions

23
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,23 @@
CrewAI takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organization.
If you believe you have found a security vulnerability in any CrewAI product or service, please report it to us as described below.
## Reporting a Vulnerability
Please do not report security vulnerabilities through public GitHub issues.
To report a vulnerability, please email us at security@crewai.com.
Please include the requested information listed below so that we can triage your report more quickly
- Type of issue (e.g. SQL injection, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue (please include screenshots if needed)
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
Once we have received your report, we will respond to you at the email address you provide. If the issue is confirmed, we will release a patch as soon as possible depending on the complexity of the issue.
At this time, we are not offering a bug bounty program. Any rewards will be at our discretion.

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ rc-tests/*
temp/*
.vscode/*
crew_tasks_output.json
.dccache

View File

@@ -6,7 +6,7 @@ icon: terminal
# CrewAI CLI Documentation
The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you to create, train, run, and manage crews and pipelines.
The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you to create, train, run, and manage crews & flows.
## Installation
@@ -146,3 +146,34 @@ crewai run
Make sure to run these commands from the directory where your CrewAI project is set up.
Some commands may require additional configuration or setup within your project structure.
</Note>
### 9. 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.
Once you've selected an LLM provider, you will be prompted for API keys.
#### Initial API key providers
The CLI will initiallyprompt for API keys for the following services:
* OpenAI
* Groq
* Anthropic
* Google Gemini
When you select a provider, the CLI will 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.
When you select a provider, the CLI will prompt you to enter the Key name and the API key.
See the following link for each provider's key name:
* [LiteLLM Providers](https://docs.litellm.ai/docs/providers)

View File

@@ -1,7 +1,7 @@
import uuid
from abc import ABC, abstractmethod
from copy import copy as shallow_copy
from hashlib import md5
from hashlib import sha256
from typing import Any, Dict, List, Optional, TypeVar
from pydantic import (
@@ -181,7 +181,7 @@ class BaseAgent(ABC, BaseModel):
self._original_goal or self.goal,
self._original_backstory or self.backstory,
]
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
return sha256("|".join(source).encode()).hexdigest()
@abstractmethod
def execute_task(

View File

@@ -1,8 +1,15 @@
from pathlib import Path
import click
from crewai.cli.utils import copy_template,load_env_vars, write_env_file
from crewai.cli.provider import get_provider_data, select_provider, select_model, PROVIDERS
from crewai.cli.utils import copy_template, load_env_vars, write_env_file
from crewai.cli.provider import (
get_provider_data,
select_provider,
select_model,
PROVIDERS,
)
from crewai.cli.constants import ENV_VARS
import sys
def create_folder_structure(name, parent_folder=None):
folder_name = name.replace(" ", "_").replace("-", "_").lower()
@@ -13,11 +20,19 @@ def create_folder_structure(name, parent_folder=None):
else:
folder_path = Path(folder_name)
click.secho(
f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...",
fg="green",
bold=True,
)
if folder_path.exists():
if not click.confirm(
f"Folder {folder_name} already exists. Do you want to override it?"
):
click.secho("Operation cancelled.", fg="yellow")
sys.exit(0)
click.secho(f"Overriding folder {folder_name}...", fg="green", bold=True)
else:
click.secho(
f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...",
fg="green",
bold=True,
)
if not folder_path.exists():
folder_path.mkdir(parents=True)
@@ -26,16 +41,10 @@ def create_folder_structure(name, parent_folder=None):
(folder_path / "src" / folder_name).mkdir(parents=True)
(folder_path / "src" / folder_name / "tools").mkdir(parents=True)
(folder_path / "src" / folder_name / "config").mkdir(parents=True)
else:
click.secho(
f"\tFolder {folder_name} already exists.",
fg="yellow",
)
return folder_path, folder_name, class_name
def copy_template_files(folder_path, name, class_name, parent_folder):
package_dir = Path(__file__).parent
templates_dir = package_dir / "templates" / "crew"
@@ -54,7 +63,9 @@ def copy_template_files(folder_path, name, class_name, parent_folder):
dst_file = folder_path / file_name
copy_template(src_file, dst_file, name, class_name, folder_path.name)
src_folder = folder_path / "src" / folder_path.name if not parent_folder else folder_path
src_folder = (
folder_path / "src" / folder_path.name if not parent_folder else folder_path
)
for file_name in src_template_files:
src_file = templates_dir / file_name
@@ -72,33 +83,73 @@ def create_crew(name, parent_folder=None):
folder_path, folder_name, class_name = create_folder_structure(name, parent_folder)
env_vars = load_env_vars(folder_path)
existing_provider = None
for provider, env_keys in ENV_VARS.items():
if any(key in env_vars for key in env_keys):
existing_provider = provider
break
if existing_provider:
if not click.confirm(
f"Found existing environment variable configuration for {existing_provider.capitalize()}. Do you want to override it?"
):
click.secho("Keeping existing provider configuration.", fg="yellow")
return
provider_models = get_provider_data()
if not provider_models:
return
selected_provider = select_provider(provider_models)
if not selected_provider:
return
provider = selected_provider
selected_model = select_model(provider, provider_models)
if not selected_model:
return
model = selected_model
if provider in PROVIDERS:
api_key_var = ENV_VARS[provider][0]
else:
api_key_var = click.prompt(
f"Enter the environment variable name for your {provider.capitalize()} API key",
type=str
while True:
selected_provider = select_provider(provider_models)
if selected_provider is None: # User typed 'q'
click.secho("Exiting...", fg="yellow")
sys.exit(0)
if selected_provider: # Valid selection
break
click.secho(
"No provider selected. Please try again or press 'q' to exit.", fg="red"
)
env_vars = {api_key_var: "YOUR_API_KEY_HERE"}
write_env_file(folder_path, env_vars)
while True:
selected_model = select_model(selected_provider, provider_models)
if selected_model is None: # User typed 'q'
click.secho("Exiting...", fg="yellow")
sys.exit(0)
if selected_model: # Valid selection
break
click.secho(
"No model selected. Please try again or press 'q' to exit.", fg="red"
)
env_vars['MODEL'] = model
click.secho(f"Selected model: {model}", fg="green")
if selected_provider in PROVIDERS:
api_key_var = ENV_VARS[selected_provider][0]
else:
api_key_var = click.prompt(
f"Enter the environment variable name for your {selected_provider.capitalize()} API key",
type=str,
default="",
)
api_key_value = ""
click.echo(
f"Enter your {selected_provider.capitalize()} API key (press Enter to skip): ",
nl=False,
)
try:
api_key_value = input()
except (KeyboardInterrupt, EOFError):
api_key_value = ""
if api_key_value.strip():
env_vars = {api_key_var: api_key_value}
write_env_file(folder_path, env_vars)
click.secho("API key saved to .env file", fg="green")
else:
click.secho("No API key provided. Skipping .env file creation.", fg="yellow")
env_vars["MODEL"] = selected_model
click.secho(f"Selected model: {selected_model}", fg="green")
package_dir = Path(__file__).parent
templates_dir = package_dir / "templates" / "crew"

View File

@@ -6,6 +6,8 @@ import click
from pathlib import Path
from crewai.cli.constants import PROVIDERS, MODELS, JSON_URL
def select_choice(prompt_message, choices):
"""
Presents a list of choices to the user and prompts them to select one.
@@ -15,20 +17,31 @@ def select_choice(prompt_message, choices):
- choices (list): A list of options to present to the user.
Returns:
- str: The selected choice from the list, or None if the operation is aborted or an invalid selection is made.
- str: The selected choice from the list, or None if the user chooses to quit.
"""
provider_models = get_provider_data()
if not provider_models:
return
click.secho(prompt_message, fg="cyan")
for idx, choice in enumerate(choices, start=1):
click.secho(f"{idx}. {choice}", fg="cyan")
try:
selected_index = click.prompt("Enter the number of your choice", type=int) - 1
except click.exceptions.Abort:
click.secho("Operation aborted by the user.", fg="red")
return None
if not (0 <= selected_index < len(choices)):
click.secho("Invalid selection.", fg="red")
return None
return choices[selected_index]
click.secho("q. Quit", fg="cyan")
while True:
choice = click.prompt("Enter the number of your choice or 'q' to quit", type=str)
if choice.lower() == 'q':
return None
try:
selected_index = int(choice) - 1
if 0 <= selected_index < len(choices):
return choices[selected_index]
except ValueError:
pass
click.secho("Invalid selection. Please select a number between 1 and 6 or 'q' to quit.", fg="red")
def select_provider(provider_models):
"""
@@ -38,21 +51,22 @@ def select_provider(provider_models):
- provider_models (dict): A dictionary of provider models.
Returns:
- str: The selected provider, or None if the operation is aborted or an invalid selection is made.
- str: The selected provider
- None: If user explicitly quits
"""
predefined_providers = [p.lower() for p in PROVIDERS]
all_providers = sorted(set(predefined_providers + list(provider_models.keys())))
provider = select_choice("Select a provider to set up:", predefined_providers + ['other'])
if not provider:
if provider is None: # User typed 'q'
return None
provider = provider.lower()
if provider == 'other':
provider = select_choice("Select a provider from the full list:", all_providers)
if not provider:
if provider is None: # User typed 'q'
return None
return provider
return provider.lower() if provider else False
def select_model(provider, provider_models):
"""
@@ -183,4 +197,4 @@ def get_provider_data():
continue
if provider:
provider_models[provider].append(model_name)
return provider_models
return provider_models

View File

@@ -4,7 +4,7 @@ import os
import uuid
import warnings
from concurrent.futures import Future
from hashlib import md5
from hashlib import sha256
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from pydantic import (
@@ -388,7 +388,7 @@ class Crew(BaseModel):
source = [agent.key for agent in self.agents] + [
task.key for task in self.tasks
]
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
return sha256("|".join(source).encode()).hexdigest()
def _setup_from_config(self):
assert self.config is not None, "Config should not be None."

View File

@@ -5,7 +5,7 @@ import threading
import uuid
from concurrent.futures import Future
from copy import copy
from hashlib import md5
from hashlib import sha256
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
from opentelemetry.trace import Span
@@ -196,7 +196,7 @@ class Task(BaseModel):
expected_output = self._original_expected_output or self.expected_output
source = [description, expected_output]
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
return sha256("|".join(source).encode()).hexdigest()
def execute_async(
self,

View File

@@ -1,4 +1,4 @@
import hashlib
from hashlib import sha256
from typing import Any, List, Optional
from crewai.agents.agent_builder.base_agent import BaseAgent
@@ -32,5 +32,5 @@ def test_key():
goal="test goal",
backstory="test backstory",
)
hash = hashlib.md5("test role|test goal|test backstory".encode()).hexdigest()
hash = sha256("test role|test goal|test backstory".encode()).hexdigest()
assert agent.key == hash

View File

@@ -1,6 +1,6 @@
"""Test Agent creation and execution basic functionality."""
import hashlib
from hashlib import sha256
import json
from concurrent.futures import Future
from unittest import mock
@@ -2328,7 +2328,7 @@ def test_key():
process=Process.sequential,
tasks=tasks,
)
hash = hashlib.md5(
hash = sha256(
f"{researcher.key}|{writer.key}|{tasks[0].key}|{tasks[1].key}".encode()
).hexdigest()
@@ -2368,7 +2368,7 @@ def test_key_with_interpolated_inputs():
process=Process.sequential,
tasks=tasks,
)
hash = hashlib.md5(
hash = sha256(
f"{researcher.key}|{writer.key}|{tasks[0].key}|{tasks[1].key}".encode()
).hexdigest()

View File

@@ -1,6 +1,6 @@
"""Test Agent creation and execution basic functionality."""
import hashlib
from hashlib import sha256
import json
import os
from unittest.mock import MagicMock, patch
@@ -819,7 +819,7 @@ def test_key():
description=original_description,
expected_output=original_expected_output,
)
hash = hashlib.md5(
hash = sha256(
f"{original_description}|{original_expected_output}".encode()
).hexdigest()