Skip to content

Commit

Permalink
fix: Update path filters, test tags and backend processing (#6613)
Browse files Browse the repository at this point in the history
* ✨ (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 <[email protected]>
  • Loading branch information
Cristhianzl and ogabrielluiz authored Feb 14, 2025
1 parent 0fbe1b0 commit 0a631a6
Show file tree
Hide file tree
Showing 30 changed files with 76 additions and 56 deletions.
5 changes: 4 additions & 1 deletion .github/changes-filter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ starter-projects:
- "src/frontend/src/pages/MainPage/**"
- "src/frontend/src/utils/reactflowUtils.ts"
- "src/frontend/tests/extended/features/**"
- "src/backend/base/langflow/custom/**"
- "src/backend/base/langflow/graph/**"

components:
- "src/frontend/src/components/**"
Expand All @@ -38,7 +40,6 @@ components:
- "src/frontend/src/CustomNodes/**"
- "src/frontend/src/style/**"
- "src/frontend/src/utils/styleUtils.ts"
- "src/backend/base/langflow/components/**"
- "src/frontend/tests/core/features/**"
- "src/frontend/tests/core/integrations/**"
- "src/frontend/tests/core/regression/**"
Expand All @@ -49,6 +50,8 @@ components:
- "src/backend/base/langflow/schema/**"
- "src/backend/base/langflow/graph/**"
- "src/backend/base/langflow/utils/**"
- "src/backend/base/langflow/custom/**"
- "src/backend/base/langflow/components/**"

workspace:
- "src/backend/base/langflow/inputs/**"
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ jobs:
frontend-tests: ${{ steps.filter.outputs.frontend-tests }}
components-changes: ${{ steps.filter.outputs.components-changes }}
starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }}
starter-projects: ${{ steps.filter.outputs.starter-projects }}
components: ${{ steps.filter.outputs.components }}
workspace: ${{ steps.filter.outputs.workspace }}
api: ${{ steps.filter.outputs.api }}
database: ${{ steps.filter.outputs.database }}

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -119,11 +125,10 @@ jobs:
uses: ./.github/workflows/python_test.yml
with:
python-versions: ${{ inputs.python-versions || '["3.10"]' }}

test-frontend:
needs: path-filter
name: Run Frontend Tests
if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' }}
if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' || needs.path-filter.outputs.starter-projects == 'true' || needs.path-filter.outputs.components == 'true' || needs.path-filter.outputs.workspace == 'true' || needs.path-filter.outputs.api == 'true' || needs.path-filter.outputs.database == 'true' }}
uses: ./.github/workflows/typescript_test.yml
with:
tests_folder: ${{ inputs.frontend-tests-folder }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,8 @@ def _build_artifact(self, result):

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):
Expand Down
27 changes: 15 additions & 12 deletions src/backend/base/langflow/graph/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ def __init__(

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()
Expand Down Expand Up @@ -469,10 +469,10 @@ def _add_edge(self, edge: EdgeData) -> None:
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)
Expand Down Expand Up @@ -1554,7 +1554,7 @@ async def process(
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):
Expand Down Expand Up @@ -2023,21 +2023,24 @@ def get_top_level_vertices(self, vertices_ids):

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:
in_degree[vertex.id] = 0
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]]]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@


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
Expand Down Expand Up @@ -74,13 +74,14 @@ def are_all_predecessors_fulfilled(self, vertex_id: str, *, is_loop: bool) -> bo
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

Expand Down
16 changes: 8 additions & 8 deletions src/backend/base/langflow/graph/graph/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/backend/base/langflow/graph/vertex/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(
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] = {}
Expand Down Expand Up @@ -207,8 +207,8 @@ def successors(self) -> list[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()
Expand Down
2 changes: 1 addition & 1 deletion src/backend/base/langflow/graph/vertex/vertex_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ def __init__(self, data: NodeData, graph):
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
Expand Down
14 changes: 10 additions & 4 deletions src/frontend/tests/core/features/group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@ test.describe("group node test", () => {
await page.getByTestId("fit_view").first().click();

await page.getByTestId("title-OpenAI").click();
await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] });
await page.getByTestId("title-Prompt").click({ modifiers: ["Control"] });
await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] });
await page
.getByTestId("title-OpenAI")
.click({ modifiers: ["ControlOrMeta"] });
await page
.getByTestId("title-Prompt")
.click({ modifiers: ["ControlOrMeta"] });
await page
.getByTestId("title-OpenAI")
.click({ modifiers: ["ControlOrMeta"] });

await page.getByRole("button", { name: "Group" }).click();
await page.getByTestId("title-Group").click();
await page.getByTestId("edit-name-description-button").click();
await page.getByTestId("input-title-Group").first().fill("test");
await page.getByTestId("save-name-description-button").first().click();
await page.keyboard.press("Control+g");
await page.keyboard.press("ControlOrMeta+g");
await page.getByTestId("title-OpenAI").isVisible();
await page.getByTestId("title-Prompt").isVisible();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test(
"Basic Prompting (Hello, World)",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/core/integrations/Blog Writer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test(
"Blog Writer",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";

test(
"Custom Component Generator",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.ANTHROPIC_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/core/integrations/Document QA.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test(
"Document Q&A",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/core/integrations/Dynamic Agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test.skip(
"Dynamic Agent",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test.skip(
"Hierarchical Tasks Agent",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda

test(
"Image Sentiment Analysis",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";

test(
"Instagram Copywriter",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";

test(
"Market Research",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test(
"Memory Chatbot",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal";

test(
"Prompt Chaining",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda

test(
"SEO Keyword Generator",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/core/integrations/SaaS Pricing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda

test(
"SaaS Pricing",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test.skip(
"Sequential Task Agent",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/core/integrations/Simple Agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup";

test(
"Simple Agent",
{ tag: ["@release", "@starter-project"] },
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
Expand Down
Loading

0 comments on commit 0a631a6

Please sign in to comment.