Compare commits

...

4 Commits

Author SHA1 Message Date
Vinicius Brasil
f2a074e35b docs: snapshot and changelog for v1.14.8a3 (#6310) 2026-06-23 14:11:31 -07:00
Vinicius Brasil
658b8ee8b9 feat: bump versions to 1.14.8a3 (#6309) 2026-06-23 14:05:23 -07:00
Vinicius Brasil
3452e5c187 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
```
2026-06-23 12:02:18 -07:00
Lucas Gomide
793539173d fix: pin opentelemetry to ~=1.42.0 (#6292)
The previous `~=1.34.0` pin kept us on the unmaintained 1.34 line —
last patched as `1.34.1` in June 2025, eight minor releases behind
upstream — and caused `_create_exp_backoff_generator` `ImportError`
crashes in factory deployments where the OpenTelemetry Operator's
injected init container shadows
`opentelemetry.exporter.otlp.proto.common._internal` with >=1.35 while
our `opentelemetry-exporter-otlp-proto-grpc==1.34.1` still imports the
removed private symbol. Pinning to `~=1.42.0` tracks the current
upstream stable line; the resolver now lands on 1.42.1 and our public
OTel trace API usage is unaffected.
2026-06-23 14:51:22 -04:00
25 changed files with 505 additions and 221 deletions

View File

@@ -4,6 +4,34 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
icon: "clock"
mode: "wide"
---
<Update label="23 يونيو 2026">
## v1.14.8a3
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.8a3)
## ما الذي تغير
### الميزات
- إضافة تحميل تدفق موحد إعلاني
- تحسين تجربة بدء تشغيل crewai run
- دمج `crewai run` و `crewai flow kickoff`
- الحفاظ على تقدم طريقة التدفق مرئيًا للفرق المتداخلة
- إضافة دعم واجهة سطر الأوامر الإعلانية للتدفق
- السماح باستخدام `@router()` كطريقة بدء لتدفق
- إضافة مخططات مخرجات مكتوبة لأدوات CrewAI
### إصلاحات الأخطاء
- تثبيت opentelemetry على ~=1.42.0
### الوثائق
- إضافة صفحة استوديو "بطاقة واحدة لكل خطوة"
## المساهمون
@jessemiller, @joaomdmoura, @lucasgomide, @vinibrsl
</Update>
<Update label="18 يونيو 2026">
## v1.14.8a2

View File

@@ -4,6 +4,34 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="Jun 23, 2026">
## v1.14.8a3
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.8a3)
## What's Changed
### Features
- Add unified declarative flow loading
- Improve crewai run startup UX
- Consolidate `crewai run` and `crewai flow kickoff`
- Keep flow method progress visible for nested crews
- Add declarative Flow CLI support
- Allow `@router()` as start method of a flow
- Add typed output schemas for CrewAI tools
### Bug Fixes
- Pin opentelemetry to ~=1.42.0
### Documentation
- Add "One Card per Step" Studio page
## Contributors
@jessemiller, @joaomdmoura, @lucasgomide, @vinibrsl
</Update>
<Update label="Jun 18, 2026">
## v1.14.8a2

View File

@@ -4,6 +4,34 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
icon: "clock"
mode: "wide"
---
<Update label="2026년 6월 23일">
## v1.14.8a3
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.14.8a3)
## 변경 사항
### 기능
- 통합 선언적 흐름 로딩 추가
- crewai run 시작 UX 개선
- `crewai run`과 `crewai flow kickoff` 통합
- 중첩된 크루의 흐름 메서드 진행 상황 표시 유지
- 선언적 Flow CLI 지원 추가
- 흐름의 시작 메서드로 `@router()` 허용
- CrewAI 도구에 대한 타입이 지정된 출력 스키마 추가
### 버그 수정
- opentelemetry를 ~=1.42.0으로 고정
### 문서
- "단계당 한 카드" 스튜디오 페이지 추가
## 기여자
@jessemiller, @joaomdmoura, @lucasgomide, @vinibrsl
</Update>
<Update label="2026년 6월 18일">
## v1.14.8a2

View File

@@ -4,6 +4,34 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="23 jun 2026">
## v1.14.8a3
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.8a3)
## O que Mudou
### Recursos
- Adicionar carregamento de fluxo declarativo unificado
- Melhorar a experiência de inicialização do crewai run
- Consolidar `crewai run` e `crewai flow kickoff`
- Manter o progresso do método de fluxo visível para equipes aninhadas
- Adicionar suporte a Flow CLI declarativo
- Permitir `@router()` como método de início de um fluxo
- Adicionar esquemas de saída tipados para ferramentas CrewAI
### Correções de Bugs
- Fixar opentelemetry em ~=1.42.0
### Documentação
- Adicionar página "Uma Cartão por Etapa" no Studio
## Contribuidores
@jessemiller, @joaomdmoura, @lucasgomide, @vinibrsl
</Update>
<Update label="18 jun 2026">
## v1.14.8a2

View File

@@ -8,7 +8,7 @@ authors = [
]
requires-python = ">=3.10, <3.14"
dependencies = [
"crewai-core==1.14.8a2",
"crewai-core==1.14.8a3",
"click>=8.1.7,<9",
"pydantic>=2.11.9,<2.13",
"pydantic-settings~=2.10.1",

View File

@@ -1 +1 @@
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"

View File

@@ -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):

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.14.8a2"
"crewai[tools]==1.14.8a3"
]
[project.scripts]

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.14.8a2"
"crewai[tools]==1.14.8a3"
]
[build-system]

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.14.8a2"
"crewai[tools]==1.14.8a3"
]
[project.scripts]

View File

@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
readme = "README.md"
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.14.8a2"
"crewai[tools]==1.14.8a3"
]
[tool.crewai]

View File

@@ -60,6 +60,43 @@ def test_run_declarative_flow_reports_missing_file(
)
def test_run_declarative_flow_reports_empty_file(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
definition_path = tmp_path / "flow.yaml"
definition_path.write_text(" \n", encoding="utf-8")
with pytest.raises(SystemExit):
run_declarative_flow_module.run_declarative_flow(str(definition_path))
assert "Flow declaration file is empty" in capsys.readouterr().err
@pytest.mark.parametrize(
"contents, expected_error",
[
("[]\n", "Flow declaration must contain a mapping"),
("schema: crewai.flow/v1\nmethods: {}\n", "Field required"),
],
)
def test_load_declarative_flow_reports_invalid_declarations(
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
contents: str,
expected_error: str,
) -> None:
definition_path = tmp_path / "flow.yaml"
definition_path.write_text(contents, encoding="utf-8")
with pytest.raises(SystemExit) as exc_info:
run_declarative_flow_module.load_declarative_flow(str(definition_path))
assert exc_info.value.code == 1
stderr = capsys.readouterr().err
assert f"Unable to read --definition path {definition_path}:" in stderr
assert expected_error in stderr
def test_run_declarative_flow_in_project_env_uses_uv(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:

View File

@@ -16,9 +16,9 @@ dependencies = [
"pyjwt>=2.13.0,<3",
"pydantic>=2.11.9,<2.13",
"rich>=13.7.1",
"opentelemetry-api~=1.34.0",
"opentelemetry-sdk~=1.34.0",
"opentelemetry-exporter-otlp-proto-http~=1.34.0",
"opentelemetry-api~=1.42.0",
"opentelemetry-sdk~=1.42.0",
"opentelemetry-exporter-otlp-proto-http~=1.42.0",
"tomli~=2.0.2",
]

View File

@@ -1 +1 @@
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"

View File

@@ -152,4 +152,4 @@ __all__ = [
"wrap_file_source",
]
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"

View File

@@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14"
dependencies = [
"pytube~=15.0.0",
"requests>=2.33.0,<3",
"crewai==1.14.8a2",
"crewai==1.14.8a3",
"tiktoken>=0.8.0,<0.13",
"beautifulsoup4~=4.13.4",
"python-docx~=1.2.0",

View File

@@ -330,4 +330,4 @@ __all__ = [
"ZapierActionTools",
]
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"

View File

@@ -8,8 +8,8 @@ authors = [
]
requires-python = ">=3.10, <3.14"
dependencies = [
"crewai-core==1.14.8a2",
"crewai-cli==1.14.8a2",
"crewai-core==1.14.8a3",
"crewai-cli==1.14.8a3",
# Core Dependencies
"pydantic>=2.11.9,<2.13",
"openai>=2.30.0,<3",
@@ -18,9 +18,9 @@ dependencies = [
"pdfplumber~=0.11.4",
"regex~=2026.1.15",
# Telemetry and Monitoring
"opentelemetry-api~=1.34.0",
"opentelemetry-sdk~=1.34.0",
"opentelemetry-exporter-otlp-proto-http~=1.34.0",
"opentelemetry-api~=1.42.0",
"opentelemetry-sdk~=1.42.0",
"opentelemetry-exporter-otlp-proto-http~=1.42.0",
# Data Handling
"chromadb~=1.1.0",
"tokenizers>=0.21,<1",
@@ -55,7 +55,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = [
"crewai-tools==1.14.8a2",
"crewai-tools==1.14.8a3",
]
embeddings = [
"tiktoken>=0.8.0,<0.13"

View File

@@ -48,7 +48,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
_suppress_pydantic_deprecation_warnings()
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),

View File

@@ -1,7 +1,7 @@
"""Flow Definition: the serializable, declarative Flow contract.
Defines :class:`FlowDefinition` and its sub-models — a static, declarative
(JSON/YAML) representation of a Flow: its methods, trigger conditions,
Defines :class:`FlowDefinition` and its sub-models — a static declarative
representation of a Flow: its methods, trigger conditions,
state, and configuration. It is independent of the Python authoring
layer that may have produced it and of the engine that runs it (see
``runtime``).
@@ -235,7 +235,7 @@ class FlowPersistenceDefinition(BaseModel):
``persistence`` may hold a live backend when the definition is built from
a decorated class — the engine then persists through the exact instance
the user configured; the JSON/YAML projection degrades it to its
the user configured; the declarative projection degrades it to its
serialized config.
"""
@@ -275,7 +275,7 @@ class FlowHumanFeedbackDefinition(BaseModel):
"""Static human feedback configuration.
``llm`` and ``provider`` may hold live Python objects when the definition
is built from a decorated class; the JSON/YAML projection degrades them to
is built from a decorated class; the declarative projection degrades them to
a serialized config (``llm``) or a ``module:qualname`` ref (``provider``).
"""
@@ -777,7 +777,7 @@ class FlowDefinition(BaseModel):
return self
def to_dict(self, *, exclude_none: bool = True) -> dict[str, Any]:
"""Serialize the definition to a JSON/YAML-ready dictionary."""
"""Serialize the definition to a declaration-ready dictionary."""
return self.model_dump(by_alias=True, exclude_none=exclude_none, mode="json")
def to_json(self, *, indent: int | None = 2, exclude_none: bool = True) -> str:
@@ -817,16 +817,37 @@ class FlowDefinition(BaseModel):
return definition
@classmethod
def from_json(cls, data: str, *, source_path: Path | None = None) -> FlowDefinition:
"""Load a definition from JSON."""
return cls.from_dict(json.loads(data), source_path=source_path)
def from_declaration(
cls,
*,
contents: FlowDefinition | str | dict[str, Any] | None = None,
path: Path | str | None = None,
) -> FlowDefinition:
"""Load a declarative flow from contents or a file path."""
if isinstance(contents, cls):
return contents
@classmethod
def from_yaml(cls, data: str, *, source_path: Path | None = None) -> FlowDefinition:
"""Load a definition from YAML."""
loaded = yaml.safe_load(data) or {}
source_path: Path | None = None
if contents is None:
if path is None:
raise ValueError("Provide contents or path")
source_path = Path(path)
contents = source_path.expanduser().read_text(encoding="utf-8")
if isinstance(contents, dict):
return cls.from_dict(contents)
if not isinstance(contents, str):
raise TypeError("Flow declaration contents must be a string or dictionary")
if not contents.strip():
if source_path is not None:
raise ValueError(f"Flow declaration file is empty: {source_path}")
raise ValueError("Flow declaration contents are empty")
loaded = yaml.safe_load(contents)
if not isinstance(loaded, dict):
raise ValueError("Flow definition YAML must contain a mapping")
raise ValueError("Flow declaration must contain a mapping")
return cls.from_dict(loaded, source_path=source_path)
@classmethod

View File

@@ -25,6 +25,7 @@ from datetime import datetime
import enum
import inspect
import logging
from pathlib import Path
import threading
from typing import (
TYPE_CHECKING,
@@ -769,6 +770,21 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta):
@classmethod
def from_definition(cls, definition: FlowDefinition, **kwargs: Any) -> Flow[Any]:
"""Build a runnable Flow directly from a definition; no subclass required."""
return cls.from_declaration(contents=definition, **kwargs)
@classmethod
def from_declaration(
cls,
*,
contents: FlowDefinition | str | dict[str, Any] | None = None,
path: Path | str | None = None,
**kwargs: Any,
) -> Flow[Any]:
"""Build a runnable declarative flow from contents or a file path."""
definition = FlowDefinition.from_declaration(
contents=contents,
path=path,
)
return cls.model_validate(
{**definition.config.model_dump(), **kwargs},
context={"flow_definition": definition},

View File

@@ -613,7 +613,7 @@ def test_flow_definition_merges_stacked_listen_router():
assert methods["second_router"].emit == ["second_approval", "not_approved"]
def test_flow_definition_round_trips_json_and_yaml():
def test_flow_definition_round_trips_declaration_serialization():
class RoundTripFlow(Flow):
@start()
def begin(self):
@@ -629,16 +629,122 @@ def test_flow_definition_round_trips_json_and_yaml():
definition = RoundTripFlow.flow_definition()
json_round_trip = flow_definition.FlowDefinition.from_json(definition.to_json())
yaml_round_trip = flow_definition.FlowDefinition.from_yaml(definition.to_yaml())
round_trips = [
flow_definition.FlowDefinition.from_declaration(contents=definition.to_json()),
flow_definition.FlowDefinition.from_declaration(contents=definition.to_yaml()),
]
assert json_round_trip.to_dict() == definition.to_dict()
assert yaml_round_trip.to_dict() == definition.to_dict()
assert yaml_round_trip.methods["decide"].router is True
assert yaml_round_trip.methods["decide"].listen == "begin"
for round_trip in round_trips:
assert round_trip.to_dict() == definition.to_dict()
assert round_trip.methods["decide"].router is True
assert round_trip.methods["decide"].listen == "begin"
def test_each_action_round_trips_json_and_yaml():
def test_flow_definition_from_declaration_accepts_contents():
data = {
"schema": "crewai.flow/v1",
"name": "DeclarationFlow",
"methods": {
"begin": {
"start": True,
"do": {
"call": "expression",
"expr": "'started'",
},
},
},
}
definition = flow_definition.FlowDefinition.from_dict(data)
contents = [
definition,
data,
definition.to_json(),
definition.to_yaml(),
]
expected = definition.to_dict()
for content in contents:
loaded = flow_definition.FlowDefinition.from_declaration(contents=content)
assert loaded.to_dict() == expected
def test_flow_definition_from_declaration_rejects_empty_file(tmp_path: Path):
declaration_path = tmp_path / "flow.crewai"
declaration_path.write_text(" \n", encoding="utf-8")
with pytest.raises(ValueError, match="Flow declaration file is empty"):
flow_definition.FlowDefinition.from_declaration(path=declaration_path)
@pytest.mark.parametrize("contents", ["[]", "false", "0", "null", "~"])
def test_flow_definition_from_declaration_rejects_falsey_non_mapping_contents(
contents: str,
):
with pytest.raises(ValueError, match="Flow declaration must contain a mapping"):
flow_definition.FlowDefinition.from_declaration(contents=contents)
def test_flow_definition_from_declaration_accepts_paths(tmp_path: Path):
definition = flow_definition.FlowDefinition.from_dict(
{
"schema": "crewai.flow/v1",
"name": "DeclarationFlow",
"methods": {
"begin": {
"start": True,
"do": {
"call": "expression",
"expr": "'started'",
},
},
},
}
)
declaration_path = tmp_path / "flow.crewai"
declaration_path.write_text(definition.to_yaml(), encoding="utf-8")
path_inputs = [
declaration_path,
str(declaration_path),
]
for path_input in path_inputs:
loaded = flow_definition.FlowDefinition.from_declaration(path=path_input)
assert loaded.to_dict() == definition.to_dict()
assert loaded.source_path == declaration_path.resolve()
def test_flow_definition_from_declaration_requires_input():
with pytest.raises(ValueError, match="Provide contents or path"):
flow_definition.FlowDefinition.from_declaration()
def test_flow_definition_from_declaration_prefers_contents_over_path(
tmp_path: Path,
):
data = {
"schema": "crewai.flow/v1",
"name": "ContentsFlow",
"methods": {
"begin": {
"start": True,
"do": {"call": "expression", "expr": "'started'"},
},
},
}
declaration_path = tmp_path / "missing.crewai"
loaded = flow_definition.FlowDefinition.from_declaration(
contents=data,
path=declaration_path,
)
assert loaded.name == "ContentsFlow"
assert loaded.source_path is None
def test_each_action_round_trips_declaration_serialization():
definition = flow_definition.FlowDefinition.from_dict(
{
"schema": "crewai.flow/v1",
@@ -677,15 +783,17 @@ def test_each_action_round_trips_json_and_yaml():
}
)
json_round_trip = flow_definition.FlowDefinition.from_json(definition.to_json())
yaml_round_trip = flow_definition.FlowDefinition.from_yaml(definition.to_yaml())
round_trips = [
flow_definition.FlowDefinition.from_declaration(contents=definition.to_json()),
flow_definition.FlowDefinition.from_declaration(contents=definition.to_yaml()),
]
assert json_round_trip.to_dict() == definition.to_dict()
assert yaml_round_trip.to_dict() == definition.to_dict()
assert yaml_round_trip.methods["process_rows"].description == (
"Process every loaded row."
)
assert yaml_round_trip.methods["process_rows"].do.call == "each"
for round_trip in round_trips:
assert round_trip.to_dict() == definition.to_dict()
assert round_trip.methods["process_rows"].description == (
"Process every loaded row."
)
assert round_trip.methods["process_rows"].do.call == "each"
def test_flow_definition_rejects_invalid_method_names():

View File

@@ -454,7 +454,7 @@ def assert_parity(flow_cls, yaml_str, inputs=None, ordered=True):
class_flow = flow_cls()
class_result, class_events = _run_with_events(class_flow, inputs)
definition = FlowDefinition.from_yaml(yaml_str)
definition = FlowDefinition.from_declaration(contents=yaml_str)
definition_flow = Flow.from_definition(definition)
definition_result, definition_events = _run_with_events(definition_flow, inputs)
@@ -477,6 +477,21 @@ def test_simple_chain_parity():
assert flow.method_outputs == ["hello", "HELLO", "confirmed:True"]
def test_flow_from_declaration_builds_runnable_flow():
flow = Flow.from_declaration(contents=CHAIN_YAML)
assert flow.kickoff() == "confirmed:True"
assert flow.method_outputs == ["hello", "HELLO", "confirmed:True"]
def test_flow_from_declaration_accepts_flow_definition():
definition = FlowDefinition.from_declaration(contents=CHAIN_YAML)
flow = Flow.from_declaration(contents=definition)
assert flow.kickoff() == "confirmed:True"
assert flow.method_outputs == ["hello", "HELLO", "confirmed:True"]
def test_and_or_merge_parity():
flow, _ = assert_parity(MergeFlow, MERGE_YAML, ordered=False)
assert flow.state["joined"] is True
@@ -499,7 +514,7 @@ def test_cyclic_flow_parity():
def test_definition_flow_events_use_definition_name():
definition = FlowDefinition.from_yaml(CHAIN_YAML)
definition = FlowDefinition.from_declaration(contents=CHAIN_YAML)
flow = Flow.from_definition(definition)
_, events = _run_with_events(flow)
assert events
@@ -588,7 +603,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff() == "found:ai agents"
@@ -639,7 +654,7 @@ methods:
listen: begin
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"topic": "ai"}) == "found:ai agents"
@@ -758,7 +773,7 @@ methods:
listen: begin
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff() == "search:hello agents"
@@ -783,7 +798,7 @@ methods:
listen: build_query
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff() == "found:ai agents news"
@@ -803,7 +818,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert (
flow.kickoff(inputs={"limit": 2, "domains": ["crewai.com", "example.com"]})
@@ -836,7 +851,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"question": "What is CrewAI?"}) == {
"agent": "Analyst",
@@ -874,7 +889,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"questions": ["one", "two"]}) == [
"Analyst:one",
@@ -905,7 +920,7 @@ def test_agent_action_round_trips_with_inline_definition():
}
)
round_trip = FlowDefinition.from_yaml(definition.to_yaml())
round_trip = FlowDefinition.from_declaration(contents=definition.to_yaml())
action = round_trip.to_dict()["methods"]["answer"]["do"]
assert action["call"] == "agent"
@@ -968,7 +983,7 @@ methods:
"""
with pytest.raises(ValidationError, match="invalid CEL expression"):
FlowDefinition.from_yaml(yaml_str)
FlowDefinition.from_declaration(contents=yaml_str)
def test_crew_action_runs_inline_yaml_definition(monkeypatch: pytest.MonkeyPatch):
@@ -1010,7 +1025,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"topic": "AI"}) == {
"crew": "inline_research",
@@ -1086,7 +1101,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"topic": "AI"}) == {
"crew": "referenced_research",
@@ -1160,9 +1175,7 @@ methods:
other_cwd.mkdir()
monkeypatch.chdir(other_cwd)
flow = Flow.from_definition(
FlowDefinition.from_yaml(yaml_str, source_path=flow_path)
)
flow = Flow.from_definition(FlowDefinition.from_declaration(path=flow_path))
assert flow.kickoff(inputs={"topic": "AI"}) == {
"crew": "relative_research",
@@ -1185,10 +1198,9 @@ methods:
from_declaration: ../outside/crew.jsonc
start: true
"""
flow_path.write_text(yaml_str, encoding="utf-8")
flow = Flow.from_definition(
FlowDefinition.from_yaml(yaml_str, source_path=flow_path)
)
flow = Flow.from_definition(FlowDefinition.from_declaration(path=flow_path))
with pytest.raises(
ValueError,
@@ -1411,7 +1423,7 @@ methods:
"""
with pytest.raises(ValidationError, match="invalid CEL expression"):
FlowDefinition.from_yaml(yaml_str)
FlowDefinition.from_declaration(contents=yaml_str)
def test_code_action_renders_keyword_inputs():
@@ -1429,7 +1441,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"name": "hello"}) == "hello!"
@@ -1448,7 +1460,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"value": "ok"}) == "callable:ok"
@@ -1472,7 +1484,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": ["a", "b"]}) == [
"normalized:a",
@@ -1499,7 +1511,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
caller_thread_id = threading.get_ident()
assert flow.kickoff(inputs={"rows": ["a"]}) == ["process_rows:a"]
@@ -1526,7 +1538,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": ["a", "b"]}) == ["async:a", "async:b"]
@@ -1548,7 +1560,7 @@ methods:
FlowScriptExecutionDisabledError,
match="CREWAI_ALLOW_FLOW_SCRIPT_EXECUTION=1",
) as exc_info:
Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert "methods with unresolvable actions" not in str(exc_info.value)
@@ -1572,7 +1584,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"raw_score": 3.2}) == "rounded:4"
assert flow.state["rounded"] == 4
@@ -1601,7 +1613,7 @@ methods:
listen: seed
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff() == "alpha:alpha"
assert flow.state["input_matches_output"] is True
@@ -1639,7 +1651,7 @@ methods:
listen: seed
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": [" a ", " b "]}) == ["global:a", "global:b"]
@@ -1671,7 +1683,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": ["a", "b"]}) == [
{"row": "a", "normalized": "saved:a"},
@@ -1700,7 +1712,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": ["a", "b"]}) == ["a", "b"]
assert flow._method_outputs == [
@@ -1738,7 +1750,7 @@ methods:
listen: seed
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": ["a", "b"]}) == [
"local:a",
@@ -1777,7 +1789,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(
inputs={
@@ -1811,7 +1823,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(inputs={"rows": [{"kind": "keep", "value": "a"}]}) == ["a"]
@@ -1838,7 +1850,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert flow.kickoff(
inputs={
@@ -1868,7 +1880,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
with pytest.raises(ValueError, match="if expression must evaluate to a boolean"):
flow.kickoff(inputs={"rows": [{"value": "truthy"}]})
@@ -1898,7 +1910,7 @@ methods:
listen: process_rows
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
events = []
with crewai_event_bus.scoped_handlers():
@@ -2069,7 +2081,7 @@ methods:
start: true
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
with pytest.raises(RuntimeError, match="bad row"):
flow.kickoff(inputs={"rows": ["ok", "bad"]})
@@ -2190,7 +2202,7 @@ methods:
listen: right
"""
definition = FlowDefinition.from_yaml(yaml_str)
definition = FlowDefinition.from_declaration(contents=yaml_str)
assert Flow.from_definition(definition).kickoff(
inputs={"direction": "left"}
@@ -2213,7 +2225,7 @@ methods:
"""
with pytest.raises(ValidationError, match="invalid CEL expression"):
FlowDefinition.from_yaml(yaml_str)
FlowDefinition.from_declaration(contents=yaml_str)
def test_expression_action_rejects_unknown_cel_root():
@@ -2229,7 +2241,7 @@ methods:
"""
with pytest.raises(ValidationError, match="unknown CEL root"):
FlowDefinition.from_yaml(yaml_str)
FlowDefinition.from_declaration(contents=yaml_str)
def test_tool_action_requires_module_qualname_ref():
@@ -2263,14 +2275,16 @@ def test_pydantic_state_from_ref_parity():
def test_pydantic_state_default_overlay():
flow = Flow.from_definition(FlowDefinition.from_yaml(PYDANTIC_STATE_OVERLAY_YAML))
flow = Flow.from_definition(
FlowDefinition.from_declaration(contents=PYDANTIC_STATE_OVERLAY_YAML)
)
result = flow.kickoff()
assert result == "count=6"
assert flow.state.count == 6
def test_json_schema_state():
flow = Flow.from_definition(FlowDefinition.from_yaml(JSON_SCHEMA_STATE_YAML))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=JSON_SCHEMA_STATE_YAML))
result = flow.kickoff()
assert result == "count=1"
assert flow.state.count == 1
@@ -2279,14 +2293,14 @@ def test_json_schema_state():
def test_json_schema_state_validates_inputs():
flow = Flow.from_definition(FlowDefinition.from_yaml(JSON_SCHEMA_STATE_YAML))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=JSON_SCHEMA_STATE_YAML))
with pytest.raises(ValueError, match="Invalid inputs"):
flow.kickoff(inputs={"count": "not-a-number"})
def test_pydantic_state_falls_back_to_json_schema_when_ref_unimportable():
flow = Flow.from_definition(
FlowDefinition.from_yaml(PYDANTIC_REF_WITH_SCHEMA_FALLBACK_YAML)
FlowDefinition.from_declaration(contents=PYDANTIC_REF_WITH_SCHEMA_FALLBACK_YAML)
)
result = flow.kickoff()
assert result == "count=1"
@@ -2295,7 +2309,9 @@ def test_pydantic_state_falls_back_to_json_schema_when_ref_unimportable():
def test_pydantic_state_without_ref_or_schema_falls_back_to_dict(caplog):
with caplog.at_level("ERROR"):
flow = Flow.from_definition(FlowDefinition.from_yaml(UNRESOLVABLE_STATE_YAML))
flow = Flow.from_definition(
FlowDefinition.from_declaration(contents=UNRESOLVABLE_STATE_YAML)
)
assert "falling back to dict state" in caplog.text
result = flow.kickoff()
@@ -2305,7 +2321,7 @@ def test_pydantic_state_without_ref_or_schema_falls_back_to_dict(caplog):
def test_dict_state_is_a_copy_of_default_plus_id():
definition = FlowDefinition.from_yaml(DICT_STATE_YAML)
definition = FlowDefinition.from_declaration(contents=DICT_STATE_YAML)
flow = Flow.from_definition(definition)
assert flow.state["count"] == 5
@@ -2322,7 +2338,7 @@ def test_dict_state_is_a_copy_of_default_plus_id():
def test_unknown_state_type_falls_back_to_dict(caplog):
with caplog.at_level("WARNING"):
flow = Flow.from_definition(FlowDefinition.from_yaml(UNKNOWN_STATE_YAML))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=UNKNOWN_STATE_YAML))
assert "falling back to dict state" in caplog.text
result = flow.kickoff()
@@ -2395,7 +2411,7 @@ def _run_capturing_flow_lifecycle(yaml_str, event_types):
def capture(source, event):
events.append(event)
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
result = flow.kickoff()
return flow, result, events
@@ -2409,7 +2425,7 @@ _LIFECYCLE_EVENTS = [
]
def test_config_suppress_flow_events_from_yaml():
def test_config_suppress_flow_events_from_declaration():
twin_events = []
with crewai_event_bus.scoped_handlers():
for event_type in _LIFECYCLE_EVENTS:
@@ -2432,14 +2448,14 @@ def test_config_suppress_flow_events_from_yaml():
)
def test_config_max_method_calls_from_yaml():
flow = Flow.from_definition(FlowDefinition.from_yaml(CAPPED_LOOP_YAML))
def test_config_max_method_calls_from_declaration():
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=CAPPED_LOOP_YAML))
with pytest.raises(RecursionError, match="has been called 2 times"):
flow.kickoff()
def test_config_stream_from_yaml():
flow = Flow.from_definition(FlowDefinition.from_yaml(STREAMING_CHAIN_YAML))
def test_config_stream_from_declaration():
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=STREAMING_CHAIN_YAML))
streaming = flow.kickoff()
assert isinstance(streaming, FlowStreamingOutput)
for _ in streaming:
@@ -2448,7 +2464,7 @@ def test_config_stream_from_yaml():
assert flow.stream is True
def test_config_defer_trace_finalization_from_yaml():
def test_config_defer_trace_finalization_from_declaration():
_, _, baseline_events = _run_capturing_flow_lifecycle(
CHAIN_YAML, [FlowFinishedEvent]
)
@@ -2462,7 +2478,7 @@ def test_config_defer_trace_finalization_from_yaml():
assert deferred_events == []
def test_config_checkpoint_from_yaml(tmp_path):
def test_config_checkpoint_from_declaration(tmp_path):
yaml_str = (
CHAIN_YAML
+ f"""
@@ -2471,19 +2487,23 @@ config:
location: {tmp_path}
"""
)
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
assert isinstance(flow.checkpoint, CheckpointConfig)
assert flow.checkpoint.location == str(tmp_path)
def test_config_input_provider_from_yaml():
flow = Flow.from_definition(FlowDefinition.from_yaml(INPUT_PROVIDER_CHAIN_YAML))
def test_config_input_provider_from_declaration():
flow = Flow.from_definition(
FlowDefinition.from_declaration(contents=INPUT_PROVIDER_CHAIN_YAML)
)
assert isinstance(flow.input_provider, StubInputProvider)
def test_round_trip_config_equivalence():
class_flow = ConfiguredFlow()
definition = FlowDefinition.from_yaml(ConfiguredFlow.flow_definition().to_yaml())
definition = FlowDefinition.from_declaration(
contents=ConfiguredFlow.flow_definition().to_yaml()
)
definition_flow = Flow.from_definition(definition)
assert definition.config.suppress_flow_events is True
@@ -2653,9 +2673,9 @@ class MethodPersistedFlow(Flow):
return "two"
def test_flow_level_persist_from_yaml_saves_once_per_method():
def test_flow_level_persist_from_declaration_saves_once_per_method():
yaml_str = _flow_level_persist_yaml("yaml-flow-level")
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
result = flow.kickoff()
assert result == "two"
@@ -2665,9 +2685,9 @@ def test_flow_level_persist_from_yaml_saves_once_per_method():
assert final_save["id"] == flow.state["id"]
def test_method_level_persist_from_yaml_saves_only_that_method():
def test_method_level_persist_from_declaration_saves_only_that_method():
yaml_str = _method_level_persist_yaml("yaml-method-level")
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
flow.kickoff()
assert _saved_methods("yaml-method-level") == ["first"]
@@ -2696,20 +2716,20 @@ methods:
persist:
enabled: false
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
flow.kickoff()
assert _saved_methods("yaml-opt-out") == ["first"]
def test_persist_restore_by_id_from_yaml():
def test_persist_restore_by_id_from_declaration():
yaml_str = _flow_level_persist_yaml("yaml-restore")
flow1 = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow1 = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
flow1.kickoff()
assert flow1.state["count"] == 2
flow2 = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow2 = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
flow2.kickoff(inputs={"id": flow1.state["id"]})
assert flow2.state["count"] == 4
@@ -2729,7 +2749,9 @@ def test_method_level_persist_decorator_saves_only_that_method():
def test_round_trip_persist_equivalence():
definition = FlowDefinition.from_yaml(ClassPersistedFlow.flow_definition().to_yaml())
definition = FlowDefinition.from_declaration(
contents=ClassPersistedFlow.flow_definition().to_yaml()
)
before = len(DefinitionStoreBackend.saves["class-decorator"])
flow = Flow.from_definition(definition)
@@ -2738,7 +2760,7 @@ def test_round_trip_persist_equivalence():
assert _saved_methods("class-decorator")[before:] == ["first", "second"]
def test_method_persist_backend_overrides_flow_level_backend_from_yaml():
def test_method_persist_backend_overrides_flow_level_backend_from_declaration():
yaml_str = f"""
schema: crewai.flow/v1
name: PersistedFlow
@@ -2762,7 +2784,7 @@ methods:
persistence_type: DefinitionStoreBackend
store: yaml-mixed-method
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
flow.kickoff()
assert _saved_methods("yaml-mixed-flow") == ["first"]
@@ -2910,8 +2932,8 @@ methods:
"""
def test_human_feedback_from_yaml_default_outcome_routes():
flow = Flow.from_definition(FlowDefinition.from_yaml(REVIEW_YAML))
def test_human_feedback_from_declaration_default_outcome_routes():
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=REVIEW_YAML))
with patch.object(flow, "_request_human_feedback", return_value="") as request:
result = flow.kickoff()
@@ -2922,8 +2944,8 @@ def test_human_feedback_from_yaml_default_outcome_routes():
assert flow.last_human_feedback.output == "draft-content"
def test_human_feedback_from_yaml_collapses_and_routes():
flow = Flow.from_definition(FlowDefinition.from_yaml(REVIEW_YAML))
def test_human_feedback_from_declaration_collapses_and_routes():
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=REVIEW_YAML))
with (
patch.object(flow, "_request_human_feedback", return_value="ship it"),
@@ -2940,7 +2962,7 @@ def test_round_trip_human_feedback_equivalence():
with patch.object(class_flow, "_request_human_feedback", return_value=""):
class_result = class_flow.kickoff()
definition = FlowDefinition.from_yaml(ReviewFlow.flow_definition().to_yaml())
definition = FlowDefinition.from_declaration(contents=ReviewFlow.flow_definition().to_yaml())
twin = Flow.from_definition(definition)
with patch.object(twin, "_request_human_feedback", return_value=""):
twin_result = twin.kickoff()
@@ -2953,8 +2975,8 @@ def test_round_trip_human_feedback_equivalence():
)
def test_human_feedback_pending_and_resume_from_yaml():
definition = FlowDefinition.from_yaml(PENDING_REVIEW_YAML)
def test_human_feedback_pending_and_resume_from_declaration():
definition = FlowDefinition.from_declaration(contents=PENDING_REVIEW_YAML)
flow = Flow.from_definition(definition)
pending = flow.kickoff()
@@ -2975,7 +2997,7 @@ def test_human_feedback_pending_and_resume_from_yaml():
assert flow_id not in DefinitionStoreBackend.pending
def test_flow_config_provider_fallback_from_yaml():
def test_flow_config_provider_fallback_from_declaration():
yaml_str = f"""
schema: crewai.flow/v1
name: ConfigProviderFlow
@@ -3001,7 +3023,7 @@ methods:
return "from-config"
provider = RecordingProvider()
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
previous = flow_config.hitl_provider
flow_config.hitl_provider = provider
@@ -3104,7 +3126,7 @@ methods:
message: "Review:"
provider: {__name__}:_NeedsArgsProvider
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
with pytest.raises(
ValueError, match="cannot instantiate human_feedback.provider ref"
@@ -3125,7 +3147,7 @@ methods:
message: "Review:"
provider: missing_module_xyz:Provider
"""
flow = Flow.from_definition(FlowDefinition.from_yaml(yaml_str))
flow = Flow.from_definition(FlowDefinition.from_declaration(contents=yaml_str))
with pytest.raises(
ValueError, match="unresolvable human_feedback.provider ref"
@@ -3137,7 +3159,7 @@ def _checkpoint_chain_flow(tmp_path):
from crewai.state.provider.json_provider import JsonProvider
from crewai.state.runtime import RuntimeState
definition = FlowDefinition.from_yaml(CHAIN_YAML)
definition = FlowDefinition.from_declaration(contents=CHAIN_YAML)
flow = Flow.from_definition(definition)
result = flow.kickoff()
assert result == "confirmed:True"
@@ -3177,7 +3199,7 @@ state:
methods: {}
"""
with pytest.raises(ValidationError, match="default"):
FlowDefinition.from_yaml(yaml_str)
FlowDefinition.from_declaration(contents=yaml_str)
def test_definition_method_missing_from_class_fails_loudly():

View File

@@ -1,3 +1,3 @@
"""CrewAI development tools."""
__version__ = "1.14.8a2"
__version__ = "1.14.8a3"

63
uv.lock generated
View File

@@ -13,7 +13,7 @@ resolution-markers = [
]
[options]
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer = "2026-06-20T16:46:21.117658Z"
exclude-newer-span = "P3D"
[options.exclude-newer-package]
@@ -1452,9 +1452,9 @@ requires-dist = [
{ name = "openai", specifier = ">=2.30.0,<3" },
{ name = "openpyxl", specifier = "~=3.1.5" },
{ name = "openpyxl", marker = "extra == 'openpyxl'", specifier = "~=3.1.5" },
{ name = "opentelemetry-api", specifier = "~=1.34.0" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "~=1.34.0" },
{ name = "opentelemetry-sdk", specifier = "~=1.34.0" },
{ name = "opentelemetry-api", specifier = "~=1.42.0" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "~=1.42.0" },
{ name = "opentelemetry-sdk", specifier = "~=1.42.0" },
{ name = "pandas", marker = "extra == 'pandas'", specifier = "~=2.2.3" },
{ name = "pdfplumber", specifier = "~=0.11.4" },
{ name = "portalocker", specifier = "~=2.7.0" },
@@ -1539,9 +1539,9 @@ requires-dist = [
{ name = "appdirs", specifier = "~=1.4.4" },
{ name = "cryptography", specifier = ">=42.0" },
{ name = "httpx", specifier = "~=0.28.1" },
{ name = "opentelemetry-api", specifier = "~=1.34.0" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "~=1.34.0" },
{ name = "opentelemetry-sdk", specifier = "~=1.34.0" },
{ name = "opentelemetry-api", specifier = "~=1.42.0" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "~=1.42.0" },
{ name = "opentelemetry-sdk", specifier = "~=1.42.0" },
{ name = "packaging", specifier = ">=23.0" },
{ name = "portalocker", specifier = "~=2.7.0" },
{ name = "pydantic", specifier = ">=2.11.9,<2.13" },
@@ -5585,45 +5585,44 @@ wheels = [
[[package]]
name = "opentelemetry-api"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/44/ba/786b4de7e39d88043622d901b92c4485835f43e0be76c2824d2687911bc2/opentelemetry_exporter_otlp-1.34.1.tar.gz", hash = "sha256:71c9ad342d665d9e4235898d205db17c5764cd7a69acb8a5dcd6d5e04c4c9988", size = 6173, upload-time = "2025-06-10T08:55:21.595Z" }
sdist = { url = "https://files.pythonhosted.org/packages/08/94/8637919a5d01f81dacf510234bc0110b944f4687a6e96b0a02adf2f6bdce/opentelemetry_exporter_otlp-1.42.1.tar.gz", hash = "sha256:2d9ebaed714377a67d224d46795ddcc11d2c877fa5de35fda70b6f3b010729a9", size = 6086, upload-time = "2026-05-21T16:32:51.963Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c1/259b8d8391c968e8f005d8a0ccefcb41aeef64cf55905cd0c0db4e22aaee/opentelemetry_exporter_otlp-1.34.1-py3-none-any.whl", hash = "sha256:f4a453e9cde7f6362fd4a090d8acf7881d1dc585540c7b65cbd63e36644238d4", size = 7040, upload-time = "2025-06-10T08:54:59.655Z" },
{ url = "https://files.pythonhosted.org/packages/6c/4d/c26080295a36fd22e201fefd7cb9c22cd203189b1af8cd73b158382b7ad8/opentelemetry_exporter_otlp-1.42.1-py3-none-any.whl", hash = "sha256:aedd54545bb0587cd45210abdc8be545af9c01413f3307786e276df1e3c83bee", size = 6733, upload-time = "2026-05-21T16:32:31.261Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-proto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/f0/ff235936ee40db93360233b62da932d4fd9e8d103cd090c6bcb9afaf5f01/opentelemetry_exporter_otlp_proto_common-1.34.1.tar.gz", hash = "sha256:b59a20a927facd5eac06edaf87a07e49f9e4a13db487b7d8a52b37cb87710f8b", size = 20817, upload-time = "2025-06-10T08:55:22.55Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0e/9c/216acfeaedadf2e1937f4373929b20f73197c5c4a2546d4f584b7fa63813/opentelemetry_exporter_otlp_proto_common-1.42.1.tar.gz", hash = "sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee", size = 21433, upload-time = "2026-05-21T16:32:55.526Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/e8/8b292a11cc8d8d87ec0c4089ae21b6a58af49ca2e51fa916435bc922fdc7/opentelemetry_exporter_otlp_proto_common-1.34.1-py3-none-any.whl", hash = "sha256:8e2019284bf24d3deebbb6c59c71e6eef3307cd88eff8c633e061abba33f7e87", size = 18834, upload-time = "2025-06-10T08:55:00.806Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/2375e7612e1121a4518c17603b6e0b03ad94f565aafad53f464dc5be2bf6/opentelemetry_exporter_otlp_proto_common-1.42.1-py3-none-any.whl", hash = "sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140", size = 17327, upload-time = "2026-05-21T16:32:33.387Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-grpc"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
@@ -5634,14 +5633,14 @@ dependencies = [
{ name = "opentelemetry-sdk" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/f7/bb63837a3edb9ca857aaf5760796874e7cecddc88a2571b0992865a48fb6/opentelemetry_exporter_otlp_proto_grpc-1.34.1.tar.gz", hash = "sha256:7c841b90caa3aafcfc4fee58487a6c71743c34c6dc1787089d8b0578bbd794dd", size = 22566, upload-time = "2025-06-10T08:55:23.214Z" }
sdist = { url = "https://files.pythonhosted.org/packages/87/87/ca7fc790dfdbcf4f9e9aab14a39ef1b7508ead13707e283de0b3131478d2/opentelemetry_exporter_otlp_proto_grpc-1.42.1.tar.gz", hash = "sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15", size = 27140, upload-time = "2026-05-21T16:32:56.162Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/42/0a4dd47e7ef54edf670c81fc06a83d68ea42727b82126a1df9dd0477695d/opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl", hash = "sha256:04bb8b732b02295be79f8a86a4ad28fae3d4ddb07307a98c7aa6f331de18cca6", size = 18615, upload-time = "2025-06-10T08:55:02.214Z" },
{ url = "https://files.pythonhosted.org/packages/89/2b/28ba5b128f47fe8c3bab541000d6feb4b5a9bd26623ca013406f01c0fb60/opentelemetry_exporter_otlp_proto_grpc-1.42.1-py3-none-any.whl", hash = "sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc", size = 19617, upload-time = "2026-05-21T16:32:34.278Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-http"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
@@ -5652,48 +5651,48 @@ dependencies = [
{ name = "requests" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/19/8f/954bc725961cbe425a749d55c0ba1df46832a5999eae764d1a7349ac1c29/opentelemetry_exporter_otlp_proto_http-1.34.1.tar.gz", hash = "sha256:aaac36fdce46a8191e604dcf632e1f9380c7d5b356b27b3e0edb5610d9be28ad", size = 15351, upload-time = "2025-06-10T08:55:24.657Z" }
sdist = { url = "https://files.pythonhosted.org/packages/77/32/826bfa1d80ecea24f47808de03cd4a0d13c17ecc07712f45123f0f61e4ac/opentelemetry_exporter_otlp_proto_http-1.42.1.tar.gz", hash = "sha256:bf142a21035d7571ac3a09cb2e5639f49886f243972883cfe777ed3bf02b734d", size = 25406, upload-time = "2026-05-21T16:32:56.807Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/54/b05251c04e30c1ac70cf4a7c5653c085dfcf2c8b98af71661d6a252adc39/opentelemetry_exporter_otlp_proto_http-1.34.1-py3-none-any.whl", hash = "sha256:5251f00ca85872ce50d871f6d3cc89fe203b94c3c14c964bbdc3883366c705d8", size = 17744, upload-time = "2025-06-10T08:55:03.802Z" },
{ url = "https://files.pythonhosted.org/packages/d3/96/82cb223a1502f0787d4bbff12907f5f8d870a50731febcd5818d93ef9555/opentelemetry_exporter_otlp_proto_http-1.42.1-py3-none-any.whl", hash = "sha256:00a16da1b312a1d6c7233d600d557c91df71125af73020f3b9a7765bd699d59d", size = 21793, upload-time = "2026-05-21T16:32:35.277Z" },
]
[[package]]
name = "opentelemetry-proto"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/b3/c3158dd012463bb7c0eb7304a85a6f63baeeb5b4c93a53845cf89f848c7e/opentelemetry_proto-1.34.1.tar.gz", hash = "sha256:16286214e405c211fc774187f3e4bbb1351290b8dfb88e8948af209ce85b719e", size = 34344, upload-time = "2025-06-10T08:55:32.25Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/55/63eac3e1089b768ba014091fdd2ae8a9a440c821ef5e2b786909c94c8836/opentelemetry_proto-1.42.1.tar.gz", hash = "sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6", size = 45839, upload-time = "2026-05-21T16:33:03.937Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/28/ab/4591bfa54e946350ce8b3f28e5c658fe9785e7cd11e9c11b1671a867822b/opentelemetry_proto-1.34.1-py3-none-any.whl", hash = "sha256:eb4bb5ac27f2562df2d6857fc557b3a481b5e298bc04f94cc68041f00cebcbd2", size = 55692, upload-time = "2025-06-10T08:55:14.904Z" },
{ url = "https://files.pythonhosted.org/packages/41/9d/171c02c84a76940b7e601805b3bb536985aded9168fbcc9ba52f0a730fa2/opentelemetry_proto-1.42.1-py3-none-any.whl", hash = "sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c", size = 71782, upload-time = "2026-05-21T16:32:44.867Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.34.1"
version = "1.42.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6f/41/fe20f9036433da8e0fcef568984da4c1d1c771fa072ecd1a4d98779dccdd/opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d", size = 159441, upload-time = "2025-06-10T08:55:33.028Z" }
sdist = { url = "https://files.pythonhosted.org/packages/40/f7/b390bd9bfd703bf98a68fea1f27786c6872331fd617164a54b8a59bdc008/opentelemetry_sdk-1.42.1.tar.gz", hash = "sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7", size = 239262, upload-time = "2026-05-21T16:33:04.641Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/1b/def4fe6aa73f483cabf4c748f4c25070d5f7604dcc8b52e962983491b29e/opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e", size = 118477, upload-time = "2025-06-10T08:55:16.02Z" },
{ url = "https://files.pythonhosted.org/packages/8f/6b/4287766cfbde577ae2272e8884abac325aeaac0d64f41c61d5b8cc595105/opentelemetry_sdk-1.42.1-py3-none-any.whl", hash = "sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d", size = 170907, upload-time = "2026-05-21T16:32:45.894Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.55b1"
version = "0.63b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/f0/f33458486da911f47c4aa6db9bda308bb80f3236c111bf848bd870c16b16/opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3", size = 119829, upload-time = "2025-06-10T08:55:33.881Z" }
sdist = { url = "https://files.pythonhosted.org/packages/93/99/4d7dd6df64795951413ce6e815f8cf1eb191daf7196ae86574589643d5f3/opentelemetry_semantic_conventions-0.63b1.tar.gz", hash = "sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9", size = 148340, upload-time = "2026-05-21T16:33:05.455Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/89/267b0af1b1d0ba828f0e60642b6a5116ac1fd917cde7fc02821627029bd1/opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed", size = 196223, upload-time = "2025-06-10T08:55:17.638Z" },
{ url = "https://files.pythonhosted.org/packages/cb/7a/7fe66f5f3682b1dd47d88cc4e11f1c6c0966b737de2d16671146e23c39a5/opentelemetry_semantic_conventions-0.63b1-py3-none-any.whl", hash = "sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682", size = 203713, upload-time = "2026-05-21T16:32:47.016Z" },
]
[[package]]