fix: Update path filters, test tags and backend processing (#6613)
* ✨ (tests): update tag value from "@starter-project" to "@starter-projects" for consistency across test files. * 📝 (changes-filter.yaml): add new path "src/backend/base/langflow/custom/**" to starter-projects section ♻️ (changes-filter.yaml): remove path "src/backend/base/langflow/components/**" from components section and add it to starter-projects section ♻️ (changes-filter.yaml): add new path "src/backend/base/langflow/custom/**" to components section * 🐛 (component.py): fix a bug where the status was not being returned when there is only one output in the component * 🔧 (ci.yml): Expand path filter outputs for more comprehensive CI testing * fix docstring placement * ✨ (group.spec.ts): update click event modifiers to use "ControlOrMeta" for better cross-platform compatibility * ✨ (changes-filter.yaml): add new directory "src/backend/base/langflow/graph/" to starter-projects for processing in the build pipeline. * 🔄 Refactor graph data structures from sets to lists for performance and compatibility * 🔧 Optimize RunnableVerticesManager predecessor checks and type hints --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
0fbe1b0d09
commit
0a631a68c2
30 changed files with 76 additions and 56 deletions
|
|
@ -982,6 +982,8 @@ class Component(CustomComponent):
|
|||
|
||||
def _process_raw_result(self, result):
|
||||
"""Process the raw result of the component."""
|
||||
if len(self.outputs) == 1:
|
||||
return self.status or self.extract_data(result)
|
||||
return self.extract_data(result)
|
||||
|
||||
def extract_data(self, result):
|
||||
|
|
|
|||
|
|
@ -116,10 +116,10 @@ class Graph:
|
|||
|
||||
self.top_level_vertices: list[str] = []
|
||||
self.vertex_map: dict[str, Vertex] = {}
|
||||
self.predecessor_map: dict[str, set[str]] = defaultdict(set)
|
||||
self.successor_map: dict[str, set[str]] = defaultdict(set)
|
||||
self.predecessor_map: dict[str, list[str]] = defaultdict(list)
|
||||
self.successor_map: dict[str, list[str]] = defaultdict(list)
|
||||
self.in_degree_map: dict[str, int] = defaultdict(int)
|
||||
self.parent_child_map: dict[str, set[str]] = defaultdict(set)
|
||||
self.parent_child_map: dict[str, list[str]] = defaultdict(list)
|
||||
self._run_queue: deque[str] = deque()
|
||||
self._first_layer: list[str] = []
|
||||
self._lock = asyncio.Lock()
|
||||
|
|
@ -469,10 +469,10 @@ class Graph:
|
|||
self.add_edge(edge)
|
||||
source_id = edge["data"]["sourceHandle"]["id"]
|
||||
target_id = edge["data"]["targetHandle"]["id"]
|
||||
self.predecessor_map[target_id].add(source_id)
|
||||
self.successor_map[source_id].add(target_id)
|
||||
self.predecessor_map[target_id].append(source_id)
|
||||
self.successor_map[source_id].append(target_id)
|
||||
self.in_degree_map[target_id] += 1
|
||||
self.parent_child_map[source_id].add(target_id)
|
||||
self.parent_child_map[source_id].append(target_id)
|
||||
|
||||
def add_node(self, node: NodeData) -> None:
|
||||
self._vertices.append(node)
|
||||
|
|
@ -1554,7 +1554,7 @@ class Graph:
|
|||
logger.debug("Graph processing complete")
|
||||
return self
|
||||
|
||||
def find_next_runnable_vertices(self, vertex_successors_ids: set[str]) -> list[str]:
|
||||
def find_next_runnable_vertices(self, vertex_successors_ids: list[str]) -> list[str]:
|
||||
next_runnable_vertices = set()
|
||||
for v_id in sorted(vertex_successors_ids):
|
||||
if not self.is_vertex_runnable(v_id):
|
||||
|
|
@ -2023,7 +2023,10 @@ class Graph:
|
|||
|
||||
def build_in_degree(self, edges: list[CycleEdge]) -> dict[str, int]:
|
||||
in_degree: dict[str, int] = defaultdict(int)
|
||||
|
||||
for edge in edges:
|
||||
# We don't need to count if a Component connects more than one
|
||||
# time to the same vertex.
|
||||
in_degree[edge.target_id] += 1
|
||||
for vertex in self.vertices:
|
||||
if vertex.id not in in_degree:
|
||||
|
|
@ -2031,13 +2034,13 @@ class Graph:
|
|||
return in_degree
|
||||
|
||||
@staticmethod
|
||||
def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, set[str]], dict[str, set[str]]]:
|
||||
def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]:
|
||||
"""Returns the adjacency maps for the graph."""
|
||||
predecessor_map: dict[str, set[str]] = defaultdict(set)
|
||||
successor_map: dict[str, set[str]] = defaultdict(set)
|
||||
predecessor_map: dict[str, list[str]] = defaultdict(list)
|
||||
successor_map: dict[str, list[str]] = defaultdict(list)
|
||||
for edge in edges:
|
||||
predecessor_map[edge.target_id].add(edge.source_id)
|
||||
successor_map[edge.source_id].add(edge.target_id)
|
||||
predecessor_map[edge.target_id].append(edge.source_id)
|
||||
successor_map[edge.source_id].append(edge.target_id)
|
||||
return predecessor_map, successor_map
|
||||
|
||||
def __to_dict(self) -> dict[str, dict[str, list[str]]]:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ from collections import defaultdict
|
|||
|
||||
|
||||
class RunnableVerticesManager:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.run_map: dict[str, list[str]] = defaultdict(list) # Tracks successors of each vertex
|
||||
self.run_predecessors: dict[str, set[str]] = defaultdict(set) # Tracks predecessors for each vertex
|
||||
self.run_predecessors: dict[str, list[str]] = defaultdict(list) # Tracks predecessors for each vertex
|
||||
self.vertices_to_run: set[str] = set() # Set of vertices that are ready to run
|
||||
self.vertices_being_run: set[str] = set() # Set of vertices that are currently running
|
||||
self.cycle_vertices: set[str] = set() # Set of vertices that are in a cycle
|
||||
|
|
@ -74,13 +74,14 @@ class RunnableVerticesManager:
|
|||
bool: True if all predecessor conditions are met, False otherwise
|
||||
"""
|
||||
# Get pending predecessors, return True if none exist
|
||||
if not (pending := self.run_predecessors.get(vertex_id, set())):
|
||||
pending = self.run_predecessors.get(vertex_id, [])
|
||||
if not pending:
|
||||
return True
|
||||
|
||||
# For cycle vertices, check if any pending predecessors are also in cycle
|
||||
# Using set intersection is faster than iteration
|
||||
if vertex_id in self.cycle_vertices:
|
||||
return is_loop or not bool(pending.intersection(self.cycle_vertices))
|
||||
return is_loop or not bool(set(pending) & self.cycle_vertices)
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -461,8 +461,8 @@ def find_cycle_vertices(edges):
|
|||
def layered_topological_sort(
|
||||
vertices_ids: set[str],
|
||||
in_degree_map: dict[str, int],
|
||||
successor_map: dict[str, set[str]],
|
||||
predecessor_map: dict[str, set[str]],
|
||||
successor_map: dict[str, list[str]],
|
||||
predecessor_map: dict[str, list[str]],
|
||||
start_id: str | None = None,
|
||||
cycle_vertices: set[str] | None = None,
|
||||
is_input_vertex: Callable[[str], bool] | None = None, # noqa: ARG001
|
||||
|
|
@ -780,8 +780,8 @@ def get_sorted_vertices(
|
|||
start_component_id: str | None = None,
|
||||
graph_dict: dict[str, Any] | None = None,
|
||||
in_degree_map: dict[str, int] | None = None,
|
||||
successor_map: dict[str, set[str]] | None = None,
|
||||
predecessor_map: dict[str, set[str]] | None = None,
|
||||
successor_map: dict[str, list[str]] | None = None,
|
||||
predecessor_map: dict[str, list[str]] | None = None,
|
||||
is_input_vertex: Callable[[str], bool] | None = None,
|
||||
get_vertex_predecessors: Callable[[str], list[str]] | None = None,
|
||||
get_vertex_successors: Callable[[str], list[str]] | None = None,
|
||||
|
|
@ -826,18 +826,18 @@ def get_sorted_vertices(
|
|||
successor_map = {}
|
||||
for vertex_id in vertices_ids:
|
||||
if get_vertex_successors is not None:
|
||||
successor_map[vertex_id] = set(get_vertex_successors(vertex_id))
|
||||
successor_map[vertex_id] = get_vertex_successors(vertex_id)
|
||||
else:
|
||||
successor_map[vertex_id] = set()
|
||||
successor_map[vertex_id] = []
|
||||
|
||||
# Build predecessor_map if not provided
|
||||
if predecessor_map is None:
|
||||
predecessor_map = {}
|
||||
for vertex_id in vertices_ids:
|
||||
if get_vertex_predecessors is not None:
|
||||
predecessor_map[vertex_id] = set(get_vertex_predecessors(vertex_id))
|
||||
predecessor_map[vertex_id] = get_vertex_predecessors(vertex_id)
|
||||
else:
|
||||
predecessor_map[vertex_id] = set()
|
||||
predecessor_map[vertex_id] = []
|
||||
|
||||
# If we have a stop component, we need to filter out all vertices
|
||||
# that are not predecessors of the stop component
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class Vertex:
|
|||
self.built_object: Any = UnbuiltObject()
|
||||
self.built_result: Any = None
|
||||
self.built = False
|
||||
self._successors_ids: set[str] | None = None
|
||||
self._successors_ids: list[str] | None = None
|
||||
self.artifacts: dict[str, Any] = {}
|
||||
self.artifacts_raw: dict[str, Any] = {}
|
||||
self.artifacts_type: dict[str, str] = {}
|
||||
|
|
@ -207,8 +207,8 @@ class Vertex:
|
|||
return self.graph.get_successors(self)
|
||||
|
||||
@property
|
||||
def successors_ids(self) -> set[str]:
|
||||
return self.graph.successor_map.get(self.id, set())
|
||||
def successors_ids(self) -> list[str]:
|
||||
return self.graph.successor_map.get(self.id, [])
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ class StateVertex(ComponentVertex):
|
|||
self.is_state = False
|
||||
|
||||
@property
|
||||
def successors_ids(self) -> set[str]:
|
||||
def successors_ids(self) -> list[str]:
|
||||
if self._successors_ids is None:
|
||||
self.is_state = False
|
||||
return super().successors_ids
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue