diff --git a/src/crewai/flow/flow_visual_utils.py b/src/crewai/flow/flow_visual_utils.py index 604d226b9..6296e2627 100644 --- a/src/crewai/flow/flow_visual_utils.py +++ b/src/crewai/flow/flow_visual_utils.py @@ -5,7 +5,7 @@ Flow graphs and calculating layout information. These utilities are separated from general-purpose utilities to maintain a clean dependency structure. """ -from typing import TYPE_CHECKING, Dict, List, Set, Any +from typing import Any, Dict, List, Set, TYPE_CHECKING if TYPE_CHECKING: from crewai.flow.flow import Flow diff --git a/src/crewai/flow/utils.py b/src/crewai/flow/utils.py index 26f266ae2..27feb9bb8 100644 --- a/src/crewai/flow/utils.py +++ b/src/crewai/flow/utils.py @@ -10,7 +10,7 @@ New code should import from the appropriate new modules directly. from typing import Any, Dict, List, Optional, Set -from .core_flow_utils import get_possible_return_constants +from .core_flow_utils import get_possible_return_constants, is_ancestor from .flow_visual_utils import ( build_ancestor_dict, build_parent_children_dict, @@ -18,7 +18,6 @@ from .flow_visual_utils import ( count_outgoing_edges, dfs_ancestors, get_child_index, - is_ancestor, ) # Re-export all functions for backwards compatibility diff --git a/src/crewai/flow/visualization_utils.py b/src/crewai/flow/visualization_utils.py index e84a04f90..1541b2679 100644 --- a/src/crewai/flow/visualization_utils.py +++ b/src/crewai/flow/visualization_utils.py @@ -4,9 +4,9 @@ import os from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast +from crewai.flow.flow import Flow from pyvis.network import Network -from crewai.flow.flow import Flow from .core_flow_utils import is_ancestor from .flow_visual_utils import ( build_ancestor_dict, @@ -16,7 +16,7 @@ from .flow_visual_utils import ( from .path_utils import safe_path_join, validate_file_path -def method_calls_crew(method: Callable[..., Any]) -> bool: +def method_calls_crew(method: Optional[Callable[..., Any]]) -> bool: """Check if the method contains a .crew() call in its implementation. Analyzes the method's source code using AST to detect if it makes any @@ -24,14 +24,18 @@ def method_calls_crew(method: Callable[..., Any]) -> bool: flow execution. Args: - method: The method to analyze for crew calls + method: The method to analyze for crew calls, can be None Returns: bool: True if the method contains a .crew() call, False otherwise Raises: - Exception: If method source code cannot be parsed + TypeError: If input is not None and not a callable method + ValueError: If method source code cannot be parsed + RuntimeError: If unexpected error occurs during parsing """ + if method is None: + return False if not callable(method): raise TypeError("Input must be a callable method") @@ -174,17 +178,6 @@ def add_nodes_to_network(net: Network, flow: Flow[Any], def compute_positions(flow: Flow[Any], node_levels: Dict[str, int], y_spacing: float = 150, x_spacing: float = 150) -> Dict[str, Tuple[float, float]]: - if not hasattr(flow, '_methods'): - raise ValueError("Invalid flow object: missing '_methods' attribute") - if not isinstance(node_levels, dict): - raise TypeError("node_levels must be a dictionary") - if not isinstance(y_spacing, (int, float)) or y_spacing <= 0: - raise ValueError("y_spacing must be a positive number") - if not isinstance(x_spacing, (int, float)) or x_spacing <= 0: - raise ValueError("x_spacing must be a positive number") - - if not node_levels: - raise ValueError("node_levels dictionary cannot be empty") """Calculate precise x,y coordinates for each node in the flow diagram. Computes optimal node positions with fine-grained control over spacing @@ -205,6 +198,17 @@ def compute_positions(flow: Flow[Any], node_levels: Dict[str, int], Positions are calculated to maintain clear hierarchical structure while ensuring optimal spacing and readability of the flow diagram. """ + if not hasattr(flow, '_methods'): + raise ValueError("Invalid flow object: missing '_methods' attribute") + if not isinstance(node_levels, dict): + raise TypeError("node_levels must be a dictionary") + if not isinstance(y_spacing, (int, float)) or y_spacing <= 0: + raise ValueError("y_spacing must be a positive number") + if not isinstance(x_spacing, (int, float)) or x_spacing <= 0: + raise ValueError("x_spacing must be a positive number") + + if not node_levels: + raise ValueError("node_levels dictionary cannot be empty") level_nodes: Dict[int, List[str]] = {} node_positions: Dict[str, Tuple[float, float]] = {} @@ -249,28 +253,6 @@ def add_edges(net: Network, flow: Flow[Any], os.makedirs(asset_dir, exist_ok=True) except (ValueError, OSError) as e: raise OSError(f"Failed to create or validate asset directory: {e}") - """Add edges between nodes with precise styling and intelligent routing. - - Creates and styles edges in the visualization with fine-grained control over - appearance, routing, and curvature. Handles both normal method connections - and router paths with specialized styling. - - Args: - net: The network visualization object to add edges to - flow: Flow object containing method relationships and router paths - node_positions: Dictionary mapping method names to (x,y) coordinates - colors: Dictionary mapping edge types to their colors - - Returns: - None - - Note: - Implements sophisticated edge routing with: - - Automatic curve direction based on node positions - - Dynamic curvature adjustment for multiple edges - - Distinct styling for router paths and AND conditions - - Cycle detection and appropriate visualization - """ ancestors = build_ancestor_dict(flow) parent_children = build_parent_children_dict(flow) @@ -299,24 +281,24 @@ def add_edges(net: Network, flow: Flow[Any], dx = target_pos[0] - source_pos[0] smooth_type = "curvedCCW" if dx <= 0 else "curvedCW" index = get_child_index(trigger, method_name, parent_children) - edge_smooth = { + edge_config = { "type": smooth_type, "roundness": 0.2 + (0.1 * index), } else: - edge_smooth = {"type": "cubicBezier"} + edge_config = {"type": "cubicBezier"} else: - edge_smooth: Dict[str, Any] = {"type": "straight"} + edge_config = {"type": "straight"} - edge_style: Dict[str, Any] = { + edge_props: Dict[str, Any] = { "color": edge_color, "width": 2, "arrows": "to", "dashes": True if is_router_edge or is_and_condition else False, - "smooth": edge_smooth, + "smooth": edge_config, } - net.add_edge(trigger, method_name, **edge_style) + net.add_edge(trigger, method_name, **edge_props) else: # Nodes not found in node_positions. Check if it's a known router outcome and a known method. is_router_edge = any( @@ -362,23 +344,23 @@ def add_edges(net: Network, flow: Flow[Any], index = get_child_index( router_method_name, listener_name, parent_children ) - edge_smooth = { + edge_config = { "type": smooth_type, "roundness": 0.2 + (0.1 * index), } else: - edge_smooth = {"type": "cubicBezier"} + edge_config = {"type": "cubicBezier"} else: - edge_smooth: Dict[str, Any] = {"type": "straight"} + edge_config = {"type": "straight"} - edge_style: Dict[str, Any] = { + edge_props: Dict[str, Any] = { "color": colors["router_edge"], "width": 2, "arrows": "to", "dashes": True, - "smooth": edge_smooth, + "smooth": edge_config, } - net.add_edge(router_method_name, listener_name, **edge_style) + net.add_edge(router_method_name, listener_name, **edge_props) else: # Same check here: known router edge and known method? method_known = listener_name in flow._methods diff --git a/uv.lock b/uv.lock index c37a1fa4e..506998441 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,18 @@ version = 1 requires-python = ">=3.10, <3.13" resolution-markers = [ - "python_full_version < '3.11'", - "python_full_version == '3.11.*'", - "python_full_version >= '3.12' and python_full_version < '3.12.4'", - "python_full_version >= '3.12.4'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux')", ] [[package]] @@ -300,7 +308,7 @@ name = "build" version = "1.2.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, + { name = "colorama", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, { name = "packaging" }, { name = "pyproject-hooks" }, @@ -535,7 +543,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -642,7 +650,6 @@ tools = [ [package.dev-dependencies] dev = [ { name = "cairosvg" }, - { name = "crewai-tools" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocs-material-extensions" }, @@ -696,7 +703,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cairosvg", specifier = ">=2.7.1" }, - { name = "crewai-tools", specifier = ">=0.17.0" }, { name = "mkdocs", specifier = ">=1.4.3" }, { name = "mkdocs-material", specifier = ">=9.5.7" }, { name = "mkdocs-material-extensions", specifier = ">=1.3.1" }, @@ -2462,7 +2468,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "jinja2" }, { name = "markdown" }, @@ -2643,7 +2649,7 @@ version = "2.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } @@ -2890,7 +2896,7 @@ name = "nvidia-cudnn-cu12" version = "9.1.0.70" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, @@ -2917,9 +2923,9 @@ name = "nvidia-cusolver-cu12" version = "11.4.5.107" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, - { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, @@ -2930,7 +2936,7 @@ name = "nvidia-cusparse-cu12" version = "12.1.0.106" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, @@ -3480,7 +3486,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -5022,19 +5028,19 @@ dependencies = [ { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ @@ -5081,7 +5087,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ @@ -5124,7 +5130,7 @@ version = "0.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, - { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "cffi", marker = "(implementation_name != 'pypy' and os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (implementation_name != 'pypy' and os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "outcome" }, @@ -5155,7 +5161,7 @@ name = "triton" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "filelock", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/45/27/14cc3101409b9b4b9241d2ba7deaa93535a217a211c86c4cc7151fb12181/triton-3.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1efef76935b2febc365bfadf74bcb65a6f959a9872e5bddf44cc9e0adce1e1a", size = 209376304 },