mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-04 22:49:23 +00:00
Add unified declarative flow loading (#6308)
Add a single declaration loader shared by API and CLI callers.
- Add FlowDefinition.from_declaration for FlowDefinition instances, dictionaries, YAML/JSON strings, and file paths
- Add Flow.from_declaration to build runnable flows directly from the same inputs
- Route declarative flow CLI loading through Flow.from_declaration so path handling and validation stay centralized
```
# Load just the serializable definition when you do not need to run it yet.
definition = FlowDefinition.from_declaration(path="flows/research.crewai")
definition = FlowDefinition.from_declaration(contents=flow_yaml)
definition = FlowDefinition.from_declaration(contents=flow_dict)
# Build a runnable flow directly from the same declaration inputs.
flow = Flow.from_declaration(path="flows/research.crewai")
flow = Flow.from_declaration(contents=flow_yaml)
flow = Flow.from_declaration(contents=flow_dict)
flow = Flow.from_declaration(contents=definition)
# Run it like any other flow.
result = flow.kickoff(inputs={"topic": "AI agents"})
# The CLI now goes through the same path-based loader.
# crewai run --definition flows/research.crewai
```
This commit is contained in:
@@ -6,6 +6,7 @@ import subprocess
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from pydantic import ValidationError
|
||||
|
||||
from crewai_cli.utils import build_env_with_all_tool_credentials
|
||||
|
||||
@@ -65,7 +66,6 @@ def load_declarative_flow(definition: str) -> Any:
|
||||
"""Load a declarative Flow instance from a definition path."""
|
||||
try:
|
||||
from crewai.flow.flow import Flow
|
||||
from crewai.flow.flow_definition import FlowDefinition
|
||||
except ImportError as exc:
|
||||
click.echo(
|
||||
"Running declarative flows requires the full crewai package.",
|
||||
@@ -74,14 +74,30 @@ def load_declarative_flow(definition: str) -> Any:
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
definition_path = Path(definition).expanduser()
|
||||
definition_source = _read_declarative_flow_source(definition_path, definition)
|
||||
try:
|
||||
if not definition_path.is_file():
|
||||
if definition_path.exists():
|
||||
click.echo(
|
||||
f"Invalid --definition path: {definition} is not a file.",
|
||||
err=True,
|
||||
)
|
||||
raise SystemExit(1)
|
||||
click.echo(
|
||||
f"Invalid --definition path: {definition} does not exist.", err=True
|
||||
)
|
||||
raise SystemExit(1)
|
||||
except OSError as exc:
|
||||
click.echo(f"Invalid --definition path: {definition} ({exc})", err=True)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
flow_definition = _parse_declarative_flow(
|
||||
FlowDefinition,
|
||||
definition_source,
|
||||
source_path=definition_path,
|
||||
)
|
||||
return Flow.from_definition(flow_definition)
|
||||
try:
|
||||
return Flow.from_declaration(path=definition_path)
|
||||
except (OSError, UnicodeError, ValueError, ValidationError) as exc:
|
||||
click.echo(
|
||||
f"Unable to read --definition path {definition_path}: {exc}",
|
||||
err=True,
|
||||
)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
|
||||
def configured_project_declarative_flow(
|
||||
@@ -154,53 +170,6 @@ def _parse_inputs(inputs: str | None) -> dict[str, Any] | None:
|
||||
return parsed
|
||||
|
||||
|
||||
def _read_declarative_flow_source(path: Path, definition: str) -> str:
|
||||
try:
|
||||
if path.is_file():
|
||||
source = _read_declarative_flow_file(path)
|
||||
elif path.exists():
|
||||
click.echo(
|
||||
f"Invalid --definition path: {definition} is not a file.", err=True
|
||||
)
|
||||
raise SystemExit(1)
|
||||
else:
|
||||
click.echo(
|
||||
f"Invalid --definition path: {definition} does not exist.", err=True
|
||||
)
|
||||
raise SystemExit(1)
|
||||
except OSError as exc:
|
||||
click.echo(f"Invalid --definition path: {definition} ({exc})", err=True)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
return source
|
||||
|
||||
|
||||
def _read_declarative_flow_file(path: Path) -> str:
|
||||
try:
|
||||
source = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeError) as exc:
|
||||
click.echo(
|
||||
f"Unable to read --definition path {path}: {exc}",
|
||||
err=True,
|
||||
)
|
||||
raise SystemExit(1) from exc
|
||||
return source
|
||||
|
||||
|
||||
def _parse_declarative_flow(
|
||||
flow_definition_cls: type[Any], source: str, *, source_path: Path
|
||||
) -> Any:
|
||||
if _looks_like_json(source):
|
||||
return flow_definition_cls.from_json(source, source_path=source_path)
|
||||
|
||||
return flow_definition_cls.from_yaml(source, source_path=source_path)
|
||||
|
||||
|
||||
def _looks_like_json(source: str) -> bool:
|
||||
stripped = source.lstrip()
|
||||
return stripped.startswith("{")
|
||||
|
||||
|
||||
def _format_result(result: Any) -> str:
|
||||
raw_result = getattr(result, "raw", result)
|
||||
if isinstance(raw_result, str):
|
||||
|
||||
Reference in New Issue
Block a user