Consolidate crewai run and crewai flow kickoff (#6296)

Make `crewai run` the single execution path for crews and flows, with
`crewai flow kickoff` kept as a deprecated compatibility alias.
This commit is contained in:
Vinicius Brasil
2026-06-22 20:44:08 -07:00
committed by GitHub
parent 720a4c7216
commit 221dfdb08e
24 changed files with 351 additions and 172 deletions

View File

@@ -12,6 +12,7 @@ from crewai_cli.cli import (
deploy_remove,
deply_status,
flow_add_crew,
flow_run,
login,
reset_memories,
run,
@@ -126,40 +127,72 @@ def test_run_uses_project_runner_by_default(run_crew, runner):
result = runner.invoke(run)
assert result.exit_code == 0
run_crew.assert_called_once_with(trained_agents_file=None)
run_crew.assert_called_once_with(
trained_agents_file=None,
definition=None,
inputs=None,
)
assert "experimental" not in result.output.lower()
@mock.patch("crewai_cli.cli.run_declarative_flow")
def test_run_with_definition_uses_definition_runner(run_declarative_flow, runner):
@mock.patch("crewai_cli.cli.run_crew")
def test_run_with_definition_uses_project_runner(run_crew, runner):
result = runner.invoke(
run,
["--definition", "flow.yaml", "--inputs", '{"topic":"AI"}'],
)
assert result.exit_code == 0
assert (
"Warning: `crewai run --definition` is experimental and may change without notice."
in result.output
)
run_declarative_flow.assert_called_once_with(
definition="flow.yaml", inputs='{"topic":"AI"}'
run_crew.assert_called_once_with(
trained_agents_file=None,
definition="flow.yaml",
inputs='{"topic":"AI"}',
)
@mock.patch("crewai_cli.cli.run_crew")
@mock.patch("crewai_cli.cli.run_declarative_flow")
def test_run_rejects_inputs_without_definition(
run_declarative_flow, run_crew, runner
):
def test_run_rejects_inputs_without_definition(run_crew, runner):
result = runner.invoke(run, ["--inputs", '{"topic":"AI"}'])
assert result.exit_code == 2
assert "Error: --inputs requires --definition" in result.output
run_declarative_flow.assert_not_called()
run_crew.assert_not_called()
@mock.patch("crewai_cli.cli.run_crew")
def test_run_rejects_filename_with_definition(run_crew, runner):
result = runner.invoke(run, ["--definition", "flow.yaml", "--filename", "x.pkl"])
assert result.exit_code == 2
assert "Error: --filename can only be used when running crews" in result.output
run_crew.assert_not_called()
@mock.patch("crewai_cli.cli.run_crew")
def test_run_passes_filename_to_project_runner(run_crew, runner):
result = runner.invoke(run, ["--filename", "trained.pkl"])
assert result.exit_code == 0
run_crew.assert_called_once_with(
trained_agents_file="trained.pkl",
definition=None,
inputs=None,
)
@mock.patch("crewai_cli.cli.run_crew")
def test_flow_kickoff_is_deprecated_and_uses_run_path(run_crew, runner):
result = runner.invoke(flow_run)
assert result.exit_code == 0
run_crew.assert_called_once_with(
trained_agents_file=None,
definition=None,
inputs=None,
)
assert "DeprecationWarning" in result.output
@mock.patch("crewai_cli.create_json_crew.create_json_crew")
def test_create_crew_in_dmn_mode_skips_provider_prompts(create_json_crew, runner):
result = runner.invoke(create, ["crew", "DMN Crew"], env={"CREWAI_DMN": "True"})

View File

@@ -28,9 +28,7 @@ def test_create_flow_declarative_project_can_run(
assert (project_root / pyproject["tool"]["crewai"]["definition"]).is_file()
monkeypatch.chdir(project_root)
result = CliRunner().invoke(
crewai, ["flow", "kickoff"], env={"UV_RUN_RECURSION_DEPTH": "1"}
)
result = CliRunner().invoke(crewai, ["run"], env={"UV_RUN_RECURSION_DEPTH": "1"})
assert result.exit_code == 0
assert "Running the Flow" in result.output

View File

@@ -1,12 +1,12 @@
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import subprocess
import pytest
from click.testing import CliRunner
import crewai_cli.kickoff_flow as kickoff_flow_module
from crewai_cli.cli import flow_run
import crewai_cli.plot_flow as plot_flow_module
@@ -33,18 +33,19 @@ def _write_flow_project(project_root: Path) -> None:
)
def test_kickoff_flow_runs_configured_declarative_definition(
def test_flow_kickoff_runs_configured_declarative_definition(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
) -> None:
_write_flow_project(tmp_path)
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("UV_RUN_RECURSION_DEPTH", "1")
kickoff_flow_module.kickoff_flow()
result = CliRunner().invoke(flow_run)
assert capsys.readouterr().out == "AI\n"
assert result.exit_code == 0
assert "DeprecationWarning" in result.output
assert "Running the Flow\nAI\n" in result.output
def test_plot_flow_runs_configured_declarative_definition(
@@ -57,18 +58,27 @@ def test_plot_flow_runs_configured_declarative_definition(
plot_flow_module.plot_flow()
@pytest.mark.parametrize(
("command", "expected"),
[
pytest.param(kickoff_flow_module.kickoff_flow, ["uv", "run", "kickoff"]),
pytest.param(plot_flow_module.plot_flow, ["uv", "run", "plot"]),
],
)
def test_flow_commands_keep_python_entrypoint_without_definition(
def test_flow_kickoff_delegates_to_run_crew(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls = []
monkeypatch.setattr(
"crewai_cli.cli.run_crew",
lambda **kwargs: calls.append(kwargs),
)
result = CliRunner().invoke(flow_run)
assert result.exit_code == 0
assert calls == [
{"trained_agents_file": None, "definition": None, "inputs": None},
]
def test_plot_flow_keeps_python_entrypoint_without_definition(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
command: Callable[[], None],
expected: list[str],
) -> None:
subprocess_calls = []
@@ -79,11 +89,11 @@ def test_flow_commands_keep_python_entrypoint_without_definition(
lambda command, **kwargs: subprocess_calls.append((command, kwargs)),
)
command()
plot_flow_module.plot_flow()
assert subprocess_calls == [
(
expected,
["uv", "run", "plot"],
{"capture_output": False, "text": True, "check": True},
)
]

View File

@@ -568,3 +568,131 @@ def test_has_json_crew_true_without_pyproject(monkeypatch, tmp_path: Path):
(tmp_path / "crew.jsonc").write_text("{}")
assert run_crew_module._has_json_crew() is True
def test_run_crew_rejects_inputs_without_definition():
with pytest.raises(click.UsageError) as exc_info:
run_crew_module.run_crew(inputs='{"topic":"AI"}')
assert "--inputs requires --definition" in exc_info.value.message
def test_run_crew_rejects_filename_with_explicit_definition():
with pytest.raises(click.UsageError) as exc_info:
run_crew_module.run_crew(
trained_agents_file="trained.pkl",
definition="flow.yaml",
)
assert "--filename can only be used when running crews" in exc_info.value.message
def test_run_crew_runs_explicit_declarative_definition(monkeypatch, capsys):
calls = []
def fake_run_declarative_flow(definition: str, inputs: str | None = None):
calls.append((definition, inputs))
monkeypatch.setattr(
"crewai_cli.run_declarative_flow.run_declarative_flow",
fake_run_declarative_flow,
)
run_crew_module.run_crew(definition="flow.yaml", inputs='{"topic":"AI"}')
captured = capsys.readouterr()
assert "experimental" not in captured.out.lower()
assert calls == [("flow.yaml", '{"topic":"AI"}')]
def test_run_crew_runs_classic_crew_project(monkeypatch, capsys):
calls = []
monkeypatch.setattr(run_crew_module, "_has_json_crew", lambda: False)
monkeypatch.setattr(
run_crew_module,
"read_toml",
lambda: {"tool": {"crewai": {"type": "crew"}}},
)
monkeypatch.setattr(
run_crew_module,
"_execute_uv_script",
lambda script_name, **kwargs: calls.append((script_name, kwargs)),
)
run_crew_module.run_crew(trained_agents_file="trained.pkl")
assert capsys.readouterr().out == "Running the Crew\n"
assert calls == [
(
"run_crew",
{"entity_type": "crew", "trained_agents_file": "trained.pkl"},
)
]
def test_run_crew_runs_python_flow_project(monkeypatch, capsys):
calls = []
monkeypatch.setattr(run_crew_module, "_has_json_crew", lambda: False)
monkeypatch.setattr(
run_crew_module,
"read_toml",
lambda: {"tool": {"crewai": {"type": "flow"}}},
)
monkeypatch.setattr(
run_crew_module,
"_execute_uv_script",
lambda script_name, **kwargs: calls.append((script_name, kwargs)),
)
run_crew_module.run_crew()
assert capsys.readouterr().out == "Running the Flow\n"
assert calls == [("kickoff", {"entity_type": "flow"})]
def test_run_crew_rejects_filename_for_flow_project(monkeypatch):
monkeypatch.setattr(run_crew_module, "_has_json_crew", lambda: False)
monkeypatch.setattr(
run_crew_module,
"read_toml",
lambda: {"tool": {"crewai": {"type": "flow"}}},
)
with pytest.raises(click.UsageError) as exc_info:
run_crew_module.run_crew(trained_agents_file="trained.pkl")
assert "--filename can only be used when running crews" in exc_info.value.message
def test_run_crew_runs_configured_declarative_flow_project(monkeypatch, capsys):
calls = []
monkeypatch.setattr(run_crew_module, "_has_json_crew", lambda: False)
monkeypatch.setattr(
run_crew_module,
"read_toml",
lambda: {
"tool": {
"crewai": {
"type": "flow",
"definition": "flow.yaml",
}
}
},
)
monkeypatch.setattr(
"crewai_cli.run_declarative_flow.run_declarative_flow_in_project_env",
lambda definition, inputs=None: calls.append((definition, inputs)),
)
monkeypatch.setattr(
run_crew_module,
"_execute_uv_script",
lambda *_args, **_kwargs: pytest.fail("declarative flows must not run kickoff"),
)
run_crew_module.run_crew()
assert capsys.readouterr().out == "Running the Flow\n"
assert calls == [("flow.yaml", None)]

View File

@@ -83,7 +83,7 @@ def test_run_declarative_flow_in_project_env_uses_uv(
assert subprocess_calls == [
(
["uv", "run", "crewai", "flow", "kickoff"],
["uv", "run", "crewai", "run"],
{
"capture_output": False,
"text": True,