From 0e36d35409335037772f38d5f74d6a991aaaf36b Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Mon, 28 Oct 2024 15:30:57 -0500 Subject: [PATCH] Update flows cli to allow you to easily add additional crews to a flow --- docs/concepts/flows.mdx | 34 ++++++++++++++ src/crewai/cli/add_crew_to_flow.py | 72 ++++++++++++++++++++++++++++++ src/crewai/cli/cli.py | 19 ++++++-- tests/cli/cli_test.py | 51 +++++++++++++++++++++ 4 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/crewai/cli/add_crew_to_flow.py diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index dee6fd4fac..39cd635796 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -560,6 +560,40 @@ uv run kickoff The flow will execute, and you should see the output in the console. + +### Adding Additional Crews Using the CLI + +Once you have created your initial flow, you can easily add additional crews to your project using the CLI. This allows you to expand your flow's capabilities by integrating new crews without starting from scratch. + +To add a new crew to your existing flow, use the following command: + +```bash +crewai flow add-crew +``` + +This command will create a new directory for your crew within the `crews` folder of your flow project. It will include the necessary configuration files and a crew definition file, similar to the initial setup. + +#### Folder Structure + +After adding a new crew, your folder structure will look like this: + +name_of_flow/ +├── crews/ +│ ├── poem_crew/ +│ │ ├── config/ +│ │ │ ├── agents.yaml +│ │ │ └── tasks.yaml +│ │ └── poem_crew.py +│ └── name_of_crew/ +│ ├── config/ +│ │ ├── agents.yaml +│ │ └── tasks.yaml +│ └── name_of_crew.py + +You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs. + +By using the CLI to add additional crews, you can efficiently build complex AI workflows that leverage multiple crews working together. + ## Plot Flows Visualizing your AI workflows can provide valuable insights into the structure and execution paths of your flows. CrewAI offers a powerful visualization tool that allows you to generate interactive plots of your flows, making it easier to understand and optimize your AI workflows. diff --git a/src/crewai/cli/add_crew_to_flow.py b/src/crewai/cli/add_crew_to_flow.py new file mode 100644 index 0000000000..861b74dd80 --- /dev/null +++ b/src/crewai/cli/add_crew_to_flow.py @@ -0,0 +1,72 @@ +from pathlib import Path + +import click + +from .create_crew import copy_template + + +def add_crew_to_flow(crew_name: str) -> None: + """Add a new crew to the current flow.""" + # Check if pyproject.toml exists in the current directory + if not Path("pyproject.toml").exists(): + print("This command must be run from the root of a flow project.") + raise click.ClickException( + "This command must be run from the root of a flow project." + ) + + # Determine the flow folder based on the current directory + flow_folder = Path.cwd() + crews_folder = flow_folder / "src" / flow_folder.name / "crews" + + if not crews_folder.exists(): + print("Crews folder does not exist in the current flow.") + raise click.ClickException("Crews folder does not exist in the current flow.") + + # Create the crew within the flow's crews directory + create_embedded_crew(crew_name, parent_folder=crews_folder) + + click.secho( + f"Crew {crew_name} added to the current flow successfully!", + fg="green", + bold=True, + ) + + +def create_embedded_crew(crew_name: str, parent_folder: Path) -> None: + """Create a new crew within an existing flow project.""" + folder_name = crew_name.replace(" ", "_").replace("-", "_").lower() + class_name = crew_name.replace("_", " ").replace("-", " ").title().replace(" ", "") + + crew_folder = parent_folder / folder_name + + if crew_folder.exists(): + if not click.confirm( + f"Crew {folder_name} already exists. Do you want to override it?" + ): + click.secho("Operation cancelled.", fg="yellow") + return + click.secho(f"Overriding crew {folder_name}...", fg="green", bold=True) + else: + click.secho(f"Creating crew {folder_name}...", fg="green", bold=True) + crew_folder.mkdir(parents=True) + + # Create config and crew.py files + config_folder = crew_folder / "config" + config_folder.mkdir(exist_ok=True) + + templates_dir = Path(__file__).parent / "templates" / "crew" + config_template_files = ["agents.yaml", "tasks.yaml"] + crew_template_file = f"{folder_name}_crew.py" # Updated file name + + for file_name in config_template_files: + src_file = templates_dir / "config" / file_name + dst_file = config_folder / file_name + copy_template(src_file, dst_file, crew_name, class_name, folder_name) + + src_file = templates_dir / "crew.py" + dst_file = crew_folder / crew_template_file + copy_template(src_file, dst_file, crew_name, class_name, folder_name) + + click.secho( + f"Crew {crew_name} added to the flow successfully!", fg="green", bold=True + ) diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 1ea2ef4e21..0f43ff3f47 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -3,6 +3,7 @@ import click import pkg_resources +from crewai.cli.add_crew_to_flow import add_crew_to_flow from crewai.cli.create_crew import create_crew from crewai.cli.create_flow import create_flow from crewai.cli.create_pipeline import create_pipeline @@ -178,10 +179,12 @@ def test(n_iterations: int, model: str): evaluate_crew(n_iterations, model) -@crewai.command(context_settings=dict( - ignore_unknown_options=True, - allow_extra_args=True, -)) +@crewai.command( + context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ) +) @click.pass_context def install(context): """Install the Crew.""" @@ -324,5 +327,13 @@ def flow_plot(): plot_flow() +@flow.command(name="add-crew") +@click.argument("crew_name") +def flow_add_crew(crew_name): + """Add a crew to an existing flow.""" + click.echo(f"Adding crew {crew_name} to the flow") + add_crew_to_flow(crew_name) + + if __name__ == "__main__": crewai() diff --git a/tests/cli/cli_test.py b/tests/cli/cli_test.py index b2fb8d0e5c..26f979f698 100644 --- a/tests/cli/cli_test.py +++ b/tests/cli/cli_test.py @@ -1,7 +1,9 @@ +from pathlib import Path from unittest import mock import pytest from click.testing import CliRunner + from crewai.cli.cli import ( deploy_create, deploy_list, @@ -9,6 +11,7 @@ deploy_push, deploy_remove, deply_status, + flow_add_crew, reset_memories, signup, test, @@ -277,3 +280,51 @@ def test_deploy_remove_no_uuid(command, runner): assert result.exit_code == 0 mock_deploy.remove_crew.assert_called_once_with(uuid=None) + + +@mock.patch("crewai.cli.add_crew_to_flow.create_crew") +def test_add_crew_to_flow(create_crew_mock, runner): + # Simulate being in the root of a flow project + with mock.patch("pathlib.Path.cwd") as mock_cwd: + mock_cwd.return_value = Path("/path/to/flow_project") + with mock.patch("pathlib.Path.exists") as mock_exists: + + def exists_side_effect(self): + if self == Path("/path/to/flow_project/pyproject.toml"): + return True + if self == Path("/path/to/flow_project/src/flow_project/crews"): + return True + return False + + mock_exists.side_effect = exists_side_effect + + result = runner.invoke(flow_add_crew, ["new_crew"]) + + create_crew_mock.assert_called_once_with( + "new_crew", + parent_folder=Path("/path/to/flow_project/src/flow_project/crews"), + ) + assert result.exit_code == 0 + assert ( + "Crew new_crew added to the current flow successfully!" + in result.exception + ) + + +def test_add_crew_to_flow_not_in_root(runner): + # Simulate not being in the root of a flow project + with mock.patch("pathlib.Path.exists", autospec=True) as mock_exists: + # Mock Path.exists to return False when checking for pyproject.toml + def exists_side_effect(self): + if self.name == "pyproject.toml": + return False # Simulate that pyproject.toml does not exist + return True # All other paths exist + + mock_exists.side_effect = exists_side_effect + + result = runner.invoke(flow_add_crew, ["new_crew"]) + + assert result.exit_code != 0 + assert "This command must be run from the root of a flow project." in str( + result.output + )