mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-15 11:58:31 +00:00
fix: make plot node selection smoother
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
This commit is contained in:
@@ -19,6 +19,7 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: true
|
pass_filenames: true
|
||||||
types: [python]
|
types: [python]
|
||||||
|
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/)
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
rev: 0.9.3
|
rev: 0.9.3
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
|
from crewai.flow.flow import Flow, and_, listen, or_, router, start
|
||||||
|
from crewai.flow.persistence import persist
|
||||||
from crewai.flow.visualization import (
|
from crewai.flow.visualization import (
|
||||||
FlowStructure,
|
FlowStructure,
|
||||||
build_flow_structure,
|
build_flow_structure,
|
||||||
print_structure_summary,
|
|
||||||
structure_to_dict,
|
|
||||||
visualize_flow_structure,
|
visualize_flow_structure,
|
||||||
)
|
)
|
||||||
from crewai.flow.flow import Flow, and_, listen, or_, router, start
|
|
||||||
from crewai.flow.persistence import persist
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -17,9 +15,7 @@ __all__ = [
|
|||||||
"listen",
|
"listen",
|
||||||
"or_",
|
"or_",
|
||||||
"persist",
|
"persist",
|
||||||
"print_structure_summary",
|
|
||||||
"router",
|
"router",
|
||||||
"start",
|
"start",
|
||||||
"structure_to_dict",
|
|
||||||
"visualize_flow_structure",
|
"visualize_flow_structure",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
from crewai.flow.visualization.builder import (
|
from crewai.flow.visualization.builder import (
|
||||||
build_flow_structure,
|
build_flow_structure,
|
||||||
calculate_execution_paths,
|
calculate_execution_paths,
|
||||||
print_structure_summary,
|
|
||||||
structure_to_dict,
|
|
||||||
)
|
)
|
||||||
from crewai.flow.visualization.renderers import render_interactive
|
from crewai.flow.visualization.renderers import render_interactive
|
||||||
from crewai.flow.visualization.types import FlowStructure, NodeMetadata, StructureEdge
|
from crewai.flow.visualization.types import FlowStructure, NodeMetadata, StructureEdge
|
||||||
@@ -18,8 +16,6 @@ __all__ = [
|
|||||||
"StructureEdge",
|
"StructureEdge",
|
||||||
"build_flow_structure",
|
"build_flow_structure",
|
||||||
"calculate_execution_paths",
|
"calculate_execution_paths",
|
||||||
"print_structure_summary",
|
|
||||||
"render_interactive",
|
"render_interactive",
|
||||||
"structure_to_dict",
|
|
||||||
"visualize_flow_structure",
|
"visualize_flow_structure",
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -66,19 +66,19 @@
|
|||||||
<div class="legend-title">Edge Types</div>
|
<div class="legend-title">Edge Types</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<svg width="24" height="12" style="margin-right: 12px;">
|
<svg width="24" height="12" style="margin-right: 12px;">
|
||||||
<line x1="0" y1="6" x2="24" y2="6" stroke="'{{ CREWAI_ORANGE }}'" stroke-width="2"/>
|
<line x1="0" y1="6" x2="24" y2="6" stroke="'{{ CREWAI_ORANGE }}'" stroke-width="2" stroke-dasharray="5,5"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Router Paths</span>
|
<span>Router Paths</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<svg width="24" height="12" style="margin-right: 12px;">
|
<svg width="24" height="12" style="margin-right: 12px;" class="legend-or-line">
|
||||||
<line x1="0" y1="6" x2="24" y2="6" stroke="'{{ GRAY }}'" stroke-width="2"/>
|
<line x1="0" y1="6" x2="24" y2="6" stroke="var(--edge-or-color)" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>OR Conditions</span>
|
<span>OR Conditions</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<svg width="24" height="12" style="margin-right: 12px;">
|
<svg width="24" height="12" style="margin-right: 12px;">
|
||||||
<line x1="0" y1="6" x2="24" y2="6" stroke="'{{ CREWAI_ORANGE }}'" stroke-width="2" stroke-dasharray="5,5"/>
|
<line x1="0" y1="6" x2="24" y2="6" stroke="'{{ CREWAI_ORANGE }}'" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>AND Conditions</span>
|
<span>AND Conditions</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
--shadow-strong: rgba(0, 0, 0, 0.15);
|
--shadow-strong: rgba(0, 0, 0, 0.15);
|
||||||
--edge-label-text: '{{ GRAY }}';
|
--edge-label-text: '{{ GRAY }}';
|
||||||
--edge-label-bg: rgba(255, 255, 255, 0.8);
|
--edge-label-bg: rgba(255, 255, 255, 0.8);
|
||||||
|
--edge-or-color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
--shadow-strong: rgba(0, 0, 0, 0.5);
|
--shadow-strong: rgba(0, 0, 0, 0.5);
|
||||||
--edge-label-text: #c9d1d9;
|
--edge-label-text: #c9d1d9;
|
||||||
--edge-label-bg: rgba(22, 27, 34, 0.9);
|
--edge-label-bg: rgba(22, 27, 34, 0.9);
|
||||||
|
--edge-or-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes dash {
|
@keyframes dash {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
"""Flow structure builder for analyzing Flow execution."""
|
"""Flow structure builder for analyzing Flow execution."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import inspect
|
import inspect
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from crewai.flow.constants import OR_CONDITION
|
from crewai.flow.constants import AND_CONDITION, OR_CONDITION
|
||||||
|
from crewai.flow.flow_wrappers import FlowCondition
|
||||||
from crewai.flow.types import FlowMethodName
|
from crewai.flow.types import FlowMethodName
|
||||||
from crewai.flow.utils import (
|
from crewai.flow.utils import (
|
||||||
_extract_all_methods_recursive,
|
|
||||||
is_flow_condition_dict,
|
is_flow_condition_dict,
|
||||||
is_simple_flow_condition,
|
is_simple_flow_condition,
|
||||||
)
|
)
|
||||||
@@ -21,7 +22,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
def _extract_direct_or_triggers(
|
def _extract_direct_or_triggers(
|
||||||
condition: str | dict[str, Any] | list[Any],
|
condition: str | dict[str, Any] | list[Any] | FlowCondition,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Extract direct OR-level trigger strings from a condition.
|
"""Extract direct OR-level trigger strings from a condition.
|
||||||
|
|
||||||
@@ -43,16 +44,15 @@ def _extract_direct_or_triggers(
|
|||||||
if isinstance(condition, str):
|
if isinstance(condition, str):
|
||||||
return [condition]
|
return [condition]
|
||||||
if isinstance(condition, dict):
|
if isinstance(condition, dict):
|
||||||
cond_type = condition.get("type", "OR")
|
cond_type = condition.get("type", OR_CONDITION)
|
||||||
conditions_list = condition.get("conditions", [])
|
conditions_list = condition.get("conditions", [])
|
||||||
|
|
||||||
if cond_type == "OR":
|
if cond_type == OR_CONDITION:
|
||||||
strings = []
|
strings = []
|
||||||
for sub_cond in conditions_list:
|
for sub_cond in conditions_list:
|
||||||
strings.extend(_extract_direct_or_triggers(sub_cond))
|
strings.extend(_extract_direct_or_triggers(sub_cond))
|
||||||
return strings
|
return strings
|
||||||
else:
|
return []
|
||||||
return []
|
|
||||||
if isinstance(condition, list):
|
if isinstance(condition, list):
|
||||||
strings = []
|
strings = []
|
||||||
for item in condition:
|
for item in condition:
|
||||||
@@ -64,7 +64,7 @@ def _extract_direct_or_triggers(
|
|||||||
|
|
||||||
|
|
||||||
def _extract_all_trigger_names(
|
def _extract_all_trigger_names(
|
||||||
condition: str | dict[str, Any] | list[Any],
|
condition: str | dict[str, Any] | list[Any] | FlowCondition,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Extract ALL trigger names from a condition for display purposes.
|
"""Extract ALL trigger names from a condition for display purposes.
|
||||||
|
|
||||||
@@ -101,6 +101,76 @@ def _extract_all_trigger_names(
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _create_edges_from_condition(
|
||||||
|
condition: str | dict[str, Any] | list[Any] | FlowCondition,
|
||||||
|
target: str,
|
||||||
|
nodes: dict[str, NodeMetadata],
|
||||||
|
) -> list[StructureEdge]:
|
||||||
|
"""Create edges from a condition tree, preserving AND/OR semantics.
|
||||||
|
|
||||||
|
This function recursively processes the condition tree and creates edges
|
||||||
|
with the appropriate condition_type for each trigger.
|
||||||
|
|
||||||
|
For AND conditions, all triggers get edges with condition_type="AND".
|
||||||
|
For OR conditions, triggers get edges with condition_type="OR".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
condition: The condition tree (string, dict, or list).
|
||||||
|
target: The target node name.
|
||||||
|
nodes: Dictionary of all nodes for validation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of StructureEdge objects representing the condition.
|
||||||
|
"""
|
||||||
|
edges: list[StructureEdge] = []
|
||||||
|
|
||||||
|
if isinstance(condition, str):
|
||||||
|
if condition in nodes:
|
||||||
|
edges.append(
|
||||||
|
StructureEdge(
|
||||||
|
source=condition,
|
||||||
|
target=target,
|
||||||
|
condition_type=OR_CONDITION,
|
||||||
|
is_router_path=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif callable(condition) and hasattr(condition, "__name__"):
|
||||||
|
method_name = condition.__name__
|
||||||
|
if method_name in nodes:
|
||||||
|
edges.append(
|
||||||
|
StructureEdge(
|
||||||
|
source=method_name,
|
||||||
|
target=target,
|
||||||
|
condition_type=OR_CONDITION,
|
||||||
|
is_router_path=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(condition, dict):
|
||||||
|
cond_type = condition.get("type", OR_CONDITION)
|
||||||
|
conditions_list = condition.get("conditions", [])
|
||||||
|
|
||||||
|
if cond_type == AND_CONDITION:
|
||||||
|
triggers = _extract_all_trigger_names(condition)
|
||||||
|
edges.extend(
|
||||||
|
StructureEdge(
|
||||||
|
source=trigger,
|
||||||
|
target=target,
|
||||||
|
condition_type=AND_CONDITION,
|
||||||
|
is_router_path=False,
|
||||||
|
)
|
||||||
|
for trigger in triggers
|
||||||
|
if trigger in nodes
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for sub_cond in conditions_list:
|
||||||
|
edges.extend(_create_edges_from_condition(sub_cond, target, nodes))
|
||||||
|
elif isinstance(condition, list):
|
||||||
|
for item in condition:
|
||||||
|
edges.extend(_create_edges_from_condition(item, target, nodes))
|
||||||
|
|
||||||
|
return edges
|
||||||
|
|
||||||
|
|
||||||
def build_flow_structure(flow: Flow[Any]) -> FlowStructure:
|
def build_flow_structure(flow: Flow[Any]) -> FlowStructure:
|
||||||
"""Build a structure representation of a Flow's execution.
|
"""Build a structure representation of a Flow's execution.
|
||||||
|
|
||||||
@@ -228,28 +298,22 @@ def build_flow_structure(flow: Flow[Any]) -> FlowStructure:
|
|||||||
nodes[method_name] = node_metadata
|
nodes[method_name] = node_metadata
|
||||||
|
|
||||||
for listener_name, condition_data in flow._listeners.items():
|
for listener_name, condition_data in flow._listeners.items():
|
||||||
condition_type: str | None = None
|
|
||||||
trigger_methods_list: list[str] = []
|
|
||||||
|
|
||||||
if is_simple_flow_condition(condition_data):
|
if is_simple_flow_condition(condition_data):
|
||||||
cond_type, methods = condition_data
|
cond_type, methods = condition_data
|
||||||
condition_type = cond_type
|
edges.extend(
|
||||||
trigger_methods_list = [str(m) for m in methods]
|
StructureEdge(
|
||||||
elif is_flow_condition_dict(condition_data):
|
source=str(trigger_method),
|
||||||
condition_type = condition_data.get("type", OR_CONDITION)
|
target=str(listener_name),
|
||||||
methods_recursive = _extract_all_methods_recursive(condition_data, flow)
|
condition_type=cond_type,
|
||||||
trigger_methods_list = [str(m) for m in methods_recursive]
|
is_router_path=False,
|
||||||
|
)
|
||||||
edges.extend(
|
for trigger_method in methods
|
||||||
StructureEdge(
|
if str(trigger_method) in nodes
|
||||||
source=str(trigger_method),
|
)
|
||||||
target=str(listener_name),
|
elif is_flow_condition_dict(condition_data):
|
||||||
condition_type=condition_type,
|
edges.extend(
|
||||||
is_router_path=False,
|
_create_edges_from_condition(condition_data, str(listener_name), nodes)
|
||||||
)
|
)
|
||||||
for trigger_method in trigger_methods_list
|
|
||||||
if trigger_method in nodes
|
|
||||||
)
|
|
||||||
|
|
||||||
for router_method_name in router_methods:
|
for router_method_name in router_methods:
|
||||||
if router_method_name not in flow._router_paths:
|
if router_method_name not in flow._router_paths:
|
||||||
@@ -299,76 +363,6 @@ def build_flow_structure(flow: Flow[Any]) -> FlowStructure:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def structure_to_dict(structure: FlowStructure) -> dict[str, Any]:
|
|
||||||
"""Convert FlowStructure to plain dictionary for serialization.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
structure: FlowStructure to convert.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Plain dictionary representation.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"nodes": dict(structure["nodes"]),
|
|
||||||
"edges": list(structure["edges"]),
|
|
||||||
"start_methods": list(structure["start_methods"]),
|
|
||||||
"router_methods": list(structure["router_methods"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def print_structure_summary(structure: FlowStructure) -> str:
|
|
||||||
"""Generate human-readable summary of Flow structure.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
structure: FlowStructure to summarize.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Formatted string summary.
|
|
||||||
"""
|
|
||||||
lines: list[str] = []
|
|
||||||
lines.append("Flow Execution Structure")
|
|
||||||
lines.append("=" * 50)
|
|
||||||
lines.append(f"Total nodes: {len(structure['nodes'])}")
|
|
||||||
lines.append(f"Total edges: {len(structure['edges'])}")
|
|
||||||
lines.append(f"Start methods: {len(structure['start_methods'])}")
|
|
||||||
lines.append(f"Router methods: {len(structure['router_methods'])}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
if structure["start_methods"]:
|
|
||||||
lines.append("Start Methods:")
|
|
||||||
for method_name in structure["start_methods"]:
|
|
||||||
node = structure["nodes"][method_name]
|
|
||||||
lines.append(f" - {method_name}")
|
|
||||||
if node.get("condition_type"):
|
|
||||||
lines.append(f" Condition: {node['condition_type']}")
|
|
||||||
if node.get("trigger_methods"):
|
|
||||||
lines.append(f" Triggers on: {', '.join(node['trigger_methods'])}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
if structure["router_methods"]:
|
|
||||||
lines.append("Router Methods:")
|
|
||||||
for method_name in structure["router_methods"]:
|
|
||||||
node = structure["nodes"][method_name]
|
|
||||||
lines.append(f" - {method_name}")
|
|
||||||
if node.get("router_paths"):
|
|
||||||
lines.append(f" Paths: {', '.join(node['router_paths'])}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
if structure["edges"]:
|
|
||||||
lines.append("Connections:")
|
|
||||||
for edge in structure["edges"]:
|
|
||||||
edge_type = ""
|
|
||||||
if edge["is_router_path"]:
|
|
||||||
edge_type = " [Router Path]"
|
|
||||||
elif edge["condition_type"]:
|
|
||||||
edge_type = f" [{edge['condition_type']}]"
|
|
||||||
|
|
||||||
lines.append(f" {edge['source']} -> {edge['target']}{edge_type}")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_execution_paths(structure: FlowStructure) -> int:
|
def calculate_execution_paths(structure: FlowStructure) -> int:
|
||||||
"""Calculate number of possible execution paths through the flow.
|
"""Calculate number of possible execution paths through the flow.
|
||||||
|
|
||||||
@@ -396,6 +390,15 @@ def calculate_execution_paths(structure: FlowStructure) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def count_paths_from(node: str, visited: set[str]) -> int:
|
def count_paths_from(node: str, visited: set[str]) -> int:
|
||||||
|
"""Recursively count execution paths from a given node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: Node name to start counting from.
|
||||||
|
visited: Set of already visited nodes to prevent cycles.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of execution paths from this node to terminal nodes.
|
||||||
|
"""
|
||||||
if node in terminal_nodes:
|
if node in terminal_nodes:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import pytest
|
|||||||
from crewai.flow.flow import Flow, and_, listen, or_, router, start
|
from crewai.flow.flow import Flow, and_, listen, or_, router, start
|
||||||
from crewai.flow.visualization import (
|
from crewai.flow.visualization import (
|
||||||
build_flow_structure,
|
build_flow_structure,
|
||||||
print_structure_summary,
|
|
||||||
structure_to_dict,
|
|
||||||
visualize_flow_structure,
|
visualize_flow_structure,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,65 +142,6 @@ def test_build_flow_structure_with_and_or_conditions():
|
|||||||
assert len(or_edges) == 2
|
assert len(or_edges) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_structure_to_dict():
|
|
||||||
"""Test converting flow structure to dictionary format."""
|
|
||||||
flow = SimpleFlow()
|
|
||||||
structure = build_flow_structure(flow)
|
|
||||||
dag_dict = structure_to_dict(structure)
|
|
||||||
|
|
||||||
assert "nodes" in dag_dict
|
|
||||||
assert "edges" in dag_dict
|
|
||||||
assert "start_methods" in dag_dict
|
|
||||||
assert "router_methods" in dag_dict
|
|
||||||
|
|
||||||
assert "begin" in dag_dict["nodes"]
|
|
||||||
assert "process" in dag_dict["nodes"]
|
|
||||||
|
|
||||||
begin_node = dag_dict["nodes"]["begin"]
|
|
||||||
assert begin_node["type"] == "start"
|
|
||||||
assert "method_signature" in begin_node
|
|
||||||
assert "source_code" in begin_node
|
|
||||||
|
|
||||||
assert len(dag_dict["edges"]) == 1
|
|
||||||
edge = dag_dict["edges"][0]
|
|
||||||
assert "source" in edge
|
|
||||||
assert "target" in edge
|
|
||||||
assert "condition_type" in edge
|
|
||||||
assert "is_router_path" in edge
|
|
||||||
|
|
||||||
|
|
||||||
def test_structure_to_dict_with_router():
|
|
||||||
"""Test dictionary conversion for flow with router."""
|
|
||||||
flow = RouterFlow()
|
|
||||||
structure = build_flow_structure(flow)
|
|
||||||
dag_dict = structure_to_dict(structure)
|
|
||||||
|
|
||||||
decide_node = dag_dict["nodes"]["decide"]
|
|
||||||
assert decide_node["type"] == "router"
|
|
||||||
assert decide_node["is_router"] is True
|
|
||||||
|
|
||||||
if "router_paths" in decide_node:
|
|
||||||
assert len(decide_node["router_paths"]) >= 1
|
|
||||||
|
|
||||||
router_edges = [edge for edge in dag_dict["edges"] if edge["is_router_path"]]
|
|
||||||
assert len(router_edges) >= 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_structure_to_dict_with_complex_conditions():
|
|
||||||
"""Test dictionary conversion for flow with complex conditions."""
|
|
||||||
flow = ComplexFlow()
|
|
||||||
structure = build_flow_structure(flow)
|
|
||||||
dag_dict = structure_to_dict(structure)
|
|
||||||
|
|
||||||
converge_and_node = dag_dict["nodes"]["converge_and"]
|
|
||||||
assert converge_and_node["condition_type"] == "AND"
|
|
||||||
assert "trigger_condition" in converge_and_node
|
|
||||||
assert converge_and_node["trigger_condition"]["type"] == "AND"
|
|
||||||
|
|
||||||
converge_or_node = dag_dict["nodes"]["converge_or"]
|
|
||||||
assert converge_or_node["condition_type"] == "OR"
|
|
||||||
|
|
||||||
|
|
||||||
def test_visualize_flow_structure_creates_html():
|
def test_visualize_flow_structure_creates_html():
|
||||||
"""Test that visualization generates valid HTML file."""
|
"""Test that visualization generates valid HTML file."""
|
||||||
flow = SimpleFlow()
|
flow = SimpleFlow()
|
||||||
@@ -243,7 +182,7 @@ def test_visualize_flow_structure_creates_assets():
|
|||||||
|
|
||||||
js_content = js_file.read_text(encoding="utf-8")
|
js_content = js_file.read_text(encoding="utf-8")
|
||||||
assert len(js_content) > 0
|
assert len(js_content) > 0
|
||||||
assert "var nodes" in js_content or "const nodes" in js_content
|
assert "NetworkManager" in js_content
|
||||||
|
|
||||||
|
|
||||||
def test_visualize_flow_structure_json_data():
|
def test_visualize_flow_structure_json_data():
|
||||||
@@ -268,22 +207,6 @@ def test_visualize_flow_structure_json_data():
|
|||||||
assert "path_b" in js_content
|
assert "path_b" in js_content
|
||||||
|
|
||||||
|
|
||||||
def test_print_structure_summary():
|
|
||||||
"""Test printing flow structure summary."""
|
|
||||||
flow = ComplexFlow()
|
|
||||||
structure = build_flow_structure(flow)
|
|
||||||
|
|
||||||
output = print_structure_summary(structure)
|
|
||||||
|
|
||||||
assert "Total nodes:" in output
|
|
||||||
assert "Total edges:" in output
|
|
||||||
assert "Start methods:" in output
|
|
||||||
assert "Router methods:" in output
|
|
||||||
|
|
||||||
assert "start_a" in output
|
|
||||||
assert "start_b" in output
|
|
||||||
|
|
||||||
|
|
||||||
def test_node_metadata_includes_source_info():
|
def test_node_metadata_includes_source_info():
|
||||||
"""Test that nodes include source code and line number information."""
|
"""Test that nodes include source code and line number information."""
|
||||||
flow = SimpleFlow()
|
flow = SimpleFlow()
|
||||||
@@ -364,8 +287,7 @@ def test_visualization_handles_special_characters():
|
|||||||
|
|
||||||
assert len(structure["nodes"]) == 2
|
assert len(structure["nodes"]) == 2
|
||||||
|
|
||||||
dag_dict = structure_to_dict(structure)
|
json_str = json.dumps(structure)
|
||||||
json_str = json.dumps(dag_dict)
|
|
||||||
assert json_str is not None
|
assert json_str is not None
|
||||||
assert "method_with_underscore" in json_str
|
assert "method_with_underscore" in json_str
|
||||||
assert "another_method_123" in json_str
|
assert "another_method_123" in json_str
|
||||||
@@ -390,7 +312,6 @@ def test_topological_path_counting():
|
|||||||
"""Test that topological path counting is accurate."""
|
"""Test that topological path counting is accurate."""
|
||||||
flow = ComplexFlow()
|
flow = ComplexFlow()
|
||||||
structure = build_flow_structure(flow)
|
structure = build_flow_structure(flow)
|
||||||
dag_dict = structure_to_dict(structure)
|
|
||||||
|
|
||||||
assert len(structure["nodes"]) > 0
|
assert len(structure["nodes"]) > 0
|
||||||
assert len(structure["edges"]) > 0
|
assert len(structure["edges"]) > 0
|
||||||
|
|||||||
@@ -117,13 +117,7 @@ warn_return_any = true
|
|||||||
show_error_codes = true
|
show_error_codes = true
|
||||||
warn_unused_ignores = true
|
warn_unused_ignores = true
|
||||||
python_version = "3.12"
|
python_version = "3.12"
|
||||||
exclude = [
|
exclude = "(?x)(^lib/crewai/src/crewai/cli/templates/ | ^lib/crewai/tests/ | ^lib/crewai-tools/tests/)"
|
||||||
"lib/crewai/src/crewai/cli/templates",
|
|
||||||
"lib/crewai/tests/",
|
|
||||||
# crewai-tools
|
|
||||||
"lib/crewai-tools/tests/",
|
|
||||||
"lib/crewai/src/crewai/experimental/a2a"
|
|
||||||
]
|
|
||||||
plugins = ["pydantic.mypy", "crewai.mypy"]
|
plugins = ["pydantic.mypy", "crewai.mypy"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user