mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-22 15:28:30 +00:00
Compare commits
9 Commits
0.75.1
...
bugfix/plo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d8584fe33 | ||
|
|
5af7df8031 | ||
|
|
8bc07e6071 | ||
|
|
6baaad045a | ||
|
|
74c1703310 | ||
|
|
a921828e51 | ||
|
|
e1fd83e6a7 | ||
|
|
7d68e287cc | ||
|
|
f39a975e20 |
@@ -351,7 +351,7 @@ pre-commit install
|
|||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx pytest
|
uv run pytest .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running static type checks
|
### Running static type checks
|
||||||
|
|||||||
@@ -31,16 +31,17 @@ Think of an agent as a member of a team, with specific skills and a particular j
|
|||||||
| **Max RPM** *(optional)* | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. |
|
| **Max RPM** *(optional)* | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. |
|
||||||
| **Max Execution Time** *(optional)* | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. |
|
| **Max Execution Time** *(optional)* | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. |
|
||||||
| **Verbose** *(optional)* | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. |
|
| **Verbose** *(optional)* | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. |
|
||||||
| **Allow Delegation** *(optional)* | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`.
|
| **Allow Delegation** *(optional)* | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. |
|
||||||
| **Step Callback** *(optional)* | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. |
|
| **Step Callback** *(optional)* | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. |
|
||||||
| **Cache** *(optional)* | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. |
|
| **Cache** *(optional)* | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. |
|
||||||
| **System Template** *(optional)* | `system_template` | Specifies the system format for the agent. Default is `None`. |
|
| **System Template** *(optional)* | `system_template` | Specifies the system format for the agent. Default is `None`. |
|
||||||
| **Prompt Template** *(optional)* | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. |
|
| **Prompt Template** *(optional)* | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. |
|
||||||
| **Response Template** *(optional)* | `response_template` | Specifies the response format for the agent. Default is `None`. |
|
| **Response Template** *(optional)* | `response_template` | Specifies the response format for the agent. Default is `None`. |
|
||||||
| **Allow Code Execution** *(optional)* | `allow_code_execution` | Enable code execution for the agent. Default is `False`. |
|
| **Allow Code Execution** *(optional)* | `allow_code_execution` | Enable code execution for the agent. Default is `False`. |
|
||||||
| **Max Retry Limit** *(optional)* | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`.
|
| **Max Retry Limit** *(optional)* | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. |
|
||||||
| **Use System Prompt** *(optional)* | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. |
|
| **Use System Prompt** *(optional)* | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. |
|
||||||
| **Respect Context Window** *(optional)* | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. |
|
| **Respect Context Window** *(optional)* | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. |
|
||||||
|
| **Code Execution Mode** *(optional)* | `code_execution_mode` | Determines the mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution on the host machine). Default is `safe`. |
|
||||||
|
|
||||||
## Creating an agent
|
## Creating an agent
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ agent = Agent(
|
|||||||
max_retry_limit=2, # Optional
|
max_retry_limit=2, # Optional
|
||||||
use_system_prompt=True, # Optional
|
use_system_prompt=True, # Optional
|
||||||
respect_context_window=True, # Optional
|
respect_context_window=True, # Optional
|
||||||
|
code_execution_mode='safe', # Optional, defaults to 'safe'
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -156,4 +158,4 @@ crew = my_crew.kickoff(inputs={"input": "Mark Twain"})
|
|||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents,
|
Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents,
|
||||||
you can create sophisticated AI systems that leverage the power of collaborative intelligence.
|
you can create sophisticated AI systems that leverage the power of collaborative intelligence. The `code_execution_mode` attribute provides flexibility in how agents execute code, allowing for both secure and direct execution options.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ icon: terminal
|
|||||||
|
|
||||||
# CrewAI CLI Documentation
|
# 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
|
## Installation
|
||||||
|
|
||||||
@@ -146,3 +146,34 @@ crewai run
|
|||||||
Make sure to run these commands from the directory where your CrewAI project is set up.
|
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.
|
Some commands may require additional configuration or setup within your project structure.
|
||||||
</Note>
|
</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 initially prompt 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ Alternatively, you can directly pass the OpenAIEmbeddingFunction to the embedder
|
|||||||
Example:
|
Example:
|
||||||
```python Code
|
```python Code
|
||||||
from crewai import Crew, Agent, Task, Process
|
from crewai import Crew, Agent, Task, Process
|
||||||
from chromadb.utils.embedding_functions.openai_embedding_function import OpenAIEmbeddingFunction
|
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
|
||||||
|
|
||||||
my_crew = Crew(
|
my_crew = Crew(
|
||||||
agents=[...],
|
agents=[...],
|
||||||
@@ -174,6 +174,7 @@ my_crew = Crew(
|
|||||||
### Using Azure OpenAI embeddings
|
### Using Azure OpenAI embeddings
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
|
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
|
||||||
from crewai import Crew, Agent, Task, Process
|
from crewai import Crew, Agent, Task, Process
|
||||||
|
|
||||||
my_crew = Crew(
|
my_crew = Crew(
|
||||||
@@ -182,7 +183,7 @@ my_crew = Crew(
|
|||||||
process=Process.sequential,
|
process=Process.sequential,
|
||||||
memory=True,
|
memory=True,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
embedder=embedding_functions.OpenAIEmbeddingFunction(
|
embedder=OpenAIEmbeddingFunction(
|
||||||
api_key="YOUR_API_KEY",
|
api_key="YOUR_API_KEY",
|
||||||
api_base="YOUR_API_BASE_PATH",
|
api_base="YOUR_API_BASE_PATH",
|
||||||
api_type="azure",
|
api_type="azure",
|
||||||
@@ -195,6 +196,7 @@ my_crew = Crew(
|
|||||||
### Using Vertex AI embeddings
|
### Using Vertex AI embeddings
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
|
from chromadb.utils.embedding_functions import GoogleVertexEmbeddingFunction
|
||||||
from crewai import Crew, Agent, Task, Process
|
from crewai import Crew, Agent, Task, Process
|
||||||
|
|
||||||
my_crew = Crew(
|
my_crew = Crew(
|
||||||
@@ -203,7 +205,7 @@ my_crew = Crew(
|
|||||||
process=Process.sequential,
|
process=Process.sequential,
|
||||||
memory=True,
|
memory=True,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
embedder=embedding_functions.GoogleVertexEmbeddingFunction(
|
embedder=GoogleVertexEmbeddingFunction(
|
||||||
project_id="YOUR_PROJECT_ID",
|
project_id="YOUR_PROJECT_ID",
|
||||||
region="YOUR_REGION",
|
region="YOUR_REGION",
|
||||||
api_key="YOUR_API_KEY",
|
api_key="YOUR_API_KEY",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "crewai"
|
name = "crewai"
|
||||||
version = "0.75.1"
|
version = "0.76.2"
|
||||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10,<=3.13"
|
requires-python = ">=3.10,<=3.13"
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ warnings.filterwarnings(
|
|||||||
category=UserWarning,
|
category=UserWarning,
|
||||||
module="pydantic.main",
|
module="pydantic.main",
|
||||||
)
|
)
|
||||||
__version__ = "0.75.1"
|
__version__ = "0.76.2"
|
||||||
__all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"]
|
__all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
from typing import Any, List, Optional, Union
|
from typing import Any, List, Literal, Optional, Union
|
||||||
|
|
||||||
from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
||||||
|
|
||||||
@@ -112,6 +114,10 @@ class Agent(BaseAgent):
|
|||||||
default=2,
|
default=2,
|
||||||
description="Maximum number of retries for an agent to execute a task when an error occurs.",
|
description="Maximum number of retries for an agent to execute a task when an error occurs.",
|
||||||
)
|
)
|
||||||
|
code_execution_mode: Literal["safe", "unsafe"] = Field(
|
||||||
|
default="safe",
|
||||||
|
description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).",
|
||||||
|
)
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_init_setup(self):
|
def post_init_setup(self):
|
||||||
@@ -173,6 +179,9 @@ class Agent(BaseAgent):
|
|||||||
if not self.agent_executor:
|
if not self.agent_executor:
|
||||||
self._setup_agent_executor()
|
self._setup_agent_executor()
|
||||||
|
|
||||||
|
if self.allow_code_execution:
|
||||||
|
self._validate_docker_installation()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _setup_agent_executor(self):
|
def _setup_agent_executor(self):
|
||||||
@@ -308,7 +317,9 @@ class Agent(BaseAgent):
|
|||||||
try:
|
try:
|
||||||
from crewai_tools import CodeInterpreterTool
|
from crewai_tools import CodeInterpreterTool
|
||||||
|
|
||||||
return [CodeInterpreterTool()]
|
# Set the unsafe_mode based on the code_execution_mode attribute
|
||||||
|
unsafe_mode = self.code_execution_mode == "unsafe"
|
||||||
|
return [CodeInterpreterTool(unsafe_mode=unsafe_mode)]
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
self._logger.log(
|
self._logger.log(
|
||||||
"info", "Coding tools not available. Install crewai_tools. "
|
"info", "Coding tools not available. Install crewai_tools. "
|
||||||
@@ -408,6 +419,25 @@ class Agent(BaseAgent):
|
|||||||
|
|
||||||
return "\n".join(tool_strings)
|
return "\n".join(tool_strings)
|
||||||
|
|
||||||
|
def _validate_docker_installation(self) -> None:
|
||||||
|
"""Check if Docker is installed and running."""
|
||||||
|
if not shutil.which("docker"):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Docker is not installed. Please install Docker to use code execution with agent: {self.role}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "info"],
|
||||||
|
check=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Docker is not running. Please start Docker to use code execution with agent: {self.role}"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __tools_names(tools) -> str:
|
def __tools_names(tools) -> str:
|
||||||
return ", ".join([t.name for t in tools])
|
return ", ".join([t.name for t in tools])
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ def crewai():
|
|||||||
@click.argument("type", type=click.Choice(["crew", "pipeline", "flow"]))
|
@click.argument("type", type=click.Choice(["crew", "pipeline", "flow"]))
|
||||||
@click.argument("name")
|
@click.argument("name")
|
||||||
@click.option("--provider", type=str, help="The provider to use for the crew")
|
@click.option("--provider", type=str, help="The provider to use for the crew")
|
||||||
def create(type, name, provider):
|
@click.option("--skip_provider", is_flag=True, help="Skip provider validation")
|
||||||
|
def create(type, name, provider, skip_provider=False):
|
||||||
"""Create a new crew, pipeline, or flow."""
|
"""Create a new crew, pipeline, or flow."""
|
||||||
if type == "crew":
|
if type == "crew":
|
||||||
create_crew(name, provider)
|
create_crew(name, provider, skip_provider)
|
||||||
elif type == "pipeline":
|
elif type == "pipeline":
|
||||||
create_pipeline(name)
|
create_pipeline(name)
|
||||||
elif type == "flow":
|
elif type == "flow":
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
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, PROVIDERS
|
|
||||||
from crewai.cli.constants import ENV_VARS
|
from crewai.cli.constants import ENV_VARS
|
||||||
|
from crewai.cli.provider import (
|
||||||
|
PROVIDERS,
|
||||||
|
get_provider_data,
|
||||||
|
select_model,
|
||||||
|
select_provider,
|
||||||
|
)
|
||||||
|
from crewai.cli.utils import copy_template, load_env_vars, write_env_file
|
||||||
|
|
||||||
|
|
||||||
def create_folder_structure(name, parent_folder=None):
|
def create_folder_structure(name, parent_folder=None):
|
||||||
@@ -14,11 +22,19 @@ def create_folder_structure(name, parent_folder=None):
|
|||||||
else:
|
else:
|
||||||
folder_path = Path(folder_name)
|
folder_path = Path(folder_name)
|
||||||
|
|
||||||
click.secho(
|
if folder_path.exists():
|
||||||
f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...",
|
if not click.confirm(
|
||||||
fg="green",
|
f"Folder {folder_name} already exists. Do you want to override it?"
|
||||||
bold=True,
|
):
|
||||||
)
|
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():
|
if not folder_path.exists():
|
||||||
folder_path.mkdir(parents=True)
|
folder_path.mkdir(parents=True)
|
||||||
@@ -27,11 +43,6 @@ def create_folder_structure(name, parent_folder=None):
|
|||||||
(folder_path / "src" / folder_name).mkdir(parents=True)
|
(folder_path / "src" / folder_name).mkdir(parents=True)
|
||||||
(folder_path / "src" / folder_name / "tools").mkdir(parents=True)
|
(folder_path / "src" / folder_name / "tools").mkdir(parents=True)
|
||||||
(folder_path / "src" / folder_name / "config").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
|
return folder_path, folder_name, class_name
|
||||||
|
|
||||||
@@ -70,38 +81,84 @@ def copy_template_files(folder_path, name, class_name, parent_folder):
|
|||||||
copy_template(src_file, dst_file, name, class_name, folder_path.name)
|
copy_template(src_file, dst_file, name, class_name, folder_path.name)
|
||||||
|
|
||||||
|
|
||||||
def create_crew(name, provider=None, parent_folder=None):
|
def create_crew(name, provider=None, skip_provider=False, parent_folder=None):
|
||||||
folder_path, folder_name, class_name = create_folder_structure(name, parent_folder)
|
folder_path, folder_name, class_name = create_folder_structure(name, parent_folder)
|
||||||
env_vars = load_env_vars(folder_path)
|
env_vars = load_env_vars(folder_path)
|
||||||
|
if not skip_provider:
|
||||||
|
if not provider:
|
||||||
|
provider_models = get_provider_data()
|
||||||
|
if not provider_models:
|
||||||
|
return
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
if not provider:
|
|
||||||
provider_models = get_provider_data()
|
provider_models = get_provider_data()
|
||||||
if not provider_models:
|
if not provider_models:
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_provider = select_provider(provider_models)
|
while True:
|
||||||
if not selected_provider:
|
selected_provider = select_provider(provider_models)
|
||||||
return
|
if selected_provider is None: # User typed 'q'
|
||||||
provider = selected_provider
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
# selected_model = select_model(provider, provider_models)
|
while True:
|
||||||
# if not selected_model:
|
selected_model = select_model(selected_provider, provider_models)
|
||||||
# return
|
if selected_model is None: # User typed 'q'
|
||||||
# model = selected_model
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
if provider in PROVIDERS:
|
if selected_provider in PROVIDERS:
|
||||||
api_key_var = ENV_VARS[provider][0]
|
api_key_var = ENV_VARS[selected_provider][0]
|
||||||
else:
|
else:
|
||||||
api_key_var = click.prompt(
|
api_key_var = click.prompt(
|
||||||
f"Enter the environment variable name for your {provider.capitalize()} API key",
|
f"Enter the environment variable name for your {selected_provider.capitalize()} API key",
|
||||||
type=str,
|
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 = ""
|
||||||
|
|
||||||
env_vars = {api_key_var: "YOUR_API_KEY_HERE"}
|
if api_key_value.strip():
|
||||||
write_env_file(folder_path, env_vars)
|
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'] = model
|
env_vars["MODEL"] = selected_model
|
||||||
# click.secho(f"Selected model: {model}", fg="green")
|
click.secho(f"Selected model: {selected_model}", fg="green")
|
||||||
|
|
||||||
package_dir = Path(__file__).parent
|
package_dir = Path(__file__).parent
|
||||||
templates_dir = package_dir / "templates" / "crew"
|
templates_dir = package_dir / "templates" / "crew"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ def plot_flow() -> None:
|
|||||||
"""
|
"""
|
||||||
Plot the flow by running a command in the UV environment.
|
Plot the flow by running a command in the UV environment.
|
||||||
"""
|
"""
|
||||||
command = ["uv", "run", "plot_flow"]
|
command = ["uv", "run", "plot"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(command, capture_output=False, text=True, check=True)
|
result = subprocess.run(command, capture_output=False, text=True, check=True)
|
||||||
|
|||||||
@@ -1,67 +1,91 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import requests
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from pathlib import Path
|
import requests
|
||||||
from crewai.cli.constants import PROVIDERS, MODELS, JSON_URL
|
|
||||||
|
from crewai.cli.constants import JSON_URL, MODELS, PROVIDERS
|
||||||
|
|
||||||
|
|
||||||
def select_choice(prompt_message, choices):
|
def select_choice(prompt_message, choices):
|
||||||
"""
|
"""
|
||||||
Presents a list of choices to the user and prompts them to select one.
|
Presents a list of choices to the user and prompts them to select one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- prompt_message (str): The message to display to the user before presenting the choices.
|
- prompt_message (str): The message to display to the user before presenting the choices.
|
||||||
- choices (list): A list of options to present to the user.
|
- choices (list): A list of options to present to the user.
|
||||||
|
|
||||||
Returns:
|
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")
|
click.secho(prompt_message, fg="cyan")
|
||||||
for idx, choice in enumerate(choices, start=1):
|
for idx, choice in enumerate(choices, start=1):
|
||||||
click.secho(f"{idx}. {choice}", fg="cyan")
|
click.secho(f"{idx}. {choice}", fg="cyan")
|
||||||
try:
|
click.secho("q. Quit", fg="cyan")
|
||||||
selected_index = click.prompt("Enter the number of your choice", type=int) - 1
|
|
||||||
except click.exceptions.Abort:
|
while True:
|
||||||
click.secho("Operation aborted by the user.", fg="red")
|
choice = click.prompt(
|
||||||
return None
|
"Enter the number of your choice or 'q' to quit", type=str
|
||||||
if not (0 <= selected_index < len(choices)):
|
)
|
||||||
click.secho("Invalid selection.", fg="red")
|
|
||||||
return None
|
if choice.lower() == "q":
|
||||||
return choices[selected_index]
|
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):
|
def select_provider(provider_models):
|
||||||
"""
|
"""
|
||||||
Presents a list of providers to the user and prompts them to select one.
|
Presents a list of providers to the user and prompts them to select one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- provider_models (dict): A dictionary of provider models.
|
- provider_models (dict): A dictionary of provider models.
|
||||||
|
|
||||||
Returns:
|
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]
|
predefined_providers = [p.lower() for p in PROVIDERS]
|
||||||
all_providers = sorted(set(predefined_providers + list(provider_models.keys())))
|
all_providers = sorted(set(predefined_providers + list(provider_models.keys())))
|
||||||
|
|
||||||
provider = select_choice("Select a provider to set up:", predefined_providers + ['other'])
|
provider = select_choice(
|
||||||
if not provider:
|
"Select a provider to set up:", predefined_providers + ["other"]
|
||||||
|
)
|
||||||
|
if provider is None: # User typed 'q'
|
||||||
return None
|
return None
|
||||||
provider = provider.lower()
|
|
||||||
|
|
||||||
if provider == 'other':
|
if provider == "other":
|
||||||
provider = select_choice("Select a provider from the full list:", all_providers)
|
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 None
|
||||||
return provider
|
|
||||||
|
return provider.lower() if provider else False
|
||||||
|
|
||||||
|
|
||||||
def select_model(provider, provider_models):
|
def select_model(provider, provider_models):
|
||||||
"""
|
"""
|
||||||
Presents a list of models for a given provider to the user and prompts them to select one.
|
Presents a list of models for a given provider to the user and prompts them to select one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- provider (str): The provider for which to select a model.
|
- provider (str): The provider for which to select a model.
|
||||||
- provider_models (dict): A dictionary of provider models.
|
- provider_models (dict): A dictionary of provider models.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- str: The selected model, or None if the operation is aborted or an invalid selection is made.
|
- str: The selected model, or None if the operation is aborted or an invalid selection is made.
|
||||||
"""
|
"""
|
||||||
@@ -76,37 +100,49 @@ def select_model(provider, provider_models):
|
|||||||
click.secho(f"No models available for provider '{provider}'.", fg="red")
|
click.secho(f"No models available for provider '{provider}'.", fg="red")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
selected_model = select_choice(f"Select a model to use for {provider.capitalize()}:", available_models)
|
selected_model = select_choice(
|
||||||
|
f"Select a model to use for {provider.capitalize()}:", available_models
|
||||||
|
)
|
||||||
return selected_model
|
return selected_model
|
||||||
|
|
||||||
|
|
||||||
def load_provider_data(cache_file, cache_expiry):
|
def load_provider_data(cache_file, cache_expiry):
|
||||||
"""
|
"""
|
||||||
Loads provider data from a cache file if it exists and is not expired. If the cache is expired or corrupted, it fetches the data from the web.
|
Loads provider data from a cache file if it exists and is not expired. If the cache is expired or corrupted, it fetches the data from the web.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- cache_file (Path): The path to the cache file.
|
- cache_file (Path): The path to the cache file.
|
||||||
- cache_expiry (int): The cache expiry time in seconds.
|
- cache_expiry (int): The cache expiry time in seconds.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- dict or None: The loaded provider data or None if the operation fails.
|
- dict or None: The loaded provider data or None if the operation fails.
|
||||||
"""
|
"""
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if cache_file.exists() and (current_time - cache_file.stat().st_mtime) < cache_expiry:
|
if (
|
||||||
|
cache_file.exists()
|
||||||
|
and (current_time - cache_file.stat().st_mtime) < cache_expiry
|
||||||
|
):
|
||||||
data = read_cache_file(cache_file)
|
data = read_cache_file(cache_file)
|
||||||
if data:
|
if data:
|
||||||
return data
|
return data
|
||||||
click.secho("Cache is corrupted. Fetching provider data from the web...", fg="yellow")
|
click.secho(
|
||||||
|
"Cache is corrupted. Fetching provider data from the web...", fg="yellow"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
click.secho("Cache expired or not found. Fetching provider data from the web...", fg="cyan")
|
click.secho(
|
||||||
|
"Cache expired or not found. Fetching provider data from the web...",
|
||||||
|
fg="cyan",
|
||||||
|
)
|
||||||
return fetch_provider_data(cache_file)
|
return fetch_provider_data(cache_file)
|
||||||
|
|
||||||
|
|
||||||
def read_cache_file(cache_file):
|
def read_cache_file(cache_file):
|
||||||
"""
|
"""
|
||||||
Reads and returns the JSON content from a cache file. Returns None if the file contains invalid JSON.
|
Reads and returns the JSON content from a cache file. Returns None if the file contains invalid JSON.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- cache_file (Path): The path to the cache file.
|
- cache_file (Path): The path to the cache file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- dict or None: The JSON content of the cache file or None if the JSON is invalid.
|
- dict or None: The JSON content of the cache file or None if the JSON is invalid.
|
||||||
"""
|
"""
|
||||||
@@ -116,13 +152,14 @@ def read_cache_file(cache_file):
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def fetch_provider_data(cache_file):
|
def fetch_provider_data(cache_file):
|
||||||
"""
|
"""
|
||||||
Fetches provider data from a specified URL and caches it to a file.
|
Fetches provider data from a specified URL and caches it to a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- cache_file (Path): The path to the cache file.
|
- cache_file (Path): The path to the cache file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- dict or None: The fetched provider data or None if the operation fails.
|
- dict or None: The fetched provider data or None if the operation fails.
|
||||||
"""
|
"""
|
||||||
@@ -139,38 +176,42 @@ def fetch_provider_data(cache_file):
|
|||||||
click.secho("Error parsing provider data. Invalid JSON format.", fg="red")
|
click.secho("Error parsing provider data. Invalid JSON format.", fg="red")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def download_data(response):
|
def download_data(response):
|
||||||
"""
|
"""
|
||||||
Downloads data from a given HTTP response and returns the JSON content.
|
Downloads data from a given HTTP response and returns the JSON content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- response (requests.Response): The HTTP response object.
|
- response (requests.Response): The HTTP response object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- dict: The JSON content of the response.
|
- dict: The JSON content of the response.
|
||||||
"""
|
"""
|
||||||
total_size = int(response.headers.get('content-length', 0))
|
total_size = int(response.headers.get("content-length", 0))
|
||||||
block_size = 8192
|
block_size = 8192
|
||||||
data_chunks = []
|
data_chunks = []
|
||||||
with click.progressbar(length=total_size, label='Downloading', show_pos=True) as progress_bar:
|
with click.progressbar(
|
||||||
|
length=total_size, label="Downloading", show_pos=True
|
||||||
|
) as progress_bar:
|
||||||
for chunk in response.iter_content(block_size):
|
for chunk in response.iter_content(block_size):
|
||||||
if chunk:
|
if chunk:
|
||||||
data_chunks.append(chunk)
|
data_chunks.append(chunk)
|
||||||
progress_bar.update(len(chunk))
|
progress_bar.update(len(chunk))
|
||||||
data_content = b''.join(data_chunks)
|
data_content = b"".join(data_chunks)
|
||||||
return json.loads(data_content.decode('utf-8'))
|
return json.loads(data_content.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def get_provider_data():
|
def get_provider_data():
|
||||||
"""
|
"""
|
||||||
Retrieves provider data from a cache file, filters out models based on provider criteria, and returns a dictionary of providers mapped to their models.
|
Retrieves provider data from a cache file, filters out models based on provider criteria, and returns a dictionary of providers mapped to their models.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- dict or None: A dictionary of providers mapped to their models or None if the operation fails.
|
- dict or None: A dictionary of providers mapped to their models or None if the operation fails.
|
||||||
"""
|
"""
|
||||||
cache_dir = Path.home() / '.crewai'
|
cache_dir = Path.home() / ".crewai"
|
||||||
cache_dir.mkdir(exist_ok=True)
|
cache_dir.mkdir(exist_ok=True)
|
||||||
cache_file = cache_dir / 'provider_cache.json'
|
cache_file = cache_dir / "provider_cache.json"
|
||||||
cache_expiry = 24 * 3600
|
cache_expiry = 24 * 3600
|
||||||
|
|
||||||
data = load_provider_data(cache_file, cache_expiry)
|
data = load_provider_data(cache_file, cache_expiry)
|
||||||
if not data:
|
if not data:
|
||||||
@@ -179,8 +220,8 @@ def get_provider_data():
|
|||||||
provider_models = defaultdict(list)
|
provider_models = defaultdict(list)
|
||||||
for model_name, properties in data.items():
|
for model_name, properties in data.items():
|
||||||
provider = properties.get("litellm_provider", "").strip().lower()
|
provider = properties.get("litellm_provider", "").strip().lower()
|
||||||
if 'http' in provider or provider == 'other':
|
if "http" in provider or provider == "other":
|
||||||
continue
|
continue
|
||||||
if provider:
|
if provider:
|
||||||
provider_models[provider].append(model_name)
|
provider_models[provider].append(model_name)
|
||||||
return provider_models
|
return provider_models
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<=3.13"
|
requires-python = ">=3.10,<=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.75.1,<1.0.0"
|
"crewai[tools]>=0.76.2,<1.0.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<=3.13"
|
requires-python = ">=3.10,<=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.75.1,<1.0.0",
|
"crewai[tools]>=0.76.2,<1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ authors = ["Your Name <you@example.com>"]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.10,<=3.13"
|
python = ">=3.10,<=3.13"
|
||||||
crewai = { extras = ["tools"], version = ">=0.75.1,<1.0.0" }
|
crewai = { extras = ["tools"], version = ">=0.76.2,<1.0.0" }
|
||||||
asyncio = "*"
|
asyncio = "*"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = ["Your Name <you@example.com>"]
|
authors = ["Your Name <you@example.com>"]
|
||||||
requires-python = ">=3.10,<=3.13"
|
requires-python = ">=3.10,<=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.75.1,<1.0.0"
|
"crewai[tools]>=0.76.2,<1.0.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10,<=3.13"
|
requires-python = ">=3.10,<=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.75.1"
|
"crewai[tools]>=0.76.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user