From 3927bf576cded50f4935aeef21ac60f4ec71d533 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:39:19 -0300 Subject: [PATCH 01/88] feat(utils.py): add escape_json_dump function to escape JSON strings for Edge dictionaries --- src/backend/base/langflow/utils/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/base/langflow/utils/util.py b/src/backend/base/langflow/utils/util.py index d58fdc4f870..1c91869041f 100644 --- a/src/backend/base/langflow/utils/util.py +++ b/src/backend/base/langflow/utils/util.py @@ -1,5 +1,6 @@ import importlib import inspect +import json import re from functools import wraps from pathlib import Path @@ -456,3 +457,7 @@ def is_class_method(func, cls): Check if a function is a class method. """ return inspect.ismethod(func) and func.__self__ is cls.__class__ + + +def escape_json_dump(edge_dict): + return json.dumps(edge_dict).replace('"', "œ") From 6c40f7b62acf0f794d74e8d535825691c054eaa9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:39:54 -0300 Subject: [PATCH 02/88] refactor(Output): streamline add_types method to prevent duplicate entries in types list for improved type management --- src/backend/base/langflow/template/field/base.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index f3583e466da..4a09c656bef 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -184,12 +184,9 @@ def to_dict(self): return self.model_dump(by_alias=True, exclude_none=True) def add_types(self, _type: list[Any]): - for type_ in _type: - if self.types and type_ in self.types: - continue - if self.types is None: - self.types = [] - self.types.append(type_) + if self.types is None: + self.types = [] + self.types.extend([t for t in _type if t not in self.types]) def set_selected(self): if not self.selected and self.types: From a744489ee0f46351b8abcee2f67f029a7ab16e92 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:40:26 -0300 Subject: [PATCH 03/88] feat(data.py): add classmethod decorator to validate_data for enhanced validation logic when checking data types --- src/backend/base/langflow/schema/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/base/langflow/schema/data.py b/src/backend/base/langflow/schema/data.py index 2becf0e6cda..4bc34e297f6 100644 --- a/src/backend/base/langflow/schema/data.py +++ b/src/backend/base/langflow/schema/data.py @@ -24,6 +24,7 @@ class Data(BaseModel): default_value: Optional[str] = "" @model_validator(mode="before") + @classmethod def validate_data(cls, values): if not isinstance(values, dict): raise ValueError("Data must be a dictionary") From 8254275e0f10d2dc5120b0a695c8688684ed4e43 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:40:43 -0300 Subject: [PATCH 04/88] feat(setup.py): implement retry logic for loading starter projects to enhance robustness against JSON decode errors --- .../base/langflow/initial_setup/setup.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/setup.py b/src/backend/base/langflow/initial_setup/setup.py index dfcd1011a57..dfedeec7e2a 100644 --- a/src/backend/base/langflow/initial_setup/setup.py +++ b/src/backend/base/langflow/initial_setup/setup.py @@ -2,6 +2,7 @@ import json import os import shutil +import time from collections import defaultdict from copy import deepcopy from datetime import datetime, timezone @@ -23,6 +24,7 @@ from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.deps import get_settings_service, get_storage_service, get_variable_service, session_scope from langflow.template.field.prompt import DEFAULT_PROMPT_INTUT_TYPES +from langflow.utils.util import escape_json_dump STARTER_FOLDER_NAME = "Starter Projects" STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow." @@ -319,10 +321,6 @@ def update_edges_with_latest_component_versions(project_data): return project_data_copy -def escape_json_dump(edge_dict): - return json.dumps(edge_dict).replace('"', "œ") - - def log_node_changes(node_changes_log): # The idea here is to log the changes that were made to the nodes in debug # Something like: @@ -339,17 +337,23 @@ def log_node_changes(node_changes_log): logger.debug("\n".join(formatted_messages)) -def load_starter_projects() -> list[tuple[Path, dict]]: +def load_starter_projects(retries=3, delay=1) -> list[tuple[Path, dict]]: starter_projects = [] folder = Path(__file__).parent / "starter_projects" for file in folder.glob("*.json"): - with open(file, "r", encoding="utf-8") as f: - try: - project = orjson.loads(f.read()) - starter_projects.append((file, project)) - logger.info(f"Loaded starter project {file}") - except orjson.JSONDecodeError as e: - raise ValueError(f"Error loading starter project {file}: {e}") + attempt = 0 + while attempt < retries: + with open(file, "r", encoding="utf-8") as f: + try: + project = orjson.loads(f.read()) + starter_projects.append((file, project)) + logger.info(f"Loaded starter project {file}") + break # Break if load is successful + except orjson.JSONDecodeError as e: + attempt += 1 + if attempt >= retries: + raise ValueError(f"Error loading starter project {file}: {e}") + time.sleep(delay) # Wait before retrying return starter_projects From fd770f2a87166579832eecb082eb0ea1316bec13 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:40:55 -0300 Subject: [PATCH 05/88] fix(input_mixin.py): improve model_config formatting and update field_type alias for clarity and consistency in field definitions --- src/backend/base/langflow/inputs/input_mixin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 7b7f7c2b193..5e885c1a794 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -27,9 +27,13 @@ class FieldTypes(str, Enum): # Base mixin for common input field attributes and methods class BaseInputMixin(BaseModel, validate_assignment=True): # type: ignore - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", populate_by_name=True) + model_config = ConfigDict( + arbitrary_types_allowed=True, + extra="forbid", + populate_by_name=True, + ) - field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT) + field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT, alias="type") required: bool = False """Specifies if the field is required. Defaults to False.""" From 22fd048c2f557a0dad256ebcc888b34c18656172 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:41:31 -0300 Subject: [PATCH 06/88] feat(types.py): refactor vertex constructors to use NodeData and add input/output methods for better component interaction --- .../base/langflow/graph/vertex/types.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index 2078e341c08..aab27ff87e1 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -9,6 +9,7 @@ from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, serialize_field from langflow.graph.vertex.base import Vertex +from langflow.graph.vertex.schema import NodeData from langflow.inputs.inputs import InputTypes from langflow.schema import Data from langflow.schema.artifact import ArtifactType @@ -23,7 +24,7 @@ class CustomComponentVertex(Vertex): - def __init__(self, data: Dict, graph): + def __init__(self, data: NodeData, graph): super().__init__(data, graph=graph, base_type="custom_components") def _built_object_repr(self): @@ -32,9 +33,19 @@ def _built_object_repr(self): class ComponentVertex(Vertex): - def __init__(self, data: Dict, graph): + def __init__(self, data: NodeData, graph): super().__init__(data, graph=graph, base_type="component") + def get_input(self, name: str) -> InputTypes: + if self._custom_component is None: + raise ValueError(f"Vertex {self.id} does not have a component instance.") + return self._custom_component.get_input(name) + + def get_output(self, name: str) -> Output: + if self._custom_component is None: + raise ValueError(f"Vertex {self.id} does not have a component instance.") + return self._custom_component.get_output(name) + def _built_object_repr(self): if self.artifacts and "repr" in self.artifacts: return self.artifacts["repr"] or super()._built_object_repr() @@ -174,7 +185,7 @@ def _finalize_build(self): class InterfaceVertex(ComponentVertex): - def __init__(self, data: Dict, graph): + def __init__(self, data: NodeData, graph): super().__init__(data, graph=graph) self.steps = [self._build, self._run] @@ -424,7 +435,7 @@ def _is_chat_input(self): class StateVertex(ComponentVertex): - def __init__(self, data: Dict, graph): + def __init__(self, data: NodeData, graph): super().__init__(data, graph=graph) self.steps = [self._build] self.is_state = False From e75d06ecd37fb703aba1af03c5741f61f15f6272 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:41:45 -0300 Subject: [PATCH 07/88] feat(schema.py): add NodeData and Position TypedDicts for improved type safety and structure in vertex data handling --- .../base/langflow/graph/vertex/schema.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/backend/base/langflow/graph/vertex/schema.py diff --git a/src/backend/base/langflow/graph/vertex/schema.py b/src/backend/base/langflow/graph/vertex/schema.py new file mode 100644 index 00000000000..98f6ba5ef20 --- /dev/null +++ b/src/backend/base/langflow/graph/vertex/schema.py @@ -0,0 +1,21 @@ +from typing import Dict + +from typing_extensions import NotRequired, TypedDict + + +class Position(TypedDict): + x: float + y: float + + +class NodeData(TypedDict): + id: str + data: Dict + dragging: NotRequired[bool] + height: NotRequired[int] + width: NotRequired[int] + position: NotRequired[Position] + positionAbsolute: NotRequired[Position] + selected: NotRequired[bool] + parent_node_id: NotRequired[str] + type: str From 7e659214b0e0f1120d871e2b2c3be48290e5102f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:41:54 -0300 Subject: [PATCH 08/88] feat(base.py): update Vertex to use NodeData type and add to_data method for better data management and access --- src/backend/base/langflow/graph/vertex/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 357e9dc2b90..b47dcf86e7c 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -13,6 +13,7 @@ from langflow.exceptions.component import ComponentBuildException from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.utils import UnbuiltObject, UnbuiltResult, log_transaction +from langflow.graph.vertex.schema import NodeData from langflow.interface.initialize import loading from langflow.interface.listing import lazy_load_dict from langflow.schema.artifact import ArtifactType @@ -42,7 +43,7 @@ class VertexStates(str, Enum): class Vertex: def __init__( self, - data: Dict, + data: NodeData, graph: "Graph", base_type: Optional[str] = None, is_task: bool = False, @@ -63,7 +64,7 @@ def __init__( self.has_external_input = False self.has_external_output = False self.graph = graph - self._data = data + self._data = data.copy() self.base_type: Optional[str] = base_type self.outputs: List[Dict] = [] self._parse_data() @@ -101,6 +102,9 @@ def set_input_value(self, name: str, value: Any): raise ValueError(f"Vertex {self.id} does not have a component instance.") self._custom_component._set_input_value(name, value) + def to_data(self): + return self._data + def add_component_instance(self, component_instance: "Component"): component_instance.set_vertex(self) self._custom_component = component_instance From 862ae9c6e47125cfb18d98c7d8ad82e3007a1f16 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:42:13 -0300 Subject: [PATCH 09/88] refactor(schema.py): update TargetHandle and SourceHandle models to include model_config attribute --- src/backend/base/langflow/graph/edge/schema.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/schema.py b/src/backend/base/langflow/graph/edge/schema.py index d8ae9963c18..7e0f04108ac 100644 --- a/src/backend/base/langflow/graph/edge/schema.py +++ b/src/backend/base/langflow/graph/edge/schema.py @@ -1,6 +1,6 @@ from typing import Any, List, Optional -from pydantic import Field, field_validator +from pydantic import ConfigDict, Field, field_validator from typing_extensions import TypedDict from langflow.helpers.base_model import BaseModel @@ -39,7 +39,8 @@ def format(self, sep: str = "\n") -> str: class TargetHandle(BaseModel): - fieldName: str = Field(..., alias="fieldName", description="Field name for the target handle.") + model_config = ConfigDict(populate_by_name=True) + field_name: str = Field(..., alias="fieldName", description="Field name for the target handle.") id: str = Field(..., description="Unique identifier for the target handle.") input_types: List[str] = Field( default_factory=list, alias="inputTypes", description="List of input types for the target handle." @@ -48,6 +49,7 @@ class TargetHandle(BaseModel): class SourceHandle(BaseModel): + model_config = ConfigDict(populate_by_name=True) base_classes: list[str] = Field( default_factory=list, alias="baseClasses", description="List of base classes for the source handle." ) From ac5e5413976d7568dc34e4c279183ac4e299a5ee Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:42:28 -0300 Subject: [PATCH 10/88] Add TypedDict classes for graph schema serialization in `schema.py` --- .../base/langflow/graph/graph/schema.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/backend/base/langflow/graph/graph/schema.py b/src/backend/base/langflow/graph/graph/schema.py index 30d67255fd9..306ea7ba63c 100644 --- a/src/backend/base/langflow/graph/graph/schema.py +++ b/src/backend/base/langflow/graph/graph/schema.py @@ -1,10 +1,35 @@ from typing import TYPE_CHECKING, NamedTuple +from typing_extensions import NotRequired, TypedDict + +from langflow.graph.edge.schema import EdgeData +from langflow.graph.vertex.schema import NodeData + if TYPE_CHECKING: from langflow.graph.schema import ResultData from langflow.graph.vertex.base import Vertex +class ViewPort(TypedDict): + x: float + y: float + zoom: float + + +class GraphData(TypedDict): + nodes: list[NodeData] + edges: list[EdgeData] + viewport: NotRequired[ViewPort] + + +class GraphDump(TypedDict, total=False): + data: GraphData + is_component: bool + name: str + description: str + endpoint_name: str + + class VertexBuildResult(NamedTuple): result_dict: "ResultData" params: str From 3006402a4b470f0ef686be9825b33d5d0f1e8767 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:43:27 -0300 Subject: [PATCH 11/88] Refactor `Edge` class to improve handle validation and data handling - Consolidated imports and removed redundant `BaseModel` definitions for `SourceHandle` and `TargetHandle`. - Added `valid_handles`, `target_param`, and `_target_handle` attributes to `Edge` class. - Enhanced handle validation logic to distinguish between dictionary and string types. - Introduced `to_data` method to return edge data. - Updated attribute names to follow consistent naming conventions (`base_classes`, `input_types`, `field_name`). --- src/backend/base/langflow/graph/edge/base.py | 28 +------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 549b3f0950f..25c61ea802f 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -3,39 +3,13 @@ from loguru import logger from pydantic import BaseModel, Field, field_validator -from langflow.graph.edge.schema import EdgeData +from langflow.graph.edge.schema import EdgeData, SourceHandle, TargetHandle, TargetHandleDict from langflow.schema.schema import INPUT_FIELD_NAME if TYPE_CHECKING: from langflow.graph.vertex.base import Vertex -class SourceHandle(BaseModel): - baseClasses: list[str] = Field(default_factory=list, description="List of base classes for the source handle.") - dataType: str = Field(..., description="Data type for the source handle.") - id: str = Field(..., description="Unique identifier for the source handle.") - name: Optional[str] = Field(None, description="Name of the source handle.") - output_types: List[str] = Field(default_factory=list, description="List of output types for the source handle.") - - @field_validator("name", mode="before") - @classmethod - def validate_name(cls, v, _info): - if _info.data["dataType"] == "GroupNode": - # 'OpenAIModel-u4iGV_text_output' - splits = v.split("_", 1) - if len(splits) != 2: - raise ValueError(f"Invalid source handle name {v}") - v = splits[1] - return v - - -class TargetHandle(BaseModel): - fieldName: str = Field(..., description="Field name for the target handle.") - id: str = Field(..., description="Unique identifier for the target handle.") - inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.") - type: str = Field(..., description="Type of the target handle.") - - class Edge: def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self.source_id: str = source.id if source else "" From cf69f83c2d2637f66240761232cabf77470b50ee Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:43:44 -0300 Subject: [PATCH 12/88] Refactor `Edge` class to improve handle validation and data handling --- src/backend/base/langflow/graph/edge/base.py | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 25c61ea802f..61d20fe65b9 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Any, List, Optional, cast from loguru import logger -from pydantic import BaseModel, Field, field_validator from langflow.graph.edge.schema import EdgeData, SourceHandle, TargetHandle, TargetHandleDict from langflow.schema.schema import INPUT_FIELD_NAME @@ -14,12 +13,19 @@ class Edge: def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self.source_id: str = source.id if source else "" self.target_id: str = target.id if target else "" + self.valid_handles: bool = False + self.target_param: str | None = None + self._target_handle: TargetHandleDict | str | None = None + self._data = edge.copy() if data := edge.get("data", {}): self._source_handle = data.get("sourceHandle", {}) - self._target_handle = data.get("targetHandle", {}) + self._target_handle = cast(TargetHandleDict, data.get("targetHandle", {})) self.source_handle: SourceHandle = SourceHandle(**self._source_handle) - self.target_handle: TargetHandle = TargetHandle(**self._target_handle) - self.target_param = self.target_handle.fieldName + if isinstance(self._target_handle, dict): + self.target_handle: TargetHandle = TargetHandle(**self._target_handle) + else: + raise ValueError("Target handle is not a dictionary") + self.target_param = self.target_handle.field_name # validate handles self.validate_handles(source, target) else: @@ -29,7 +35,12 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self._target_handle = edge.get("targetHandle", "") # type: ignore # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD' # target_param is documents - self.target_param = cast(str, self._target_handle.split("|")[1]) # type: ignore + if isinstance(self._target_handle, str): + self.target_param = self._target_handle.split("|")[1] + self.source_handle = None + self.target_handle = None + else: + raise ValueError("Target handle is not a string") # Validate in __init__ to fail fast self.validate_edge(source, target) From 6ea27152373f40e457db1752ff8de5964f2d0de8 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:43:58 -0300 Subject: [PATCH 13/88] Refactor: Standardize attribute naming and add `to_data` method in Edge class - Renamed attributes to use snake_case consistently (`baseClasses` to `base_classes`, `inputTypes` to `input_types`, `fieldName` to `field_name`). - Added `to_data` method to return `_data` attribute. - Updated validation methods to use new attribute names. --- src/backend/base/langflow/graph/edge/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 61d20fe65b9..77a566f7cef 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -44,6 +44,9 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): # Validate in __init__ to fail fast self.validate_edge(source, target) + def to_data(self): + return self._data + def validate_handles(self, source, target) -> None: if isinstance(self._source_handle, str) or self.source_handle.baseClasses: self._legacy_validate_handles(source, target) From c6f31f890a292497d1274f360f22cefc95003c92 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:44:35 -0300 Subject: [PATCH 14/88] Refactor: Update Edge class to consistently use snake_case for attributes and improve validation logic for handles --- src/backend/base/langflow/graph/edge/base.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 77a566f7cef..f3520e72cf1 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, List, Optional, cast +from typing import TYPE_CHECKING, Any, cast from loguru import logger @@ -48,18 +48,18 @@ def to_data(self): return self._data def validate_handles(self, source, target) -> None: - if isinstance(self._source_handle, str) or self.source_handle.baseClasses: + if isinstance(self._source_handle, str) or self.source_handle.base_classes: self._legacy_validate_handles(source, target) else: self._validate_handles(source, target) def _validate_handles(self, source, target) -> None: - if self.target_handle.inputTypes is None: + if self.target_handle.input_types is None: self.valid_handles = self.target_handle.type in self.source_handle.output_types elif self.source_handle.output_types is not None: self.valid_handles = ( - any(output_type in self.target_handle.inputTypes for output_type in self.source_handle.output_types) + any(output_type in self.target_handle.input_types for output_type in self.source_handle.output_types) or self.target_handle.type in self.source_handle.output_types ) @@ -69,12 +69,12 @@ def _validate_handles(self, source, target) -> None: raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles") def _legacy_validate_handles(self, source, target) -> None: - if self.target_handle.inputTypes is None: - self.valid_handles = self.target_handle.type in self.source_handle.baseClasses + if self.target_handle.input_types is None: + self.valid_handles = self.target_handle.type in self.source_handle.base_classes else: self.valid_handles = ( - any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses) - or self.target_handle.type in self.source_handle.baseClasses + any(baseClass in self.target_handle.input_types for baseClass in self.source_handle.base_classes) + or self.target_handle.type in self.source_handle.base_classes ) if not self.valid_handles: logger.debug(self.source_handle) @@ -89,9 +89,9 @@ def __setstate__(self, state): self.target_handle = state.get("target_handle") def validate_edge(self, source, target) -> None: - # If the self.source_handle has baseClasses, then we are using the legacy + # If the self.source_handle has base_classes, then we are using the legacy # way of defining the source and target handles - if isinstance(self._source_handle, str) or self.source_handle.baseClasses: + if isinstance(self._source_handle, str) or self.source_handle.base_classes: self._legacy_validate_edge(source, target) else: self._validate_edge(source, target) @@ -218,5 +218,5 @@ def __repr__(self) -> str: if (hasattr(self, "source_handle") and self.source_handle) and ( hasattr(self, "target_handle") and self.target_handle ): - return f"{self.source_id} -[{self.source_handle.name}->{self.target_handle.fieldName}]-> {self.target_id}" + return f"{self.source_id} -[{self.source_handle.name}->{self.target_handle.field_name}]-> {self.target_id}" return f"{self.source_id} -[{self.target_param}]-> {self.target_id}" From 898326084e00f09261d1a930f2def36a7d3082aa Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:45:59 -0300 Subject: [PATCH 15/88] Refactor: Change node argument type in add_node and _create_vertex methods to NodeData for better type safety and clarity --- src/backend/base/langflow/graph/graph/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index e48519cb4c2..61cb334e400 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -19,6 +19,7 @@ from langflow.graph.graph.utils import find_start_component_id, process_flow, sort_up_to_vertex from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.vertex.base import Vertex, VertexStates +from langflow.graph.vertex.schema import NodeData from langflow.graph.vertex.types import ComponentVertex, InterfaceVertex, StateVertex from langflow.schema import Data from langflow.schema.schema import INPUT_FIELD_NAME, InputType @@ -183,7 +184,7 @@ async def async_start(self, inputs: Optional[List[dict]] = None): return def start(self, inputs: Optional[List[dict]] = None) -> Generator: - #! Change this soon + #! Change this ASAP nest_asyncio.apply() loop = asyncio.get_event_loop() async_gen = self.async_start(inputs) @@ -208,8 +209,7 @@ def _add_edge(self, edge: EdgeData): self.in_degree_map[target_id] += 1 self.parent_child_map[source_id].append(target_id) - # TODO: Create a TypedDict to represente the node - def add_node(self, node: dict): + def add_node(self, node: NodeData): self._vertices.append(node) def add_edge(self, edge: EdgeData): @@ -1400,7 +1400,7 @@ def _build_vertices(self) -> List[Vertex]: return vertices - def _create_vertex(self, frontend_data: dict): + def _create_vertex(self, frontend_data: NodeData): vertex_data = frontend_data["data"] vertex_type: str = vertex_data["type"] # type: ignore vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore From 5f24db85b5e6ae864fa069cc99e536c102729eea Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:49:50 -0300 Subject: [PATCH 16/88] Refactor: Implement JSON serialization for graph data with `dumps` and `dump` methods, enhancing data export capabilities --- src/backend/base/langflow/graph/graph/base.py | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 61cb334e400..393f657315e 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1,4 +1,5 @@ import asyncio +import json import uuid from collections import defaultdict, deque from datetime import datetime, timezone @@ -14,7 +15,7 @@ from langflow.graph.edge.schema import EdgeData from langflow.graph.graph.constants import Finish, lazy_load_vertex_dict from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager -from langflow.graph.graph.schema import VertexBuildResult +from langflow.graph.graph.schema import GraphData, GraphDump, VertexBuildResult from langflow.graph.graph.state_manager import GraphStateManager from langflow.graph.graph.utils import find_start_component_id, process_flow, sort_up_to_vertex from langflow.graph.schema import InterfaceComponentTypes, RunOutputs @@ -76,7 +77,7 @@ def __init__( self.vertices: List[Vertex] = [] self.run_manager = RunnableVerticesManager() self.state_manager = GraphStateManager() - self._vertices: List[dict] = [] + self._vertices: List[NodeData] = [] self._edges: List[EdgeData] = [] self.top_level_vertices: List[str] = [] self.vertex_map: Dict[str, Vertex] = {} @@ -87,6 +88,7 @@ def __init__( self._run_queue: deque[str] = deque() self._first_layer: List[str] = [] self._lock = asyncio.Lock() + self.raw_graph_data: GraphData = {"nodes": [], "edges": []} try: self.tracing_service: "TracingService" | None = get_tracing_service() except Exception as exc: @@ -98,7 +100,39 @@ def __init__( if (start is not None and end is None) or (start is None and end is not None): raise ValueError("You must provide both input and output components") - def add_nodes_and_edges(self, nodes: List[Dict], edges: List[EdgeData]): + def dumps( + self, + name: Optional[str] = None, + description: Optional[str] = None, + endpoint_name: Optional[str] = None, + ) -> str: + graph_dict = self.dump(name, description, endpoint_name) + return json.dumps(graph_dict, indent=4, sort_keys=True) + + def dump( + self, name: Optional[str] = None, description: Optional[str] = None, endpoint_name: Optional[str] = None + ) -> GraphDump: + if self.raw_graph_data != {"nodes": [], "edges": []}: + data_dict = self.raw_graph_data + else: + # we need to convert the vertices and edges to json + nodes = [node.to_data() for node in self.vertices] + edges = [edge.to_data() for edge in self.edges] + self.raw_graph_data = {"nodes": nodes, "edges": edges} + data_dict = self.raw_graph_data + graph_dict: GraphDump = { + "data": data_dict, + "is_component": len(data_dict.get("nodes", [])) == 1 and data_dict["edges"] == [], + } + if name: + graph_dict["name"] = name + if description: + graph_dict["description"] = description + if endpoint_name: + graph_dict["endpoint_name"] = endpoint_name + return graph_dict + + def add_nodes_and_edges(self, nodes: List[NodeData], edges: List[EdgeData]): self._vertices = nodes self._edges = edges self.raw_graph_data = {"nodes": nodes, "edges": edges} From 4239ab679c9711b0cc02688a2815d62df769a03b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 15:59:47 -0300 Subject: [PATCH 17/88] Refactor: Add pytest fixtures for ingestion and RAG graphs, enhance test structure for better clarity and organization --- .../starter_projects/test_vector_store_rag.py | 182 ++++++++++++++++-- 1 file changed, 170 insertions(+), 12 deletions(-) diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py b/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py index 1482ed55621..95fc5a4e74d 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py @@ -1,5 +1,7 @@ from textwrap import dedent +import pytest + from langflow.components.data.File import FileComponent from langflow.components.embeddings.OpenAIEmbeddings import OpenAIEmbeddingsComponent from langflow.components.helpers.ParseData import ParseDataComponent @@ -14,10 +16,17 @@ from langflow.schema.data import Data -def test_vector_store_rag(): +@pytest.fixture +def client(): + pass + + +@pytest.fixture +def ingestion_graph(): # Ingestion Graph file_component = FileComponent(_id="file-123") file_component.set(path="test.txt") + file_component.set_output_value("data", Data(text="This is a test file.")) text_splitter = SplitTextComponent(_id="text-splitter-123") text_splitter.set(data_inputs=file_component.load_file) openai_embeddings = OpenAIEmbeddingsComponent(_id="openai-embeddings-123") @@ -31,8 +40,18 @@ def test_vector_store_rag(): api_endpoint="https://astra.example.com", token="token", ) + vector_store.set_output_value("vector_store", "mock_vector_store") + vector_store.set_output_value("base_retriever", "mock_retriever") + vector_store.set_output_value("search_results", [Data(text="This is a test file.")]) + ingestion_graph = Graph(file_component, vector_store) + return ingestion_graph + + +@pytest.fixture +def rag_graph(): # RAG Graph + openai_embeddings = OpenAIEmbeddingsComponent(_id="openai-embeddings-124") chat_input = ChatInput(_id="chatinput-123") chat_input.get_output("message").value = "What is the meaning of life?" rag_vector_store = AstraVectorStoreComponent(_id="rag-vector-store-123") @@ -69,21 +88,160 @@ def test_vector_store_rag(): chat_output.set(input_value=openai_component.text_response) graph = Graph(start=chat_input, end=chat_output) - assert graph is not None - ids = [ + return graph + + +def test_vector_store_rag(ingestion_graph, rag_graph): + assert ingestion_graph is not None + ingestion_ids = [ + "file-123", + "text-splitter-123", + "openai-embeddings-123", + "vector-store-123", + ] + assert rag_graph is not None + rag_ids = [ "chatinput-123", "chatoutput-123", "openai-123", "parse-data-123", "prompt-123", "rag-vector-store-123", - "openai-embeddings-123", + "openai-embeddings-124", + ] + for ids, graph, len_results in zip([ingestion_ids, rag_ids], [ingestion_graph, rag_graph], [5, 8]): + results = [] + for result in graph.start(): + results.append(result) + + assert len(results) == len_results + vids = [result.vertex.id for result in results if hasattr(result, "vertex")] + assert all(vid in ids for vid in vids), f"Diff: {set(vids) - set(ids)}" + assert results[-1] == Finish() + + +def test_vector_store_rag_dump_components_and_edges(ingestion_graph, rag_graph): + # Test ingestion graph components and edges + ingestion_graph_dump = ingestion_graph.dump( + name="Ingestion Graph", description="Graph for data ingestion", endpoint_name="ingestion" + ) + + ingestion_data = ingestion_graph_dump["data"] + ingestion_nodes = ingestion_data["nodes"] + ingestion_edges = ingestion_data["edges"] + + # Sort nodes by id to check components + ingestion_nodes = sorted(ingestion_nodes, key=lambda x: x["id"]) + + # Check components in the ingestion graph + assert ingestion_nodes[0]["data"]["type"] == "FileComponent" + assert ingestion_nodes[0]["id"] == "file-123" + + assert ingestion_nodes[1]["data"]["type"] == "OpenAIEmbeddingsComponent" + assert ingestion_nodes[1]["id"] == "openai-embeddings-123" + + assert ingestion_nodes[2]["data"]["type"] == "SplitTextComponent" + assert ingestion_nodes[2]["id"] == "text-splitter-123" + + assert ingestion_nodes[3]["data"]["type"] == "AstraVectorStoreComponent" + assert ingestion_nodes[3]["id"] == "vector-store-123" + + # Check edges in the ingestion graph + expected_ingestion_edges = [ + ("file-123", "text-splitter-123"), + ("text-splitter-123", "vector-store-123"), + ("openai-embeddings-123", "vector-store-123"), + ] + assert len(ingestion_edges) == len(expected_ingestion_edges) + + for edge in ingestion_edges: + source = edge["source"] + target = edge["target"] + assert (source, target) in expected_ingestion_edges, edge + + # Test RAG graph components and edges + rag_graph_dump = rag_graph.dump( + name="RAG Graph", description="Graph for Retrieval-Augmented Generation", endpoint_name="rag" + ) + + rag_data = rag_graph_dump["data"] + rag_nodes = rag_data["nodes"] + rag_edges = rag_data["edges"] + + # Sort nodes by id to check components + rag_nodes = sorted(rag_nodes, key=lambda x: x["id"]) + + # Check components in the RAG graph + assert rag_nodes[0]["data"]["type"] == "ChatInput" + assert rag_nodes[0]["id"] == "chatinput-123" + + assert rag_nodes[1]["data"]["type"] == "ChatOutput" + assert rag_nodes[1]["id"] == "chatoutput-123" + + assert rag_nodes[2]["data"]["type"] == "OpenAIModelComponent" + assert rag_nodes[2]["id"] == "openai-123" + + assert rag_nodes[3]["data"]["type"] == "OpenAIEmbeddingsComponent" + assert rag_nodes[3]["id"] == "openai-embeddings-124" + + assert rag_nodes[4]["data"]["type"] == "ParseDataComponent" + assert rag_nodes[4]["id"] == "parse-data-123" + + assert rag_nodes[5]["data"]["type"] == "PromptComponent" + assert rag_nodes[5]["id"] == "prompt-123" + + assert rag_nodes[6]["data"]["type"] == "AstraVectorStoreComponent" + assert rag_nodes[6]["id"] == "rag-vector-store-123" + + # Check edges in the RAG graph + expected_rag_edges = [ + ("chatinput-123", "rag-vector-store-123"), + ("openai-embeddings-124", "rag-vector-store-123"), + ("chatinput-123", "prompt-123"), + ("rag-vector-store-123", "parse-data-123"), + ("parse-data-123", "prompt-123"), + ("prompt-123", "openai-123"), + ("openai-123", "chatoutput-123"), ] - results = [] - for result in graph.start(): - results.append(result) - - assert len(results) == 8 - vids = [result.vertex.id for result in results if hasattr(result, "vertex")] - assert all(vid in ids for vid in vids), f"Diff: {set(vids) - set(ids)}" - assert results[-1] == Finish() + assert len(rag_edges) == len(expected_rag_edges), rag_edges + + for edge in rag_edges: + source = edge["source"] + target = edge["target"] + assert (source, target) in expected_rag_edges, f"Edge {source} -> {target} not found" + + +def test_vector_store_rag_dump(ingestion_graph, rag_graph): + # Test ingestion graph dump + ingestion_graph_dump = ingestion_graph.dump( + name="Ingestion Graph", description="Graph for data ingestion", endpoint_name="ingestion" + ) + assert isinstance(ingestion_graph_dump, dict) + + ingestion_data = ingestion_graph_dump["data"] + assert "nodes" in ingestion_data + assert "edges" in ingestion_data + assert "description" in ingestion_graph_dump + assert "endpoint_name" in ingestion_graph_dump + + ingestion_nodes = ingestion_data["nodes"] + ingestion_edges = ingestion_data["edges"] + assert len(ingestion_nodes) == 4 # There are 4 components in the ingestion graph + assert len(ingestion_edges) == 3 # There are 3 connections between components + + # Test RAG graph dump + rag_graph_dump = rag_graph.dump( + name="RAG Graph", description="Graph for Retrieval-Augmented Generation", endpoint_name="rag" + ) + assert isinstance(rag_graph_dump, dict) + + rag_data = rag_graph_dump["data"] + assert "nodes" in rag_data + assert "edges" in rag_data + assert "description" in rag_graph_dump + assert "endpoint_name" in rag_graph_dump + + rag_nodes = rag_data["nodes"] + rag_edges = rag_data["edges"] + assert len(rag_nodes) == 7 # There are 7 components in the RAG graph + assert len(rag_edges) == 7 # There are 7 connections between components From 46fec804602f60770da9a8468c690a05d3724d66 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:00:05 -0300 Subject: [PATCH 18/88] Refactor: Add pytest fixtures for memory_chatbot_graph tests and improve test structure --- .../starter_projects/test_memory_chatbot.py | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py index 3fd135de288..3957c2c7c27 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py @@ -1,5 +1,7 @@ from collections import deque +import pytest + from langflow.components.helpers.Memory import MemoryComponent from langflow.components.inputs.ChatInput import ChatInput from langflow.components.models.OpenAIModel import OpenAIModelComponent @@ -7,9 +9,16 @@ from langflow.components.prompts.Prompt import PromptComponent from langflow.graph import Graph from langflow.graph.graph.constants import Finish +from langflow.graph.graph.schema import GraphDump + +@pytest.fixture +def client(): + pass -def test_memory_chatbot(): + +@pytest.fixture +def memory_chatbot_graph(): session_id = "test_session_id" template = """{context} @@ -32,10 +41,87 @@ def test_memory_chatbot(): chat_output.set(input_value=openai_component.text_response) graph = Graph(chat_input, chat_output) + return graph + + +def test_memory_chatbot(memory_chatbot_graph): # Now we run step by step expected_order = deque(["chat_input", "chat_memory", "prompt", "openai", "chat_output"]) for step in expected_order: - result = graph.step() + result = memory_chatbot_graph.step() if isinstance(result, Finish): break assert step == result.vertex.id + + +def test_memory_chatbot_dump_structure(memory_chatbot_graph: Graph): + # Now we run step by step + graph_dict = memory_chatbot_graph.dump( + name="Memory Chatbot", description="A memory chatbot", endpoint_name="membot" + ) + assert isinstance(graph_dict, dict) + # Test structure + assert "data" in graph_dict + assert "is_component" in graph_dict + + data_dict = graph_dict["data"] + assert "nodes" in data_dict + assert "edges" in data_dict + assert "description" in graph_dict + assert "endpoint_name" in graph_dict + + # Test data + nodes = data_dict["nodes"] + edges = data_dict["edges"] + description = graph_dict["description"] + endpoint_name = graph_dict["endpoint_name"] + + assert len(nodes) == 5 + assert len(edges) == 4 + assert description is not None + assert endpoint_name is not None + + +def test_memory_chatbot_dump_components_and_edges(memory_chatbot_graph: Graph): + # Check all components and edges were dumped correctly + graph_dict: GraphDump = memory_chatbot_graph.dump( + name="Memory Chatbot", description="A memory chatbot", endpoint_name="membot" + ) + + data_dict = graph_dict["data"] + nodes = data_dict["nodes"] + edges = data_dict["edges"] + + # sort the nodes by id + nodes = sorted(nodes, key=lambda x: x["id"]) + + # Check each node + assert nodes[0]["data"]["type"] == "ChatInput" + assert nodes[0]["id"] == "chat_input" + + assert nodes[1]["data"]["type"] == "MemoryComponent" + assert nodes[1]["id"] == "chat_memory" + + assert nodes[2]["data"]["type"] == "ChatOutput" + assert nodes[2]["id"] == "chat_output" + + assert nodes[3]["data"]["type"] == "OpenAIModelComponent" + assert nodes[3]["id"] == "openai" + + assert nodes[4]["data"]["type"] == "PromptComponent" + assert nodes[4]["id"] == "prompt" + + # Check edges + expected_edges = [ + ("chat_input", "prompt"), + ("chat_memory", "prompt"), + ("prompt", "openai"), + ("openai", "chat_output"), + ] + + assert len(edges) == len(expected_edges) + + for edge in edges: + source = edge["source"] + target = edge["target"] + assert (source, target) in expected_edges, edge From 78ff6547b62448b5c49023a03446120a7979d8af Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:59:41 -0300 Subject: [PATCH 19/88] Refactor: Remove unused methods in ComponentVertex class to streamline code and improve readability --- src/backend/base/langflow/graph/vertex/types.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index aab27ff87e1..b374d15989e 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -69,15 +69,7 @@ def _update_built_object_and_artifacts(self, result): for key, value in self._built_object.items(): self.add_result(key, value) - def get_input(self, name: str) -> InputTypes: - if self._custom_component is None: - raise ValueError(f"Vertex {self.id} does not have a component instance.") - return self._custom_component.get_input(name) - - def get_output(self, name: str) -> Output: - if self._custom_component is None: - raise ValueError(f"Vertex {self.id} does not have a component instance.") - return self._custom_component.get_output(name) +x def get_edge_with_target(self, target_id: str) -> Generator["ContractEdge", None, None]: """ From cc3fc126d5b9d82c06a72bba559d8d6a79bddf62 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:00:06 -0300 Subject: [PATCH 20/88] Refactor: Remove unnecessary line in ComponentVertex class to enhance code clarity and maintainability --- src/backend/base/langflow/graph/vertex/types.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index b374d15989e..4c033227a3e 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -69,8 +69,6 @@ def _update_built_object_and_artifacts(self, result): for key, value in self._built_object.items(): self.add_result(key, value) -x - def get_edge_with_target(self, target_id: str) -> Generator["ContractEdge", None, None]: """ Get the edge with the target id. From 8d0140f09a7874aaf7e00ee649756a045cb21024 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:11:37 -0300 Subject: [PATCH 21/88] refactor: Add utility functions for getting handle IDs in CustomNodes - Added `getRightHandleId` function to generate the right handle ID for source handles. - Added `getLeftHandleId` function to generate the left handle ID for target handles. - These functions improve code readability and maintainability by encapsulating the logic for generating handle IDs. --- .../src/CustomNodes/utils/get-handle-id.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/frontend/src/CustomNodes/utils/get-handle-id.tsx diff --git a/src/frontend/src/CustomNodes/utils/get-handle-id.tsx b/src/frontend/src/CustomNodes/utils/get-handle-id.tsx new file mode 100644 index 00000000000..934efe2f248 --- /dev/null +++ b/src/frontend/src/CustomNodes/utils/get-handle-id.tsx @@ -0,0 +1,30 @@ +import { sourceHandleType, targetHandleType } from "@/types/flow"; +import { scapedJSONStringfy } from "@/utils/reactflowUtils"; + +export function getRightHandleId({ + output_types, + id, + dataType, + name, +}: sourceHandleType): string { + return scapedJSONStringfy({ + dataType, + id, + output_types, + name, + }); +} + +export function getLeftHandleId({ + inputTypes, + type, + fieldName, + id, +}: targetHandleType): string { + return scapedJSONStringfy({ + inputTypes, + id, + type, + fieldName, + }); +} From 5f763d569b1268c934a07bed2f086178d2678f48 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:12:00 -0300 Subject: [PATCH 22/88] refactor: Add type for escaped handle IDs in edges to improve type safety in reactflowUtils --- src/frontend/src/types/utils/reactflowUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/src/types/utils/reactflowUtils.ts b/src/frontend/src/types/utils/reactflowUtils.ts index fda6b75a83b..1eb2430a89e 100644 --- a/src/frontend/src/types/utils/reactflowUtils.ts +++ b/src/frontend/src/types/utils/reactflowUtils.ts @@ -6,6 +6,10 @@ export type unselectAllNodesType = { data: Node[]; }; +export type addEscapedHandleIdsToEdgesType = { + edges: Edge[]; +}; + export type updateEdgesHandleIdsType = { nodes: NodeType[]; edges: Edge[]; From 26961a071b22d3e0a660e2023e287088b028ac13 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:12:24 -0300 Subject: [PATCH 23/88] feat: Add function to escape handle IDs in edges, enhancing edge management in reactflowUtils --- src/frontend/src/utils/reactflowUtils.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 3c2089f1068..f0ce281f72f 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -475,6 +475,26 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) { return newName; } +export function addEscapedHandleIdsToEdges({ + edges, +}: addEscapedHandleIdsToEdgesType): Edge[] { + let newEdges = cloneDeep(edges); + newEdges.forEach((edge) => { + let escapedSourceHandle = edge.sourceHandle; + let escapedTargetHandle = edge.targetHandle; + if (!escapedSourceHandle) { + let sourceHandle = edge.data.sourceHandle; + escapedSourceHandle = getRightHandleId(sourceHandle); + edge.sourceHandle = escapedSourceHandle; + } + if (!escapedTargetHandle) { + let targetHandle = edge.data.targetHandle; + escapedTargetHandle = getLeftHandleId(targetHandle); + edge.targetHandle = escapedTargetHandle; + } + }); + return newEdges; +} export function updateEdgesHandleIds({ edges, nodes, From d5d0ea1307994a99256eaeffe3cef50e692047d9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:12:36 -0300 Subject: [PATCH 24/88] feat: Add function to check edges without escaped handle IDs, improving edge validation in reactflowUtils --- src/frontend/src/utils/reactflowUtils.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index f0ce281f72f..6f186676ee2 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -760,6 +760,13 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean { ); } +export function checkEdgeWithoutEscapedHandleIds(edges: Edge[]): boolean { + return edges.some( + (edge) => + (!edge.sourceHandle || !edge.targetHandle) && edge.data.sourceHandle, + ); +} + export function checkOldNodesOutput(nodes: NodeType[]): boolean { return nodes.some((node) => !node.data.node?.outputs); } From 2b264da6f31c9342e0199321c37c52b2ef7eb024 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:14:02 -0300 Subject: [PATCH 25/88] feat: Enhance edge processing in reactflowUtils to handle edges without escaped handle IDs more effectively --- src/frontend/src/utils/reactflowUtils.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 6f186676ee2..840b2fea294 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -1,3 +1,7 @@ +import { + getLeftHandleId, + getRightHandleId, +} from "@/CustomNodes/utils/get-handle-id"; import { cloneDeep } from "lodash"; import { Connection, @@ -34,6 +38,7 @@ import { targetHandleType, } from "../types/flow"; import { + addEscapedHandleIdsToEdgesType, findLastNodeType, generateFlowType, unselectAllNodesType, @@ -1258,7 +1263,10 @@ export function updateEdgesIds( export function processFlowEdges(flow: FlowType) { if (!flow.data || !flow.data.edges) return; - if (checkOldEdgesHandles(flow.data.edges)) { + if (checkEdgeWithoutEscapedHandleIds(flow.data.edges)) { + const newEdges = addEscapedHandleIdsToEdges({ edges: flow.data.edges }); + flow.data.edges = newEdges; + } else if (checkOldEdgesHandles(flow.data.edges)) { const newEdges = updateEdgesHandleIds(flow.data); flow.data.edges = newEdges; } From 0590d26bd444ba6621d960c7707616aa8bed57ac Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:20:42 -0300 Subject: [PATCH 26/88] feat: Add layoutUtils module for handling node layout using elkjs --- src/frontend/src/utils/layoutUtils.ts | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/frontend/src/utils/layoutUtils.ts diff --git a/src/frontend/src/utils/layoutUtils.ts b/src/frontend/src/utils/layoutUtils.ts new file mode 100644 index 00000000000..4e256376903 --- /dev/null +++ b/src/frontend/src/utils/layoutUtils.ts @@ -0,0 +1,75 @@ +import { NodeType } from "@/types/flow"; +import ELK, { ElkNode } from "elkjs/lib/elk.bundled.js"; +import { cloneDeep } from "lodash"; +import { Edge } from "reactflow"; + +const layoutOptions = { + "elk.algorithm": "layered", + "elk.direction": "RIGHT", + "elk.layered.spacing.edgeNodeBetweenLayers": "40", + "elk.spacing.nodeNode": "40", + "elk.layered.nodePlacement.strategy": "SIMPLE", +}; +const elk = new ELK(); + +// uses elkjs to give each node a layouted position +export const getLayoutedNodes = async (nodes: NodeType[], edges: Edge[]) => { + const graph = { + id: "root", + layoutOptions, + children: cloneDeep(nodes).map((n) => { + const targetPorts = edges + .filter((e) => e.source === n.id) + .map((e) => ({ + id: e.sourceHandle, + properties: { + side: "EAST", + }, + })); + + const sourcePorts = edges + .filter((e) => e.target === n.id) + .map((e) => ({ + id: e.targetHandle, + properties: { + side: "WEST", + }, + })); + const width = 384; + return { + id: n.id, + width: width, + height: width * 3, + // ⚠️ we need to tell elk that the ports are fixed, in order to reduce edge crossings + properties: { + "org.eclipse.elk.portConstraints": "FIXED_ORDER", + }, + // we are also passing the id, so we can also handle edges without a sourceHandle or targetHandle option + ports: [{ id: n.id }, ...targetPorts, ...sourcePorts], + }; + }) as ElkNode[], + edges: edges.map((e) => ({ + id: e.id, + sources: [e.sourceHandle || e.source], + targets: [e.targetHandle || e.target], + })), + }; + + const layoutedGraph = await elk.layout(graph); + + const layoutedNodes = nodes.map((node) => { + const layoutedNode = layoutedGraph.children?.find( + (lgNode) => lgNode.id === node.id, + ); + + return { + ...node, + position: { + x: layoutedNode?.x ?? 0, + y: layoutedNode?.y ?? 0, + }, + }; + }); + + return layoutedNodes; +}; From 711f25818c9942d36e63625cf76c4402d508c8dc Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:22:22 -0300 Subject: [PATCH 27/88] feat: update processDataFromFlow to add layout to nodes if needed --- src/frontend/src/stores/flowsManagerStore.ts | 2 +- src/frontend/src/utils/reactflowUtils.ts | 24 ++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 04c32444ff9..99aae003156 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -216,7 +216,7 @@ const useFlowsManagerStore = create((set, get) => ({ fromDragAndDrop?: boolean, ): Promise => { let flowData = flow - ? processDataFromFlow(flow) + ? await processDataFromFlow(flow) : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } }; flowData?.nodes.forEach((node) => { updateGroupRecursion( diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 840b2fea294..8ea6a21bc6b 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -44,6 +44,7 @@ import { unselectAllNodesType, updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; +import { getLayoutedNodes } from "./layoutUtils"; import { createRandomKey, toTitleCase } from "./utils"; const uid = new ShortUniqueId(); @@ -279,7 +280,7 @@ export function updateTemplate( export const processFlows = (DbData: FlowType[], skipUpdate = true) => { let savedComponents: { [key: string]: APIClassType } = {}; - DbData.forEach((flow: FlowType) => { + DbData.forEach(async (flow: FlowType) => { try { if (!flow.data) { return; @@ -295,7 +296,9 @@ export const processFlows = (DbData: FlowType[], skipUpdate = true) => { ] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!); return; } - processDataFromFlow(flow, !skipUpdate); + await processDataFromFlow(flow, !skipUpdate).catch((e) => { + console.log(e); + }); } catch (e) { console.log(e); } @@ -303,7 +306,14 @@ export const processFlows = (DbData: FlowType[], skipUpdate = true) => { return { data: savedComponents, flows: DbData }; }; -export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { +const needsLayout = (nodes: NodeType[]) => { + return nodes.some((node) => !node.position); +}; + +export async function processDataFromFlow( + flow: FlowType, + refreshIds = true, +): Promise { let data = flow?.data ? flow.data : null; if (data) { processFlowEdges(flow); @@ -313,9 +323,14 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { updateEdges(data.edges); // updateNodes(data.nodes, data.edges); if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere + // add layout to nodes if not present + if (needsLayout(data.nodes)) { + const layoutedNodes = await getLayoutedNodes(data.nodes, data.edges); + data.nodes = layoutedNodes; + } } return data; -}; +} export function updateIds( { edges, nodes }: { edges: Edge[]; nodes: Node[] }, @@ -346,6 +361,7 @@ export function updateIds( concatedEdges.forEach((edge: Edge) => { edge.source = idsMap[edge.source]; edge.target = idsMap[edge.target]; + const sourceHandleObject: sourceHandleType = scapeJSONParse( edge.sourceHandle!, ); From d21843eb7f1677a455d3adecb9e8370cc3cbce27 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:26:57 -0300 Subject: [PATCH 28/88] feat: Update flowsManagerStore to parse flow data from file before processing - Replace usages of `fileData` with `parsedFlowData` for improved clarity - Ensure compatibility with newProject and isComponent parameters - Improve error handling for uploading components as flows or vice versa - Refactor code for better readability and maintainability --- src/frontend/src/stores/flowsManagerStore.ts | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 99aae003156..c49692be116 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -396,25 +396,25 @@ const useFlowsManagerStore = create((set, get) => ({ let id; if (file) { let text = await file.text(); - let fileData = JSON.parse(text); + let parsedFlowData = await JSON.parse(text); if ( newProject && isComponent !== null && - ((!fileData.is_component && isComponent === true) || - (fileData.is_component !== undefined && - fileData.is_component !== isComponent)) + ((!parsedFlowData.is_component && isComponent === true) || + (parsedFlowData.is_component !== undefined && + parsedFlowData.is_component !== isComponent)) ) { reject("You cannot upload a component as a flow or vice versa"); } else { - if (fileData.flows) { - fileData.flows.forEach((flow: FlowType) => { + if (parsedFlowData.flows) { + parsedFlowData.flows.forEach((flow: FlowType) => { id = get().addFlow(newProject, flow, undefined, position); }); resolve(""); } else { id = await get().addFlow( newProject, - fileData, + parsedFlowData, undefined, position, true, @@ -434,16 +434,16 @@ const useFlowsManagerStore = create((set, get) => ({ ) { const currentfile = (e.target as HTMLInputElement).files![0]; let text = await currentfile.text(); - let fileData: FlowType = await JSON.parse(text); + let parsedFlowData: FlowType = await JSON.parse(text); if ( - (!fileData.is_component && isComponent === true) || - (fileData.is_component !== undefined && - fileData.is_component !== isComponent) + (!parsedFlowData.is_component && isComponent === true) || + (parsedFlowData.is_component !== undefined && + parsedFlowData.is_component !== isComponent) ) { reject("You cannot upload a component as a flow or vice versa"); } else { - id = await get().addFlow(newProject, fileData); + id = await get().addFlow(newProject, parsedFlowData); resolve(id); } } From c9a6c3448a82b75140445a3b0248c9ec9c7d1c83 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:28:30 -0300 Subject: [PATCH 29/88] Refactor import paths to use 'initialize' module in 'base.py' --- src/backend/base/langflow/graph/vertex/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index b47dcf86e7c..d7ffc875dfc 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -14,7 +14,7 @@ from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.utils import UnbuiltObject, UnbuiltResult, log_transaction from langflow.graph.vertex.schema import NodeData -from langflow.interface.initialize import loading +from langflow.interface import initialize from langflow.interface.listing import lazy_load_dict from langflow.schema.artifact import ArtifactType from langflow.schema.data import Data @@ -451,10 +451,10 @@ async def _build( raise ValueError(f"Base type for vertex {self.display_name} not found") if not self._custom_component: - custom_component, custom_params = await loading.instantiate_class(user_id=user_id, vertex=self) + custom_component, custom_params = await initialize.loading.instantiate_class(user_id=user_id, vertex=self) else: custom_component = self._custom_component - custom_params = loading.get_params(self.params) + custom_params = initialize.loading.get_params(self.params) await self._build_results(custom_component, custom_params, fallback_to_env_vars) @@ -669,7 +669,7 @@ def _extend_params_list_with_result(self, key, result): async def _build_results(self, custom_component, custom_params, fallback_to_env_vars=False): try: - result = await loading.get_instance_results( + result = await initialize.loading.get_instance_results( custom_component=custom_component, custom_params=custom_params, vertex=self, From db0a021affc437737fc9964dcf843a98dce46fce Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:30:42 -0300 Subject: [PATCH 30/88] feat: Add method to set class source code and integrate it with frontend node input field --- .../custom/custom_component/component.py | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 7c495e82374..09477986117 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -6,9 +6,7 @@ import yaml from pydantic import BaseModel -from langflow.graph.edge.schema import EdgeData from langflow.helpers.custom import format_type -from langflow.inputs.inputs import InputTypes from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.data import Data from langflow.schema.message import Message @@ -20,13 +18,15 @@ from .custom_component import CustomComponent if TYPE_CHECKING: + from langflow.graph.edge.schema import EdgeData from langflow.graph.vertex.base import Vertex + from langflow.inputs.inputs import InputTypes BACKWARDS_COMPATIBLE_ATTRIBUTES = ["user_id", "vertex", "tracing_service"] class Component(CustomComponent): - inputs: List[InputTypes] = [] + inputs: List["InputTypes"] = [] outputs: List[Output] = [] code_class_base_inheritance: ClassVar[str] = "Component" _output_logs: dict[str, Log] = {} @@ -41,7 +41,7 @@ def __init__(self, **kwargs): config[key] = value else: inputs[key] = value - self._inputs: dict[str, InputTypes] = {} + self._inputs: dict[str, "InputTypes"] = {} self._outputs: dict[str, Output] = {} self._results: dict[str, Any] = {} self._attributes: dict[str, Any] = {} @@ -64,6 +64,20 @@ def __init__(self, **kwargs): self.map_outputs(self.outputs) # Set output types self._set_output_types() + self.set_class_code() + + def set_class_code(self): + # Get the source code of the calling class + if self._code: + return + try: + module = inspect.getmodule(self.__class__) + if module is None: + raise ValueError("Could not find module for class") + class_code = inspect.getsource(module) + self._code = class_code + except OSError: + raise ValueError(f"Could not find source code for {self.__class__.__name__}") def set(self, **kwargs): """ @@ -366,6 +380,21 @@ def to_frontend_node(self): self._map_parameters_on_template(frontend_node_dict["template"]) frontend_node = ComponentFrontendNode.from_dict(frontend_node_dict) + if not self._code: + self.set_class_code() + code_field = Input( + dynamic=True, + required=True, + placeholder="", + multiline=True, + value=self._code, + password=False, + name="code", + advanced=True, + field_type="code", + is_list=False, + ) + frontend_node.template.add_field(code_field) for output in frontend_node.outputs: if output.types: From d0884218555bf6e3c4c0fbe09d888cc5f3fd9bbb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:31:57 -0300 Subject: [PATCH 31/88] refactor: Update sourceHandle dataType to use custom component class name --- src/backend/base/langflow/graph/graph/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 393f657315e..0e9164c15e8 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -187,7 +187,7 @@ def add_component_edge(self, source_id: str, output_input_tuple: Tuple[str, str] "target": target_id, "data": { "sourceHandle": { - "dataType": source_vertex.base_name, + "dataType": source_vertex._custom_component.__class__.__name__, "id": source_vertex.id, "name": output_name, "output_types": source_vertex.get_output(output_name).types, From 685cc8c3f347d368d690862ce9231510fba6f459 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:34:18 -0300 Subject: [PATCH 32/88] fix: Raise error for unknown vertex types instead of returning default Vertex class --- src/backend/base/langflow/graph/graph/base.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 0e9164c15e8..958661ca0c2 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1416,11 +1416,7 @@ def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) - if node_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP: return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_type] - return ( - lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_base_type] - if node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP - else Vertex - ) + raise ValueError(f"Vertex type {node_type} not found") def _build_vertices(self) -> List[Vertex]: """Builds the vertices of the graph.""" From e2162874d83d0f4c2cd0e26377c21982da1bf45a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:34:36 -0300 Subject: [PATCH 33/88] refactor: Remove redundant call to _import_vertex_types() in VertexTypesDict initialization --- src/backend/base/langflow/graph/graph/constants.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/constants.py b/src/backend/base/langflow/graph/graph/constants.py index 740a7e44321..51da1182a0c 100644 --- a/src/backend/base/langflow/graph/graph/constants.py +++ b/src/backend/base/langflow/graph/graph/constants.py @@ -19,7 +19,7 @@ def _import_vertex_types(): class VertexTypesDict(LazyLoadDictBase): def __init__(self): self._all_types_dict = None - self._types = _import_vertex_types() + self._types = _import_vertex_types @property def VERTEX_TYPE_MAP(self): @@ -33,14 +33,15 @@ def _build_dict(self): } def get_type_dict(self): + types = self._types() return { - **{t: self._types.CustomComponentVertex for t in ["CustomComponent"]}, - **{t: self._types.ComponentVertex for t in ["Component"]}, - **{t: self._types.InterfaceVertex for t in CHAT_COMPONENTS}, + **{t: types.CustomComponentVertex for t in ["CustomComponent"]}, + **{t: types.ComponentVertex for t in ["Component"]}, + **{t: types.InterfaceVertex for t in CHAT_COMPONENTS}, } def get_custom_component_vertex_type(self): - return self._types.CustomComponentVertex + return self._types().CustomComponentVertex lazy_load_vertex_dict = VertexTypesDict() From afc9a3f32bf6622c7d3083f207c8bd419b33c82d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:35:57 -0300 Subject: [PATCH 34/88] refactor: Simplify add_code_field by removing unnecessary field_config parameter from function signature --- src/backend/base/langflow/custom/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index 9fdbc789ed6..c92d208c48d 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -283,7 +283,7 @@ def get_component_instance(custom_component: CustomComponent, user_id: Optional[ ) from exc try: - custom_instance = custom_class(_user_id=user_id) + custom_instance = custom_class(_user_id=user_id, _code=custom_component._code) return custom_instance except Exception as exc: logger.error(f"Error while instantiating custom component: {str(exc)}") @@ -339,7 +339,7 @@ def run_build_config( raise exc -def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code, field_config): +def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code): code_field = Input( dynamic=True, required=True, @@ -364,7 +364,7 @@ def build_custom_component_template_from_inputs( cc_instance = get_component_instance(custom_component, user_id=user_id) field_config = cc_instance.get_template_config(cc_instance) frontend_node = ComponentFrontendNode.from_inputs(**field_config) - frontend_node = add_code_field(frontend_node, custom_component._code, field_config.get("code", {})) + frontend_node = add_code_field(frontend_node, custom_component._code) # But we now need to calculate the return_type of the methods in the outputs for output in frontend_node.outputs: if output.types: @@ -408,7 +408,7 @@ def build_custom_component_template( add_extra_fields(frontend_node, field_config, entrypoint_args) - frontend_node = add_code_field(frontend_node, custom_component._code, field_config.get("code", {})) + frontend_node = add_code_field(frontend_node, custom_component._code) add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type) add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type) From bb8802718987143c6aeef7aa4679ceb89c26bdce Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 16:36:23 -0300 Subject: [PATCH 35/88] feat: Add elkjs dependency to package.json and package-lock.json for enhanced functionality in the frontend --- src/frontend/package-lock.json | 6 ++++++ src/frontend/package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 231dc6f2f87..db0e15228cd 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -45,6 +45,7 @@ "cmdk": "^1.0.0", "dompurify": "^3.1.5", "dotenv": "^16.4.5", + "elkjs": "^0.9.3", "emoji-regex": "^10.3.0", "esbuild": "^0.21.5", "file-saver": "^2.0.5", @@ -7424,6 +7425,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.806.tgz", "integrity": "sha512-nkoEX2QIB8kwCOtvtgwhXWy2IHVcOLQZu9Qo36uaGB835mdX/h8uLRlosL6QIhLVUnAiicXRW00PwaPZC74Nrg==" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" + }, "node_modules/emoji-regex": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index baf6af68f21..1b179f2b9fc 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -40,6 +40,7 @@ "cmdk": "^1.0.0", "dompurify": "^3.1.5", "dotenv": "^16.4.5", + "elkjs": "^0.9.3", "emoji-regex": "^10.3.0", "esbuild": "^0.21.5", "file-saver": "^2.0.5", From 80a2b05c21926c083d49ea2d222951f274ee1225 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:04:04 -0300 Subject: [PATCH 36/88] refactor: Update fields type in Template class to use InputTypes for improved type safety --- src/backend/base/langflow/template/template/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/template/base.py b/src/backend/base/langflow/template/template/base.py index d0372b0b654..97142cadb28 100644 --- a/src/backend/base/langflow/template/template/base.py +++ b/src/backend/base/langflow/template/template/base.py @@ -9,7 +9,7 @@ class Template(BaseModel): type_name: str = Field(serialization_alias="_type") - fields: list[Union[Input, InputTypes]] + fields: list[InputTypes] def process_fields( self, From a7820f3b4b9e5f03a7cfa16463b75c2dc1b9a1a4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:04:22 -0300 Subject: [PATCH 37/88] refactor: Update import path for DefaultPromptField to improve code organization and maintain compatibility --- src/backend/base/langflow/template/field/prompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/field/prompt.py b/src/backend/base/langflow/template/field/prompt.py index 5ad43946ebb..aed416cbca0 100644 --- a/src/backend/base/langflow/template/field/prompt.py +++ b/src/backend/base/langflow/template/field/prompt.py @@ -1,3 +1,3 @@ # This file is for backwards compatibility from langflow.inputs.inputs import DEFAULT_PROMPT_INTUT_TYPES # noqa -from langflow.inputs import DefaultPromptField # noqa +from langflow.inputs.inputs import DefaultPromptField # noqa From ac270308d6a78dd644bcdc5f415f4a693f7c3aa5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:05:13 -0300 Subject: [PATCH 38/88] refactor: Reorganize imports in __init__.py for better structure and consistency across the inputs module --- src/backend/base/langflow/inputs/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/inputs/__init__.py b/src/backend/base/langflow/inputs/__init__.py index 00842931cfe..a68597da967 100644 --- a/src/backend/base/langflow/inputs/__init__.py +++ b/src/backend/base/langflow/inputs/__init__.py @@ -1,23 +1,24 @@ from .inputs import ( BoolInput, DataInput, + DefaultPromptField, DictInput, DropdownInput, - MultiselectInput, FileInput, FloatInput, HandleInput, + Input, IntInput, MessageInput, MessageTextInput, MultilineInput, MultilineSecretInput, + MultiselectInput, NestedDictInput, PromptInput, SecretStrInput, StrInput, TableInput, - DefaultPromptField, ) __all__ = [ @@ -39,5 +40,6 @@ "StrInput", "MessageTextInput", "TableInput", + "Input", "DefaultPromptField", ] From dff6f5e9aa25031566780c7d383f45026f0a8805 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:05:30 -0300 Subject: [PATCH 39/88] refactor: Clean up imports in types.py for better organization and consistency in the graph vertex module --- src/backend/base/langflow/graph/vertex/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index 4c033227a3e..3ee3a06991c 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -7,7 +7,7 @@ from loguru import logger from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData -from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, serialize_field +from langflow.graph.utils import UnbuiltObject, log_transaction, serialize_field from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.schema import NodeData from langflow.inputs.inputs import InputTypes @@ -15,6 +15,7 @@ from langflow.schema.artifact import ArtifactType from langflow.schema.message import Message from langflow.schema.schema import INPUT_FIELD_NAME +from langflow.graph.utils import log_vertex_build from langflow.template.field.base import UNDEFINED, Output from langflow.utils.schemas import ChatOutputResponse, DataOutputResponse from langflow.utils.util import unescape_string From cf5d7198f8e43d0b693c78b0df8ddd2fe5976a4c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:06:05 -0300 Subject: [PATCH 40/88] refactor: Change vertex type annotations to strings for better compatibility and consistency in the graph module methods --- src/backend/base/langflow/graph/graph/base.py | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 958661ca0c2..9c22b062a4a 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -676,7 +676,7 @@ def metadata(self): "flow_name": self.flow_name, } - def build_graph_maps(self, edges: Optional[List[ContractEdge]] = None, vertices: Optional[List[Vertex]] = None): + def build_graph_maps(self, edges: Optional[List[ContractEdge]] = None, vertices: Optional[List["Vertex"]] = None): """ Builds the adjacency maps for the graph. """ @@ -738,7 +738,7 @@ def get_edge(self, source_id: str, target_id: str) -> Optional[ContractEdge]: return edge return None - def build_parent_child_map(self, vertices: List[Vertex]): + def build_parent_child_map(self, vertices: List["Vertex"]): parent_child_map = defaultdict(list) for vertex in vertices: parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)] @@ -834,7 +834,7 @@ def __eq__(self, other: object) -> bool: # both graphs have the same vertices and edges # but the data of the vertices might be different - def update_edges_from_vertex(self, vertex: Vertex, other_vertex: Vertex) -> None: + def update_edges_from_vertex(self, vertex: "Vertex", other_vertex: "Vertex") -> None: """Updates the edges of a vertex in the Graph.""" new_edges = [] for edge in self.edges: @@ -844,13 +844,13 @@ def update_edges_from_vertex(self, vertex: Vertex, other_vertex: Vertex) -> None new_edges += other_vertex.edges self.edges = new_edges - def vertex_data_is_identical(self, vertex: Vertex, other_vertex: Vertex) -> bool: + def vertex_data_is_identical(self, vertex: "Vertex", other_vertex: "Vertex") -> bool: data_is_equivalent = vertex == other_vertex if not data_is_equivalent: return False return self.vertex_edges_are_identical(vertex, other_vertex) - def vertex_edges_are_identical(self, vertex: Vertex, other_vertex: Vertex) -> bool: + def vertex_edges_are_identical(self, vertex: "Vertex", other_vertex: "Vertex") -> bool: same_length = len(vertex.edges) == len(other_vertex.edges) if not same_length: return False @@ -909,7 +909,7 @@ def update(self, other: "Graph") -> "Graph": self.increment_update_count() return self - def update_vertex_from_another(self, vertex: Vertex, other_vertex: Vertex) -> None: + def update_vertex_from_another(self, vertex: "Vertex", other_vertex: "Vertex") -> None: """ Updates a vertex from another vertex. @@ -947,12 +947,12 @@ def _add_vertex(self, vertex: Vertex) -> None: self.vertices.append(vertex) self.vertex_map[vertex.id] = vertex - def add_vertex(self, vertex: Vertex) -> None: + def add_vertex(self, vertex: "Vertex") -> None: """Adds a new vertex to the graph.""" self._add_vertex(vertex) self._update_edges(vertex) - def _update_edges(self, vertex: Vertex) -> None: + def _update_edges(self, vertex: "Vertex") -> None: """Updates the edges of a vertex.""" # Vertex has edges, so we need to update the edges for edge in vertex.edges: @@ -987,19 +987,19 @@ def _build_vertex_params(self) -> None: for vertex in self.vertices: vertex._build_params() - def _validate_vertex(self, vertex: Vertex) -> bool: + def _validate_vertex(self, vertex: "Vertex") -> bool: """Validates a vertex.""" # All vertices that do not have edges are invalid return len(self.get_vertex_edges(vertex.id)) > 0 - def get_vertex(self, vertex_id: str, silent: bool = False) -> Vertex: + def get_vertex(self, vertex_id: str, silent: bool = False) -> "Vertex": """Returns a vertex by id.""" try: return self.vertex_map[vertex_id] except KeyError: raise ValueError(f"Vertex {vertex_id} not found") - def get_root_of_group_node(self, vertex_id: str) -> Vertex: + def get_root_of_group_node(self, vertex_id: str) -> "Vertex": """Returns the root of a group node.""" if vertex_id in self.top_level_vertices: # Get all vertices with vertex_id as .parent_node_id @@ -1157,9 +1157,9 @@ def get_vertex_edges( or (edge.target_id == vertex_id and is_target is not False) ] - def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]: + def get_vertices_with_target(self, vertex_id: str) -> List["Vertex"]: """Returns the vertices connected to a vertex.""" - vertices: List[Vertex] = [] + vertices: List["Vertex"] = [] for edge in self.edges: if edge.target_id == vertex_id: vertex = self.get_vertex(edge.source_id) @@ -1246,7 +1246,7 @@ async def _execute_tasks(self, tasks: List[asyncio.Task], lock: asyncio.Lock) -> """Executes tasks in parallel, handling exceptions for each task.""" results = [] completed_tasks = await asyncio.gather(*tasks, return_exceptions=True) - vertices: List[Vertex] = [] + vertices: List["Vertex"] = [] for i, result in enumerate(completed_tasks): task_name = tasks[i].get_name() @@ -1273,7 +1273,7 @@ async def _execute_tasks(self, tasks: List[asyncio.Task], lock: asyncio.Lock) -> no_duplicate_results = list(set(results)) return no_duplicate_results - def topological_sort(self) -> List[Vertex]: + def topological_sort(self) -> List["Vertex"]: """ Performs a topological sort of the vertices in the graph. @@ -1306,7 +1306,7 @@ def dfs(vertex): return list(reversed(sorted_vertices)) - def generator_build(self) -> Generator[Vertex, None, None]: + def generator_build(self) -> Generator["Vertex", None, None]: """Builds each vertex in the graph and yields it.""" sorted_vertices = self.topological_sort() logger.debug("There are %s vertices in the graph", len(sorted_vertices)) @@ -1316,7 +1316,7 @@ def get_predecessors(self, vertex): """Returns the predecessors of a vertex.""" return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])] - def get_all_successors(self, vertex: Vertex, recursive=True, flat=True): + def get_all_successors(self, vertex: "Vertex", recursive=True, flat=True): # Recursively get the successors of the current vertex # successors = vertex.successors # if not successors: @@ -1353,13 +1353,13 @@ def get_all_successors(self, vertex: Vertex, recursive=True, flat=True): successors_result.append([successor]) return successors_result - def get_successors(self, vertex: Vertex) -> List[Vertex]: + def get_successors(self, vertex: "Vertex") -> List["Vertex"]: """Returns the successors of a vertex.""" return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])] - def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]: + def get_vertex_neighbors(self, vertex: "Vertex") -> Dict["Vertex", int]: """Returns the neighbors of a vertex.""" - neighbors: Dict[Vertex, int] = {} + neighbors: Dict["Vertex", int] = {} for edge in self.edges: if edge.source_id == vertex.id: neighbor = self.get_vertex(edge.target_id) @@ -1401,7 +1401,7 @@ def build_edge(self, edge: EdgeData) -> ContractEdge: new_edge = ContractEdge(source, target, edge) return new_edge - def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]: + def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type["Vertex"]: """Returns the node class based on the node type.""" # First we check for the node_base_type node_name = node_id.split("-")[0] @@ -1418,9 +1418,9 @@ def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) - return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_type] raise ValueError(f"Vertex type {node_type} not found") - def _build_vertices(self) -> List[Vertex]: + def _build_vertices(self) -> List["Vertex"]: """Builds the vertices of the graph.""" - vertices: List[Vertex] = [] + vertices: List["Vertex"] = [] for frontend_data in self._vertices: try: vertex_instance = self.get_vertex(frontend_data["id"]) @@ -1481,7 +1481,7 @@ def __repr__(self): def layered_topological_sort( self, - vertices: List[Vertex], + vertices: List["Vertex"], filter_graphs: bool = False, ) -> List[List[str]]: """Performs a layered topological sort of the vertices in the graph.""" From 5128701fd9580fef6c56ef6c3f90051daef5e342 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:06:47 -0300 Subject: [PATCH 41/88] refactor: Update component instantiation to include _code parameter and fix input type annotations for improved type handling --- .../base/langflow/custom/custom_component/base_component.py | 2 +- src/backend/base/langflow/custom/custom_component/component.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/base_component.py b/src/backend/base/langflow/custom/custom_component/base_component.py index 5cb36ec396c..2403d590a95 100644 --- a/src/backend/base/langflow/custom/custom_component/base_component.py +++ b/src/backend/base/langflow/custom/custom_component/base_component.py @@ -97,7 +97,7 @@ def build_template_config(self) -> dict: return {} cc_class = eval_custom_component_code(self._code) - component_instance = cc_class() + component_instance = cc_class(_code=self._code) template_config = self.get_template_config(component_instance) return template_config diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 09477986117..ea585f1afb8 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -188,7 +188,7 @@ def map_outputs(self, outputs: List[Output]): raise ValueError("Output name cannot be None.") self._outputs[output.name] = output - def map_inputs(self, inputs: List[InputTypes]): + def map_inputs(self, inputs: List["InputTypes"]): """ Maps the given inputs to the component. From fe33a50e85ef8aab66fb2c0f07d2140c76f19b2f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:06:55 -0300 Subject: [PATCH 42/88] refactor: Remove unused CustomComponent import from __init__.py for cleaner module structure and improved organization --- src/backend/base/langflow/custom/custom_component/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/__init__.py b/src/backend/base/langflow/custom/custom_component/__init__.py index 295992f6089..e69de29bb2d 100644 --- a/src/backend/base/langflow/custom/custom_component/__init__.py +++ b/src/backend/base/langflow/custom/custom_component/__init__.py @@ -1,3 +0,0 @@ -from .custom_component import CustomComponent - -__all__ = ["CustomComponent"] From 9aadcc39952767b034eee97c02dfeb428ef6bfc9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:07:08 -0300 Subject: [PATCH 43/88] refactor: Modify custom_component instantiation to include _code argument for enhanced functionality and clarity in CodeParser class --- src/backend/base/langflow/custom/code_parser/code_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/code_parser/code_parser.py b/src/backend/base/langflow/custom/code_parser/code_parser.py index 8fc276efd54..a5d8c559544 100644 --- a/src/backend/base/langflow/custom/code_parser/code_parser.py +++ b/src/backend/base/langflow/custom/code_parser/code_parser.py @@ -384,7 +384,7 @@ def parse_global_vars(self, node: ast.Assign) -> None: def execute_and_inspect_classes(self, code: str): custom_component_class = eval_custom_component_code(code) - custom_component = custom_component_class() + custom_component = custom_component_class(_code=code) dunder_class = custom_component.__class__ # Get the base classes at two levels of inheritance bases = [] From 220a1c184f01a5ce4db19eb291e3c789f54ee21d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:07:18 -0300 Subject: [PATCH 44/88] refactor: Update CustomComponent import in __init__.py for improved module structure and organization --- src/backend/base/langflow/custom/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/__init__.py b/src/backend/base/langflow/custom/__init__.py index c6ce56c3ec6..0ba9d831da8 100644 --- a/src/backend/base/langflow/custom/__init__.py +++ b/src/backend/base/langflow/custom/__init__.py @@ -1,4 +1,4 @@ -from langflow.custom.custom_component import CustomComponent from langflow.custom.custom_component.component import Component +from langflow.custom.custom_component.custom_component import CustomComponent __all__ = ["CustomComponent", "Component"] From beaf5e24f60c756c941f054e1422b556cb4b30eb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:07:32 -0300 Subject: [PATCH 45/88] refactor: Update launch.json to include correct path for backend source files --- .vscode/launch.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index bd9f4733448..1f66413d183 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,6 @@ { "version": "0.2.0", "configurations": [ - { "name": "Debug Backend", "type": "debugpy", @@ -18,7 +17,7 @@ "--loop", "asyncio", "--reload-include", - "src/backend/*" + "./src/backend/*" ], "jinja": true, "justMyCode": false, From 13e1cfbb2c0d53d3dbf364a7d3a7d8bef67509dd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 5 Aug 2024 17:11:21 -0300 Subject: [PATCH 46/88] refactor: Update dependencies in poetry.lock to latest versions and resolve merge conflicts in backend files --- poetry.lock | 8 ++++---- src/backend/base/poetry.lock | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index b71793db042..440c65ca63f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4745,19 +4745,19 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" [[package]] name = "langchain-anthropic" -version = "0.1.20" +version = "0.1.21" description = "An integration package connecting AnthropicMessages and LangChain" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_anthropic-0.1.20-py3-none-any.whl", hash = "sha256:3a0d89ac6856be98beb3ec63813393bf29af3c5134247979c055938e741b7d9d"}, - {file = "langchain_anthropic-0.1.20.tar.gz", hash = "sha256:cb9607fecfc0f0de49b79dd0fc066790e2877873ef753abd98d2ae38d6e0f5b2"}, + {file = "langchain_anthropic-0.1.21-py3-none-any.whl", hash = "sha256:74094162e0badd9f5d275a1e2c4019303fbb45638091c202dcaab5f88fda97aa"}, + {file = "langchain_anthropic-0.1.21.tar.gz", hash = "sha256:04131e024f79c6a60837a4f3b1399b90440da7a9b0c984b92704e66c317f6c5b"}, ] [package.dependencies] anthropic = ">=0.28.0,<1" defusedxml = ">=0.7.1,<0.8.0" -langchain-core = ">=0.2.17,<0.3" +langchain-core = ">=0.2.24,<0.3" [[package]] name = "langchain-astradb" diff --git a/src/backend/base/poetry.lock b/src/backend/base/poetry.lock index f8958eaa1d9..7e9e76bf5a3 100644 --- a/src/backend/base/poetry.lock +++ b/src/backend/base/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiofiles" @@ -860,6 +860,7 @@ wrapt = ">=1.10,<2" dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] +<<<<<<< Updated upstream name = "devtools" version = "0.12.2" description = "Python's missing debug print command, and more." @@ -876,6 +877,8 @@ executing = ">=1.1.1" pygments = ">=2.15.0" [[package]] +======= +>>>>>>> Stashed changes name = "dictdiffer" version = "0.9.0" description = "Dictdiffer is a library that helps you to diff and patch dictionaries." @@ -5256,4 +5259,8 @@ local = [] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" +<<<<<<< Updated upstream content-hash = "877648288f4f9d5d3304c36853d1b2c5f833d673665bf9b1c707c37bbf02e6ad" +======= +content-hash = "bbbc234350488c8293c4b74695db9757be15cabbf6df99ce24d569aecdcd503b" +>>>>>>> Stashed changes From c689330b1bac46bfaa06acfb99f9e7a1b5d14acf Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:36:59 +0000 Subject: [PATCH 47/88] [autofix.ci] apply automated fixes --- .../controllers/API/queries/_builds/use-delete-builds.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/controllers/API/queries/_builds/use-delete-builds.ts b/src/frontend/src/controllers/API/queries/_builds/use-delete-builds.ts index 9ca111171b1..4ab6522e090 100644 --- a/src/frontend/src/controllers/API/queries/_builds/use-delete-builds.ts +++ b/src/frontend/src/controllers/API/queries/_builds/use-delete-builds.ts @@ -8,9 +8,10 @@ interface IDeleteBuilds { } // add types for error handling and success -export const useDeleteBuilds: useMutationFunctionType = ( - options, -) => { +export const useDeleteBuilds: useMutationFunctionType< + undefined, + IDeleteBuilds +> = (options) => { const { mutate } = UseRequestProcessor(); const deleteBuildsFn = async (payload: IDeleteBuilds): Promise => { From 121c9c04b4b4a8dc60912de5bdbc648d316ce5ce Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 11:44:11 -0300 Subject: [PATCH 48/88] refactor: Remove unnecessary line in test_memory_chatbot.py --- .../unit/initial_setup/starter_projects/test_memory_chatbot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py index 83bf6efcb2f..afe24441cef 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py @@ -12,7 +12,6 @@ from langflow.graph.graph.schema import GraphDump - @pytest.fixture def memory_chatbot_graph(): session_id = "test_session_id" From 6e09dc3d434d3fe61b491d2f7ca90a7f7b3fef13 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:14:17 -0300 Subject: [PATCH 49/88] refactor: Update dataType assignment in Component class to use component name if available, or fallback to class name --- .../base/langflow/custom/custom_component/component.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index ea585f1afb8..5a222e8a961 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -286,7 +286,7 @@ def _add_edge(self, component, key, output, _input): "target": self._id, "data": { "sourceHandle": { - "dataType": self.name, + "dataType": component.name or component.__class__.__name__, "id": component._id, "name": output.name, "output_types": output.types, @@ -408,7 +408,7 @@ def to_frontend_node(self): data = { "data": { "node": frontend_node.to_dict(keep_name=False), - "type": self.__class__.__name__, + "type": self.name or self.__class__.__name__, } } return data From a05a7bc33e522b631f25ceb9a0a0cc9db395a7e6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:14:56 -0300 Subject: [PATCH 50/88] refactor: Correct flow_id reference in MemoryComponent to improve clarity and consistency in memory handling --- src/backend/base/langflow/components/helpers/Memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/components/helpers/Memory.py b/src/backend/base/langflow/components/helpers/Memory.py index 25a6d89e3e5..bdde2c2e30f 100644 --- a/src/backend/base/langflow/components/helpers/Memory.py +++ b/src/backend/base/langflow/components/helpers/Memory.py @@ -118,5 +118,5 @@ def build_lc_memory(self) -> BaseChatMemory: if self.memory: chat_memory = self.memory else: - chat_memory = LCBuiltinChatMemory(flow_id=self.graph.flow_id, session_id=self.session_id) + chat_memory = LCBuiltinChatMemory(flow_id=self.flow_id, session_id=self.session_id) return ConversationBufferMemory(chat_memory=chat_memory) From 8e7c9ad9dfdeacf99b78d1d4c49ae7c11f2ef3d9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:15:05 -0300 Subject: [PATCH 51/88] refactor: Update import path for DefaultPromptField to improve code organization and maintainability in api_utils.py --- src/backend/base/langflow/components/helpers/Memory.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/components/helpers/Memory.py b/src/backend/base/langflow/components/helpers/Memory.py index bdde2c2e30f..0173f9cfb8e 100644 --- a/src/backend/base/langflow/components/helpers/Memory.py +++ b/src/backend/base/langflow/components/helpers/Memory.py @@ -1,13 +1,13 @@ +from langchain.memory import ConversationBufferMemory + from langflow.custom import Component +from langflow.field_typing import BaseChatMemory from langflow.helpers.data import data_to_text from langflow.inputs import HandleInput from langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output -from langflow.memory import get_messages, LCBuiltinChatMemory +from langflow.memory import LCBuiltinChatMemory, get_messages from langflow.schema import Data from langflow.schema.message import Message -from langflow.field_typing import BaseChatMemory -from langchain.memory import ConversationBufferMemory - from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER From 3c76f7bca0b178c4759e39ac7b1b95bf846833e0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:15:12 -0300 Subject: [PATCH 52/88] refactor: Add loading module to __init__.py for improved organization of interface package --- src/backend/base/langflow/interface/initialize/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/base/langflow/interface/initialize/__init__.py b/src/backend/base/langflow/interface/initialize/__init__.py index e69de29bb2d..a37cc6bcb44 100644 --- a/src/backend/base/langflow/interface/initialize/__init__.py +++ b/src/backend/base/langflow/interface/initialize/__init__.py @@ -0,0 +1,3 @@ +from . import loading + +__all__ = ["loading"] From 73613e1e709cef5a9a2f598c0b082d05a6af0253 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:25:03 -0300 Subject: [PATCH 53/88] refactor: Clean up imports in base.py and enforce edge validation in Graph class for improved maintainability and error handling --- src/backend/base/langflow/graph/graph/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 9c22b062a4a..6262426c2b5 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -21,7 +21,7 @@ from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.vertex.base import Vertex, VertexStates from langflow.graph.vertex.schema import NodeData -from langflow.graph.vertex.types import ComponentVertex, InterfaceVertex, StateVertex +from langflow.graph.vertex.types import InterfaceVertex, StateVertex from langflow.schema import Data from langflow.schema.schema import INPUT_FIELD_NAME, InputType from langflow.services.cache.utils import CacheMiss @@ -1387,7 +1387,8 @@ def _build_edges(self) -> List[ContractEdge]: for edge in self._edges: new_edge = self.build_edge(edge) edges.add(new_edge) - + if self.vertices and not self.edges: + raise ValueError("Graph has vertices but no edges") return list(edges) def build_edge(self, edge: EdgeData) -> ContractEdge: @@ -1448,6 +1449,7 @@ def prepare(self, stop_component_id: Optional[str] = None, start_component_id: O raise ValueError("You can only provide one of stop_component_id or start_component_id") self.validate_stream() self.edges = self._build_edges() + if stop_component_id or start_component_id: try: first_layer = self.sort_vertices(stop_component_id, start_component_id) From ab8cdec7a5ada1e2ed5636f27c606a2fd9ecf760 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:26:10 -0300 Subject: [PATCH 54/88] refactor: Remove edge component additions in test_base.py to streamline graph tests and emphasize error handling for unprepared graphs --- src/backend/tests/unit/graph/graph/test_base.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index 68ecdff7a67..de7d5ccf13b 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -21,7 +21,6 @@ async def test_graph_not_prepared(): graph = Graph() graph.add_component("chat_input", chat_input) graph.add_component("chat_output", chat_output) - graph.add_component_edge("chat_input", (chat_input.outputs[0].name, chat_input.inputs[0].name), "chat_output") with pytest.raises(ValueError): await graph.astep() @@ -33,16 +32,8 @@ async def test_graph(): graph = Graph() graph.add_component("chat_input", chat_input) graph.add_component("chat_output", chat_output) - graph.add_component_edge("chat_input", (chat_input.outputs[0].name, chat_input.inputs[0].name), "chat_output") - graph.prepare() - assert graph._run_queue == deque(["chat_input"]) - await graph.astep() - assert graph._run_queue == deque(["chat_output"]) - - assert graph.vertices[0].id == "chat_input" - assert graph.vertices[1].id == "chat_output" - assert graph.edges[0].source_id == "chat_input" - assert graph.edges[0].target_id == "chat_output" + with pytest.raises(ValueError, match="Graph has vertices but no edges"): + graph.prepare() @pytest.mark.asyncio From f21ff7651f7ee1be5c8bada0b9aaee0f4264b196 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:26:23 -0300 Subject: [PATCH 55/88] refactor: Mark @clack/prompts is-unicode-supported as extraneous in package-lock.json for better dependency management --- src/frontend/package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index db0e15228cd..f150e6b429d 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -1079,6 +1079,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { From 13a5383ccd9f9aa3be815866801375b29d8c68b2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:28:01 -0300 Subject: [PATCH 56/88] refactor: Update dataType assignment in Component class to use component name if available, or fallback to class name --- src/backend/base/langflow/graph/graph/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 6262426c2b5..9e0d8148bf9 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -21,7 +21,7 @@ from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.vertex.base import Vertex, VertexStates from langflow.graph.vertex.schema import NodeData -from langflow.graph.vertex.types import InterfaceVertex, StateVertex +from langflow.graph.vertex.types import ComponentVertex, InterfaceVertex, StateVertex from langflow.schema import Data from langflow.schema.schema import INPUT_FIELD_NAME, InputType from langflow.services.cache.utils import CacheMiss @@ -187,7 +187,8 @@ def add_component_edge(self, source_id: str, output_input_tuple: Tuple[str, str] "target": target_id, "data": { "sourceHandle": { - "dataType": source_vertex._custom_component.__class__.__name__, + "dataType": source_vertex._custom_component.name + or source_vertex._custom_component.__class__.__name__, "id": source_vertex.id, "name": output_name, "output_types": source_vertex.get_output(output_name).types, From de11978db73617fff3e13b6be07db5f7c6be63d3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:31:48 -0300 Subject: [PATCH 57/88] refactor: Fix edge existence check in Graph class to use correct variable, ensuring accurate validation of graph structure --- src/backend/base/langflow/graph/graph/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 9e0d8148bf9..1452cca0bec 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1388,7 +1388,7 @@ def _build_edges(self) -> List[ContractEdge]: for edge in self._edges: new_edge = self.build_edge(edge) edges.add(new_edge) - if self.vertices and not self.edges: + if self.vertices and not edges: raise ValueError("Graph has vertices but no edges") return list(edges) From 7a0b10e9bf09f07508d03c9a76f370fd50a0301f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 12:32:17 -0300 Subject: [PATCH 58/88] refactor: Add test for graph with edge and improve graph preparation logic --- .../tests/unit/graph/graph/test_base.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index de7d5ccf13b..45be6c609ac 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -36,6 +36,25 @@ async def test_graph(): graph.prepare() +@pytest.mark.asyncio +async def test_graph_with_edge(): + chat_input = ChatInput() + chat_output = ChatOutput() + graph = Graph() + graph.add_component("chat_input", chat_input) + graph.add_component("chat_output", chat_output) + graph.add_component_edge("chat_input", (chat_input.outputs[0].name, chat_input.inputs[0].name), "chat_output") + graph.prepare() + assert graph._run_queue == deque(["chat_input"]) + await graph.astep() + assert graph._run_queue == deque(["chat_output"]) + + assert graph.vertices[0].id == "chat_input" + assert graph.vertices[1].id == "chat_output" + assert graph.edges[0].source_id == "chat_input" + assert graph.edges[0].target_id == "chat_output" + + @pytest.mark.asyncio async def test_graph_functional(): chat_input = ChatInput(_id="chat_input") From c9e46acbcbc38fb67a32158f8d232576f8ac693c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 13:37:02 -0300 Subject: [PATCH 59/88] refactor: Set default node type to "genericNode" in getLayoutedNodes for consistent layout structure --- src/frontend/src/utils/layoutUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/utils/layoutUtils.ts b/src/frontend/src/utils/layoutUtils.ts index 4e256376903..7e175535137 100644 --- a/src/frontend/src/utils/layoutUtils.ts +++ b/src/frontend/src/utils/layoutUtils.ts @@ -68,6 +68,7 @@ export const getLayoutedNodes = async (nodes: NodeType[], edges: Edge[]) => { x: layoutedNode?.x ?? 0, y: layoutedNode?.y ?? 0, }, + type: "genericNode", }; }); From a1aa7cf78e6f5ed0f8e6c06391a42affcc6a72c9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 09:30:00 -0300 Subject: [PATCH 60/88] refactor: Add _call_inputs attribute to Component class This commit adds the _call_inputs attribute to the Component class in the component.py file. The _call_inputs attribute is a dictionary that stores key-value pairs of inputs passed to the component during runtime. This change enhances the flexibility and extensibility of the Component class, allowing for more dynamic behavior based on the inputs provided. --- .../base/langflow/custom/custom_component/component.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 5a222e8a961..c8340a55dea 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -48,6 +48,7 @@ def __init__(self, **kwargs): self._parameters = inputs or {} self._edges: list[EdgeData] = [] self._components: list[Component] = [] + self._call_inputs: dict[str, Any] = {} self.set_attributes(self._parameters) self._output_logs = {} config = config or {} @@ -66,6 +67,9 @@ def __init__(self, **kwargs): self._set_output_types() self.set_class_code() + def _set_call_inputs(self, key: str, value: Any): + self._call_inputs[key] = value + def set_class_code(self): # Get the source code of the calling class if self._code: From f3acfe60f2d810e375edc8b3547d14887cd8a10d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 09:30:37 -0300 Subject: [PATCH 61/88] refactor: Update test_base.py with code generation functions This commit adds code generation functions to the test_base.py file. The functions generate_import_statement, get_variable_name, generate_instantiation_string, generate_call_string, and generate_script are added to facilitate code generation for testing purposes. This change improves the testability and maintainability of the codebase. --- .../tests/unit/graph/graph/test_base.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index 45be6c609ac..163579e91c6 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -1,3 +1,4 @@ +import re from collections import deque import pytest @@ -139,3 +140,84 @@ def test_graph_functional_start_end(): assert len(results) == len(ids) + 1 assert all(result.vertex.id in ids for result in results if hasattr(result, "vertex")) assert results[-1] == Finish() + + +def generate_import_statement(instance): + class_name = instance.__class__.__name__ + module_path = instance.__class__.__module__ + parts = module_path.split(".") + + # Construct the correct import statement + if len(parts) > 2: + module_path = ".".join(parts) + return f"from {module_path} import {class_name}" + else: + return f"from {module_path} import {class_name}" + + +def get_variable_name(instance): + return re.sub(r"[^0-9a-zA-Z_]", "_", instance._id.lower()) + + +def generate_instantiation_string(instance): + class_name = instance.__class__.__name__ + instance_id = instance._id + variable_name = get_variable_name(instance) + return f"{variable_name} = {class_name}(_id='{instance_id}')" + + +def generate_call_string(instance): + variable_name = get_variable_name(instance) + if hasattr(instance, "_call_inputs"): + args = ", ".join( + f"{key}={get_variable_name(value.__self__)}.{value.__name__}" if callable(value) else f"{key}={repr(value)}" + for key, value in instance._call_inputs.items() + ) + if args: + return f"{variable_name}({args})" + + +def generate_script(*instances): + import_statements = set() + instantiation_strings = [] + call_strings = [] + + for instance in instances: + import_statements.add(generate_import_statement(instance)) + instantiation_strings.append(generate_instantiation_string(instance)) + call_string = generate_call_string(instance) + + if call_string: + call_strings.append(call_string) + + import_code = "\n".join(sorted(import_statements)) + instantiation_code = "\n".join(instantiation_strings) + call_code = "\n".join(call_strings) + + return f"{import_code}\n\n{instantiation_code}\n\n{call_code}" + + +def test_generate_code(): + chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") + import_statement = generate_import_statement(chat_input_instance) + instantiation_string = generate_instantiation_string(chat_input_instance) + assert import_statement == "from langflow.components.inputs import ChatInput" + assert instantiation_string == "chatinput_1230 = ChatInput(_id='chatInput-1230')" + + +def test_generate_script(): + chat_input = components.inputs.ChatInput(_id="chatInput-1230") + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( + input_value=chat_input.message_response + ) + script = generate_script(chat_input, text_output) + assert ( + script + == """from langflow.components.inputs.ChatInput import ChatInput +from langflow.components.outputs.TextOutput import TextOutputComponent + +chatinput_1230 = ChatInput(_id='chatInput-1230') +textoutput_1231 = TextOutputComponent(_id='textoutput-1231') + +textoutput_1231(input_value=chatinput_1230.message_response)""" + ) From 00d6f86b2adaf7d8723809c6aeb84ce946a38878 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 09:33:46 -0300 Subject: [PATCH 62/88] refactor: Sort vertices in the graph This commit refactors the `sort_vertices` method in the `Graph` class to improve the sorting of vertices in the graph. The `_sort_vertices` method is introduced to handle the sorting logic, while the `sort_vertices` method serves as a public interface. This change enhances the readability and maintainability of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 1452cca0bec..4397640ad51 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1627,12 +1627,11 @@ def __filter_vertices(self, vertex_id: str, is_start: bool = False): vertex_ids = sort_up_to_vertex(dictionaryized_graph, vertex_id, is_start) return [self.get_vertex(vertex_id) for vertex_id in vertex_ids] - def sort_vertices( + def _sort_vertices( self, stop_component_id: Optional[str] = None, start_component_id: Optional[str] = None, - ) -> List[str]: - """Sorts the vertices in the graph.""" + ): self.mark_all_vertices("ACTIVE") if stop_component_id is not None: self.stop_vertex = stop_component_id @@ -1653,6 +1652,17 @@ def sort_vertices( # Now we should sort each layer in a way that we make sure # vertex V does not depend on vertex V+1 vertices_layers = self.sort_layer_by_dependency(vertices_layers) + return vertices_layers + + def sort_vertices( + self, + stop_component_id: Optional[str] = None, + start_component_id: Optional[str] = None, + ) -> List[str]: + """Sorts the vertices in the graph.""" + vertices_layers = self._sort_vertices( + stop_component_id=stop_component_id, start_component_id=start_component_id + ) self.increment_run_count() self._sorted_vertices_layers = vertices_layers first_layer = vertices_layers[0] From 83fd9ecdd87bbc350edbdf86af6be93bdff23a59 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 09:35:52 -0300 Subject: [PATCH 63/88] feat(base.py): add support for flattening vertices layers in Graph class --- src/backend/base/langflow/graph/graph/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 4397640ad51..30e61f48e45 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1631,6 +1631,7 @@ def _sort_vertices( self, stop_component_id: Optional[str] = None, start_component_id: Optional[str] = None, + flatten: bool = False, ): self.mark_all_vertices("ACTIVE") if stop_component_id is not None: @@ -1652,6 +1653,11 @@ def _sort_vertices( # Now we should sort each layer in a way that we make sure # vertex V does not depend on vertex V+1 vertices_layers = self.sort_layer_by_dependency(vertices_layers) + if flatten: + layer = [] + for layer_ in vertices_layers: + layer.extend(layer_) + vertices_layers = [layer] return vertices_layers def sort_vertices( From 9112f9974740e38994804eb8312df4884406eba5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:09:03 -0300 Subject: [PATCH 64/88] refactor: Rename test_generate_code to test_generate_import_statement_and_instantiation_string in test_base.py --- src/backend/tests/unit/graph/graph/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index 163579e91c6..d34f52ca9d7 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -197,7 +197,7 @@ def generate_script(*instances): return f"{import_code}\n\n{instantiation_code}\n\n{call_code}" -def test_generate_code(): +def test_generate_import_statement_and_instantiation_string(): chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") import_statement = generate_import_statement(chat_input_instance) instantiation_string = generate_instantiation_string(chat_input_instance) From d8403c5e7662049b4ad213e3e62093a35a22f2bc Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:09:56 -0300 Subject: [PATCH 65/88] feat(base.py): add support for flattening vertices layers in Graph class --- src/backend/base/langflow/graph/graph/base.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 30e61f48e45..39d1f478469 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1654,10 +1654,7 @@ def _sort_vertices( # vertex V does not depend on vertex V+1 vertices_layers = self.sort_layer_by_dependency(vertices_layers) if flatten: - layer = [] - for layer_ in vertices_layers: - layer.extend(layer_) - vertices_layers = [layer] + vertices_layers = chain.from_iterable(vertices_layers) return vertices_layers def sort_vertices( From f04d5ce11bc9a41f1493dcd1b792b3badcc4ea84 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:10:04 -0300 Subject: [PATCH 66/88] refactor: Sort components in the graph This commit refactors the `sort_components` method in the `Graph` class to improve the sorting of components in the graph. The `_sort_vertices` method is used to sort the vertices and retrieve the corresponding components. This change enhances the readability and maintainability of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 39d1f478469..40dc8944af6 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1657,6 +1657,19 @@ def _sort_vertices( vertices_layers = chain.from_iterable(vertices_layers) return vertices_layers + def sort_components( + self, stop_component_id: Optional[str] = None, start_component_id: Optional[str] = None + ) -> List[str]: + """Sorts the vertices in the graph.""" + vertices_layers = self._sort_vertices( + stop_component_id=stop_component_id, start_component_id=start_component_id, flatten=True + ) + # Now get all the vertices instances + vertices = [self.get_vertex(vertex_id) for vertex_id in vertices_layers] + # Now we need to get the components + components = [vertex._custom_component for vertex in vertices if hasattr(vertex, "_custom_component")] + return components + def sort_vertices( self, stop_component_id: Optional[str] = None, From da89cea0dbd652c937035b96239f058f9547efd9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:10:11 -0300 Subject: [PATCH 67/88] refactor: Add test_gerenate_script_from_graph to test_base.py This commit adds the test_gerenate_script_from_graph function to the test_base.py file. The function tests the generation of a script from a graph by creating instances of various components and asserting the generated script. This change improves the test coverage and reliability of the codebase. --- .../tests/unit/graph/graph/test_base.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index d34f52ca9d7..9b40b29035e 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -221,3 +221,28 @@ def test_generate_script(): textoutput_1231(input_value=chatinput_1230.message_response)""" ) + + +def test_gerenate_script_from_graph(): + chat_input = components.inputs.ChatInput(_id="chatInput-1230") + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( + input_value=chat_input.message_response + ) + chat_output = components.outputs.ChatOutput(input_value="test", _id="chatOutput-1232")( + input_value=text_output.text_response + ) + graph = Graph(chat_input, chat_output) + script = generate_script(*graph.sort_components()) + assert ( + script + == """from langflow.components.inputs.ChatInput import ChatInput +from langflow.components.outputs.ChatOutput import ChatOutput +from langflow.components.outputs.TextOutput import TextOutputComponent + +chatinput_1230 = ChatInput(_id='chatInput-1230') +textoutput_1231 = TextOutputComponent(_id='textoutput-1231') +chatoutput_1232 = ChatOutput(_id='chatOutput-1232') + +textoutput_1231(input_value=chatinput_1230.message_response) +chatoutput_1232(input_value=textoutput_1231.text_response)""" + ) From b8d987f52160814c8d4eeaa3a93a3dc637f0cae3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:17:55 -0300 Subject: [PATCH 68/88] refactor: Add code generation functions to test_base.py This commit adds code generation functions to the test_base.py file. The functions generate_import_statement, get_variable_name, generate_instantiation_string, generate_call_string, and generate_script are added to facilitate code generation for testing purposes. This change improves the testability and maintainability of the codebase. --- .../base/langflow/code_gen/__init__.py | 0 .../base/langflow/code_gen/component.py | 39 ++++++++++++ src/backend/base/langflow/code_gen/generic.py | 18 ++++++ src/backend/base/langflow/code_gen/graph.py | 6 ++ .../tests/unit/graph/graph/test_base.py | 59 +------------------ 5 files changed, 66 insertions(+), 56 deletions(-) create mode 100644 src/backend/base/langflow/code_gen/__init__.py create mode 100644 src/backend/base/langflow/code_gen/component.py create mode 100644 src/backend/base/langflow/code_gen/generic.py create mode 100644 src/backend/base/langflow/code_gen/graph.py diff --git a/src/backend/base/langflow/code_gen/__init__.py b/src/backend/base/langflow/code_gen/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/backend/base/langflow/code_gen/component.py b/src/backend/base/langflow/code_gen/component.py new file mode 100644 index 00000000000..f9d52a8d277 --- /dev/null +++ b/src/backend/base/langflow/code_gen/component.py @@ -0,0 +1,39 @@ +from langflow.code_gen.generic import generate_import_statement, get_variable_name + + +def generate_call_string(instance): + variable_name = get_variable_name(instance) + if hasattr(instance, "_call_inputs"): + args = ", ".join( + f"{key}={get_variable_name(value.__self__)}.{value.__name__}" if callable(value) else f"{key}={repr(value)}" + for key, value in instance._call_inputs.items() + ) + if args: + return f"{variable_name}({args})" + + +def generate_instantiation_string(instance): + class_name = instance.__class__.__name__ + instance_id = instance._id + variable_name = get_variable_name(instance) + return f"{variable_name} = {class_name}(_id='{instance_id}')" + + +def generate_script(*instances): + import_statements = set() + instantiation_strings = [] + call_strings = [] + + for instance in instances: + import_statements.add(generate_import_statement(instance)) + instantiation_strings.append(generate_instantiation_string(instance)) + call_string = generate_call_string(instance) + + if call_string: + call_strings.append(call_string) + + import_code = "\n".join(sorted(import_statements)) + instantiation_code = "\n".join(instantiation_strings) + call_code = "\n".join(call_strings) + + return f"{import_code}\n\n{instantiation_code}\n\n{call_code}" diff --git a/src/backend/base/langflow/code_gen/generic.py b/src/backend/base/langflow/code_gen/generic.py new file mode 100644 index 00000000000..6b0fde10765 --- /dev/null +++ b/src/backend/base/langflow/code_gen/generic.py @@ -0,0 +1,18 @@ +import re + + +def get_variable_name(instance): + return re.sub(r"[^0-9a-zA-Z_]", "_", instance._id.lower()) + + +def generate_import_statement(instance): + class_name = instance.__class__.__name__ + module_path = instance.__class__.__module__ + parts = module_path.split(".") + + # Construct the correct import statement + if len(parts) > 2: + module_path = ".".join(parts) + return f"from {module_path} import {class_name}" + else: + return f"from {module_path} import {class_name}" diff --git a/src/backend/base/langflow/code_gen/graph.py b/src/backend/base/langflow/code_gen/graph.py new file mode 100644 index 00000000000..1445e016f5b --- /dev/null +++ b/src/backend/base/langflow/code_gen/graph.py @@ -0,0 +1,6 @@ +from langflow.code_gen.component import generate_script + + +def generate_script_from_graph(graph): + script = generate_script(*graph.sort_components()) + return script diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index 9b40b29035e..cbd24c5b7b5 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -1,8 +1,10 @@ -import re from collections import deque import pytest +from langflow import components +from langflow.code_gen.component import generate_instantiation_string, generate_script +from langflow.code_gen.generic import generate_import_statement from langflow.components.inputs.ChatInput import ChatInput from langflow.components.outputs.ChatOutput import ChatOutput from langflow.components.outputs.TextOutput import TextOutputComponent @@ -142,61 +144,6 @@ def test_graph_functional_start_end(): assert results[-1] == Finish() -def generate_import_statement(instance): - class_name = instance.__class__.__name__ - module_path = instance.__class__.__module__ - parts = module_path.split(".") - - # Construct the correct import statement - if len(parts) > 2: - module_path = ".".join(parts) - return f"from {module_path} import {class_name}" - else: - return f"from {module_path} import {class_name}" - - -def get_variable_name(instance): - return re.sub(r"[^0-9a-zA-Z_]", "_", instance._id.lower()) - - -def generate_instantiation_string(instance): - class_name = instance.__class__.__name__ - instance_id = instance._id - variable_name = get_variable_name(instance) - return f"{variable_name} = {class_name}(_id='{instance_id}')" - - -def generate_call_string(instance): - variable_name = get_variable_name(instance) - if hasattr(instance, "_call_inputs"): - args = ", ".join( - f"{key}={get_variable_name(value.__self__)}.{value.__name__}" if callable(value) else f"{key}={repr(value)}" - for key, value in instance._call_inputs.items() - ) - if args: - return f"{variable_name}({args})" - - -def generate_script(*instances): - import_statements = set() - instantiation_strings = [] - call_strings = [] - - for instance in instances: - import_statements.add(generate_import_statement(instance)) - instantiation_strings.append(generate_instantiation_string(instance)) - call_string = generate_call_string(instance) - - if call_string: - call_strings.append(call_string) - - import_code = "\n".join(sorted(import_statements)) - instantiation_code = "\n".join(instantiation_strings) - call_code = "\n".join(call_strings) - - return f"{import_code}\n\n{instantiation_code}\n\n{call_code}" - - def test_generate_import_statement_and_instantiation_string(): chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") import_statement = generate_import_statement(chat_input_instance) From af2a3c26364324e47f7334100471373af281c1dc Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:21:49 -0300 Subject: [PATCH 69/88] feat(tests): add unit tests for component code generation and graph code generation --- .../unit/code_gen/test_component_codegen.py | 29 ++++++++++ .../unit/code_gen/test_generic_codegen.py | 0 .../tests/unit/code_gen/test_graph_codegen.py | 28 ++++++++++ .../tests/unit/graph/graph/test_base.py | 54 ------------------- 4 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 src/backend/tests/unit/code_gen/test_component_codegen.py create mode 100644 src/backend/tests/unit/code_gen/test_generic_codegen.py create mode 100644 src/backend/tests/unit/code_gen/test_graph_codegen.py diff --git a/src/backend/tests/unit/code_gen/test_component_codegen.py b/src/backend/tests/unit/code_gen/test_component_codegen.py new file mode 100644 index 00000000000..11893279a2e --- /dev/null +++ b/src/backend/tests/unit/code_gen/test_component_codegen.py @@ -0,0 +1,29 @@ +from langflow.code_gen.component import generate_instantiation_string, generate_script +from langflow.code_gen.generic import generate_import_statement +from langflow.components import components + + +def test_generate_script(): + chat_input = components.inputs.ChatInput(_id="chatInput-1230") + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( + input_value=chat_input.message_response + ) + script = generate_script(chat_input, text_output) + assert ( + script + == """from langflow.components.inputs.ChatInput import ChatInput +from langflow.components.outputs.TextOutput import TextOutputComponent + +chatinput_1230 = ChatInput(_id='chatInput-1230') +textoutput_1231 = TextOutputComponent(_id='textoutput-1231') + +textoutput_1231(input_value=chatinput_1230.message_response)""" + ) + + +def test_generate_import_statement_and_instantiation_string(): + chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") + import_statement = generate_import_statement(chat_input_instance) + instantiation_string = generate_instantiation_string(chat_input_instance) + assert import_statement == "from langflow.components.inputs import ChatInput" + assert instantiation_string == "chatinput_1230 = ChatInput(_id='chatInput-1230')" diff --git a/src/backend/tests/unit/code_gen/test_generic_codegen.py b/src/backend/tests/unit/code_gen/test_generic_codegen.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/backend/tests/unit/code_gen/test_graph_codegen.py b/src/backend/tests/unit/code_gen/test_graph_codegen.py new file mode 100644 index 00000000000..bcc216317a3 --- /dev/null +++ b/src/backend/tests/unit/code_gen/test_graph_codegen.py @@ -0,0 +1,28 @@ +from langflow import components +from langflow.code_gen.graph import generate_script_from_graph +from langflow.graph.graph.base import Graph + + +def test_gerenate_script_from_graph(): + chat_input = components.inputs.ChatInput(_id="chatInput-1230") + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( + input_value=chat_input.message_response + ) + chat_output = components.outputs.ChatOutput(input_value="test", _id="chatOutput-1232")( + input_value=text_output.text_response + ) + graph = Graph(chat_input, chat_output) + script = generate_script_from_graph(graph) + assert ( + script + == """from langflow.components.inputs.ChatInput import ChatInput +from langflow.components.outputs.ChatOutput import ChatOutput +from langflow.components.outputs.TextOutput import TextOutputComponent + +chatinput_1230 = ChatInput(_id='chatInput-1230') +textoutput_1231 = TextOutputComponent(_id='textoutput-1231') +chatoutput_1232 = ChatOutput(_id='chatOutput-1232') + +textoutput_1231(input_value=chatinput_1230.message_response) +chatoutput_1232(input_value=textoutput_1231.text_response)""" + ) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index cbd24c5b7b5..45be6c609ac 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -2,9 +2,6 @@ import pytest -from langflow import components -from langflow.code_gen.component import generate_instantiation_string, generate_script -from langflow.code_gen.generic import generate_import_statement from langflow.components.inputs.ChatInput import ChatInput from langflow.components.outputs.ChatOutput import ChatOutput from langflow.components.outputs.TextOutput import TextOutputComponent @@ -142,54 +139,3 @@ def test_graph_functional_start_end(): assert len(results) == len(ids) + 1 assert all(result.vertex.id in ids for result in results if hasattr(result, "vertex")) assert results[-1] == Finish() - - -def test_generate_import_statement_and_instantiation_string(): - chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") - import_statement = generate_import_statement(chat_input_instance) - instantiation_string = generate_instantiation_string(chat_input_instance) - assert import_statement == "from langflow.components.inputs import ChatInput" - assert instantiation_string == "chatinput_1230 = ChatInput(_id='chatInput-1230')" - - -def test_generate_script(): - chat_input = components.inputs.ChatInput(_id="chatInput-1230") - text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( - input_value=chat_input.message_response - ) - script = generate_script(chat_input, text_output) - assert ( - script - == """from langflow.components.inputs.ChatInput import ChatInput -from langflow.components.outputs.TextOutput import TextOutputComponent - -chatinput_1230 = ChatInput(_id='chatInput-1230') -textoutput_1231 = TextOutputComponent(_id='textoutput-1231') - -textoutput_1231(input_value=chatinput_1230.message_response)""" - ) - - -def test_gerenate_script_from_graph(): - chat_input = components.inputs.ChatInput(_id="chatInput-1230") - text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( - input_value=chat_input.message_response - ) - chat_output = components.outputs.ChatOutput(input_value="test", _id="chatOutput-1232")( - input_value=text_output.text_response - ) - graph = Graph(chat_input, chat_output) - script = generate_script(*graph.sort_components()) - assert ( - script - == """from langflow.components.inputs.ChatInput import ChatInput -from langflow.components.outputs.ChatOutput import ChatOutput -from langflow.components.outputs.TextOutput import TextOutputComponent - -chatinput_1230 = ChatInput(_id='chatInput-1230') -textoutput_1231 = TextOutputComponent(_id='textoutput-1231') -chatoutput_1232 = ChatOutput(_id='chatOutput-1232') - -textoutput_1231(input_value=chatinput_1230.message_response) -chatoutput_1232(input_value=textoutput_1231.text_response)""" - ) From 12e4139609e5c0689ead5eb6e1af7e481dd2fa44 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:28:43 -0300 Subject: [PATCH 70/88] refactor(test_component_codegen.py): update import statement to improve code readability and maintainability --- src/backend/tests/unit/code_gen/test_component_codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/tests/unit/code_gen/test_component_codegen.py b/src/backend/tests/unit/code_gen/test_component_codegen.py index 11893279a2e..a5b04e27f37 100644 --- a/src/backend/tests/unit/code_gen/test_component_codegen.py +++ b/src/backend/tests/unit/code_gen/test_component_codegen.py @@ -1,6 +1,6 @@ from langflow.code_gen.component import generate_instantiation_string, generate_script from langflow.code_gen.generic import generate_import_statement -from langflow.components import components +from langflow import components def test_generate_script(): From a1aed060d0be612db13707462ced25c390d2dee6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 28 Jul 2024 10:45:02 -0300 Subject: [PATCH 71/88] refactor(test_component_codegen.py): update import statement to match the correct module path for ChatInput class --- src/backend/tests/unit/code_gen/test_component_codegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/tests/unit/code_gen/test_component_codegen.py b/src/backend/tests/unit/code_gen/test_component_codegen.py index a5b04e27f37..a20a81d5628 100644 --- a/src/backend/tests/unit/code_gen/test_component_codegen.py +++ b/src/backend/tests/unit/code_gen/test_component_codegen.py @@ -1,6 +1,6 @@ +from langflow import components from langflow.code_gen.component import generate_instantiation_string, generate_script from langflow.code_gen.generic import generate_import_statement -from langflow import components def test_generate_script(): @@ -25,5 +25,5 @@ def test_generate_import_statement_and_instantiation_string(): chat_input_instance = components.inputs.ChatInput(_id="chatInput-1230") import_statement = generate_import_statement(chat_input_instance) instantiation_string = generate_instantiation_string(chat_input_instance) - assert import_statement == "from langflow.components.inputs import ChatInput" + assert import_statement == "from langflow.components.inputs.ChatInput import ChatInput" assert instantiation_string == "chatinput_1230 = ChatInput(_id='chatInput-1230')" From 080943b1ccf1ca6370bdaf6b421e7b5571d6d461 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 14:14:34 -0300 Subject: [PATCH 72/88] fix(component.py): update call string generation to use .set method and improve error handling for invalid instance types --- src/backend/base/langflow/code_gen/component.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/code_gen/component.py b/src/backend/base/langflow/code_gen/component.py index f9d52a8d277..0188522c598 100644 --- a/src/backend/base/langflow/code_gen/component.py +++ b/src/backend/base/langflow/code_gen/component.py @@ -9,10 +9,14 @@ def generate_call_string(instance): for key, value in instance._call_inputs.items() ) if args: - return f"{variable_name}({args})" + return f"{variable_name}.set({args})" def generate_instantiation_string(instance): + if isinstance(instance, tuple): + raise ValueError( + "An instance of Component was expected, but a tuple was provided. You might be trying to call the component instead of calling the `set` method." + ) class_name = instance.__class__.__name__ instance_id = instance._id variable_name = get_variable_name(instance) From 87137f8028b01c275202ef81a27f9af8147e0b6b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 14:16:04 -0300 Subject: [PATCH 73/88] fix(component.py): add set call inputs in Component class This commit adds support for setting call inputs in the Component class. The `_set_call_inputs` method is implemented to handle the setting of call inputs based on the provided key-value pairs. This change enhances the functionality and flexibility of the Component class. --- src/backend/base/langflow/custom/custom_component/component.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index c8340a55dea..53f6276df66 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -98,6 +98,7 @@ def set(self, **kwargs): """ for key, value in kwargs.items(): self._process_connection_or_parameter(key, value) + return self def list_inputs(self): @@ -267,6 +268,7 @@ def _process_connection_or_parameter(self, key, value): self._connect_to_component(key, value, _input) else: self._set_parameter_or_attribute(key, value) + self._set_call_inputs(key, value) def _get_or_create_input(self, key): try: From 4b812fd6c767dbc98097062ce949f2256f4b3cb3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 14:53:23 -0300 Subject: [PATCH 74/88] refactor(graph.py): rename start/end to entry/exit and update related methods for clarity and consistency --- src/backend/base/langflow/graph/graph/base.py | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 40dc8944af6..86998367d5d 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -40,8 +40,8 @@ class Graph: def __init__( self, - start: Optional["Component"] = None, - end: Optional["Component"] = None, + entry: Optional["Component"] = None, + exit: Optional["Component"] = None, flow_id: Optional[str] = None, flow_name: Optional[str] = None, user_id: Optional[str] = None, @@ -89,15 +89,17 @@ def __init__( self._first_layer: List[str] = [] self._lock = asyncio.Lock() self.raw_graph_data: GraphData = {"nodes": [], "edges": []} + self._entry = None + self._exit = None try: self.tracing_service: "TracingService" | None = get_tracing_service() except Exception as exc: logger.error(f"Error getting tracing service: {exc}") self.tracing_service = None - if start is not None and end is not None: - self._set_start_and_end(start, end) + if entry is not None and exit is not None: + self._set_entry_and_exit(entry, exit) self.prepare() - if (start is not None and end is None) or (start is None and end is not None): + if (entry is not None and exit is None) or (entry is None and exit is not None): raise ValueError("You must provide both input and output components") def dumps( @@ -166,13 +168,27 @@ def add_component(self, _id: str, component: "Component"): for _component in component._components: self.add_component(_component._id, _component) - def _set_start_and_end(self, start: "Component", end: "Component"): - if not hasattr(start, "to_frontend_node"): - raise TypeError(f"start must be a Component. Got {type(start)}") - if not hasattr(end, "to_frontend_node"): - raise TypeError(f"end must be a Component. Got {type(end)}") - self.add_component(start._id, start) - self.add_component(end._id, end) + @property + def entry(self): + if self._entry is None: + raise ValueError("Graph has no entry component") + return self._entry + + @property + def exit(self): + if self._exit is None: + raise ValueError("Graph has no exit component") + return self._exit + + def _set_entry_and_exit(self, entry: "Component", exit: "Component"): + if not hasattr(entry, "to_frontend_node"): + raise TypeError(f"start must be a Component. Got {type(entry)}") + if not hasattr(exit, "to_frontend_node"): + raise TypeError(f"end must be a Component. Got {type(exit)}") + self._entry = entry + self._exit = exit + self.add_component(entry._id, entry) + self.add_component(exit._id, exit) def add_component_edge(self, source_id: str, output_input_tuple: Tuple[str, str], target_id: str): source_vertex = self.get_vertex(source_id) From 8acb248b8f17c2df260014590e3ae033ffb10ccf Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:03:06 -0300 Subject: [PATCH 75/88] refactor(graph.py): update generate_script_from_graph to use entry/exit and sort_components This commit updates the generate_script_from_graph function in graph.py to use the entry and exit attributes instead of start and end for clarity and consistency. It also modifies the sort_components method to improve the sorting of components in the graph. These changes enhance the readability and maintainability of the codebase. --- src/backend/base/langflow/code_gen/graph.py | 9 +++++++-- .../starter_projects/test_vector_store_rag.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/code_gen/graph.py b/src/backend/base/langflow/code_gen/graph.py index 1445e016f5b..2fac2b52fce 100644 --- a/src/backend/base/langflow/code_gen/graph.py +++ b/src/backend/base/langflow/code_gen/graph.py @@ -1,6 +1,11 @@ +from git import TYPE_CHECKING + from langflow.code_gen.component import generate_script +if TYPE_CHECKING: + from langflow.graph.graph.base import Graph + -def generate_script_from_graph(graph): - script = generate_script(*graph.sort_components()) +def generate_script_from_graph(graph: "Graph"): + script = generate_script(entry=graph._entry, exit=graph._exit, instances=graph.sort_components()) return script diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py b/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py index 95fc5a4e74d..b0544dd188c 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py @@ -87,7 +87,7 @@ def rag_graph(): chat_output = ChatOutput(_id="chatoutput-123") chat_output.set(input_value=openai_component.text_response) - graph = Graph(start=chat_input, end=chat_output) + graph = Graph(entry=chat_input, exit=chat_output) return graph From e287a85d5cd89d10a1518b542971a1bc9325c3db Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:03:31 -0300 Subject: [PATCH 76/88] refactor(component.py): update generate_script to include graph instantiation This commit refactors the generate_script function in component.py to include the instantiation of a graph when entry and exit parameters are provided. It introduces the generate_graph_instantiation_string function to generate the graph instantiation code. This change enhances the functionality and flexibility of the codebase. --- src/backend/base/langflow/code_gen/component.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/code_gen/component.py b/src/backend/base/langflow/code_gen/component.py index 0188522c598..770a0d33da4 100644 --- a/src/backend/base/langflow/code_gen/component.py +++ b/src/backend/base/langflow/code_gen/component.py @@ -23,7 +23,11 @@ def generate_instantiation_string(instance): return f"{variable_name} = {class_name}(_id='{instance_id}')" -def generate_script(*instances): +def generate_graph_instantiation_string(entry, _exit): + return f"graph = Graph(entry={get_variable_name(entry)}, exit={get_variable_name(_exit)})" + + +def generate_script(*, instances, entry=None, exit=None): import_statements = set() instantiation_strings = [] call_strings = [] @@ -39,5 +43,9 @@ def generate_script(*instances): import_code = "\n".join(sorted(import_statements)) instantiation_code = "\n".join(instantiation_strings) call_code = "\n".join(call_strings) + if entry is None or exit is None: + graph_instantiation_code = "" + else: + graph_instantiation_code = generate_graph_instantiation_string(entry, exit) - return f"{import_code}\n\n{instantiation_code}\n\n{call_code}" + return f"{import_code}\n\n{instantiation_code}\n\n{call_code}\n\n{graph_instantiation_code}".strip() From 7ff77cb1680bc2f33cc46ca379a7e6aeec7e00cd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:03:51 -0300 Subject: [PATCH 77/88] test(tests): add pytest fixture for client in test_component_codegen.py to improve test structure and functionality --- src/backend/tests/unit/code_gen/test_component_codegen.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/tests/unit/code_gen/test_component_codegen.py b/src/backend/tests/unit/code_gen/test_component_codegen.py index a20a81d5628..ba297ef4fa4 100644 --- a/src/backend/tests/unit/code_gen/test_component_codegen.py +++ b/src/backend/tests/unit/code_gen/test_component_codegen.py @@ -1,8 +1,15 @@ +import pytest + from langflow import components from langflow.code_gen.component import generate_instantiation_string, generate_script from langflow.code_gen.generic import generate_import_statement +@pytest.fixture +def client(): + pass + + def test_generate_script(): chat_input = components.inputs.ChatInput(_id="chatInput-1230") text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( From 8493b584f34c6b099587a24c7ab394ab7c23a721 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:04:21 -0300 Subject: [PATCH 78/88] test(tests): refactor test_generate_script to update TextOutput component input handling for better clarity and structure --- .../tests/unit/code_gen/test_component_codegen.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/backend/tests/unit/code_gen/test_component_codegen.py b/src/backend/tests/unit/code_gen/test_component_codegen.py index ba297ef4fa4..79b3f65fb27 100644 --- a/src/backend/tests/unit/code_gen/test_component_codegen.py +++ b/src/backend/tests/unit/code_gen/test_component_codegen.py @@ -12,10 +12,9 @@ def client(): def test_generate_script(): chat_input = components.inputs.ChatInput(_id="chatInput-1230") - text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( - input_value=chat_input.message_response - ) - script = generate_script(chat_input, text_output) + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231") + text_output.set(input_value=chat_input.message_response) + script = generate_script(instances=(chat_input, text_output), entry=chat_input, exit=text_output) assert ( script == """from langflow.components.inputs.ChatInput import ChatInput @@ -24,7 +23,9 @@ def test_generate_script(): chatinput_1230 = ChatInput(_id='chatInput-1230') textoutput_1231 = TextOutputComponent(_id='textoutput-1231') -textoutput_1231(input_value=chatinput_1230.message_response)""" +textoutput_1231.set(input_value=chatinput_1230.message_response) + +graph = Graph(entry=chatinput_1230, exit=textoutput_1231)""" ) From 84f8219a311d137ea68f4c1c0dd445754a2272bd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:04:32 -0300 Subject: [PATCH 79/88] Refactor test to use `set` method for setting input values in `test_generate_script_from_graph` --- src/backend/tests/unit/code_gen/test_graph_codegen.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/backend/tests/unit/code_gen/test_graph_codegen.py b/src/backend/tests/unit/code_gen/test_graph_codegen.py index bcc216317a3..34835bca601 100644 --- a/src/backend/tests/unit/code_gen/test_graph_codegen.py +++ b/src/backend/tests/unit/code_gen/test_graph_codegen.py @@ -5,12 +5,11 @@ def test_gerenate_script_from_graph(): chat_input = components.inputs.ChatInput(_id="chatInput-1230") - text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231")( - input_value=chat_input.message_response - ) - chat_output = components.outputs.ChatOutput(input_value="test", _id="chatOutput-1232")( - input_value=text_output.text_response - ) + text_output = components.outputs.TextOutput.TextOutputComponent(_id="textoutput-1231") + text_output.set(input_value=chat_input.message_response) + chat_output = components.outputs.ChatOutput(input_value="test", _id="chatOutput-1232") + chat_output.set(input_value=text_output.text_response) + graph = Graph(chat_input, chat_output) script = generate_script_from_graph(graph) assert ( From e6b3861350d65f405ccf4446bd4992e7d78e1a0c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Aug 2024 16:04:43 -0300 Subject: [PATCH 80/88] refactor(test_memory_chatbot.py): add test for generating script from graph This commit adds a new test case to test_memory_chatbot.py to verify the functionality of generating a script from a graph. The test_memory_chatbot_generate_script test ensures that the generate_script_from_graph function correctly generates the expected script based on the provided graph. This test enhances the test coverage and ensures the correctness of the codebase. --- .../starter_projects/test_memory_chatbot.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py index afe24441cef..2ff2426ead5 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py @@ -2,6 +2,7 @@ import pytest +from langflow.code_gen.graph import generate_script_from_graph from langflow.components.helpers.Memory import MemoryComponent from langflow.components.inputs.ChatInput import ChatInput from langflow.components.models.OpenAIModel import OpenAIModelComponent @@ -120,3 +121,29 @@ def test_memory_chatbot_dump_components_and_edges(memory_chatbot_graph: Graph): source = edge["source"] target = edge["target"] assert (source, target) in expected_edges, edge + + +def test_memory_chatbot_generate_script(memory_chatbot_graph: Graph): + script = generate_script_from_graph(memory_chatbot_graph) + expected_script = """from langflow.components.inputs.ChatInput import ChatInput +from langflow.components.outputs.ChatOutput import ChatOutput +from langflow.components.helpers.Memory import MemoryComponent +from langflow.components.prompts.Prompt import PromptComponent +from langflow.components.models.OpenAIModel import OpenAIModelComponent + +chat_input = ChatInput(_id='chat_input') +chat_memory = MemoryComponent(_id='chat_memory') +prompt = PromptComponent(_id='prompt') +openai = OpenAIModelComponent(_id='openai') +chat_output = ChatOutput(_id='chat_output') + +# Setting up the components +chat_memory.set(session_id='test_session_id') +prompt.set(template='{context}\\n\\nUser: {user_message}\\nAI: ', user_message=chat_input.message_response, context=chat_memory.retrieve_messages_as_text) +openai.set(input_value=prompt.build_prompt, max_tokens=100, temperature=0.1, api_key='test_api_key') +chat_output.set(input_value=openai.text_response) + +# Building the graph +graph = Graph(entry=chat_input, exit=chat_output) +""" + assert script == expected_script From c0c7331d8153083143dc1d139d74ca39eef2822c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:25:21 -0300 Subject: [PATCH 81/88] refactor: enhance Vertex class with _build_instance and get_component_instance methods for better component management --- .../base/langflow/graph/vertex/base.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 8284736acf3..d890bc78dac 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -6,6 +6,7 @@ import types from enum import Enum from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Mapping, Optional, Set +from uuid import UUID import pandas as pd from loguru import logger @@ -22,6 +23,7 @@ from langflow.schema.schema import INPUT_FIELD_NAME, OutputValue, build_output_logs from langflow.services.deps import get_storage_service from langflow.services.tracing.schema import Log +from langflow.utils.async_helpers import run_until_complete from langflow.utils.constants import DIRECT_TYPES from langflow.utils.schemas import ChatOutputResponse from langflow.utils.util import sync_to_async, unescape_string @@ -453,7 +455,7 @@ async def _build( raise ValueError(f"Base type for vertex {self.display_name} not found") if not self._custom_component: - custom_component, custom_params = await initialize.loading.instantiate_class(user_id=user_id, vertex=self) + custom_component, custom_params = await self._build_instance(user_id) else: custom_component = self._custom_component custom_params = initialize.loading.get_params(self.params) @@ -464,6 +466,32 @@ async def _build( self._built = True + async def _build_instance(self, user_id: Optional[str | UUID] = None): + """ + Builds the instance of the component. + """ + if self._custom_component is not None: + raise ValueError("Component is already built.") + custom_component, custom_params = await initialize.loading.instantiate_class(user_id=user_id, vertex=self) + return custom_component, custom_params + + def get_component_instance(self, user_id: Optional[str | UUID] = None): + """ + Retrieves the instance of the component. + + Args: + user_id (Optional[str | UUID], optional): The user ID. Defaults to None. + + Returns: + Any: The instance of the component. + + Raises: + ValueError: If the component is not built. + """ + if not self._custom_component: + self._custom_component = run_until_complete(self._build_instance(user_id))[0] + return self._custom_component + def extract_messages_from_artifacts(self, artifacts: Dict[str, Any]) -> List[dict]: """ Extracts messages from the artifacts. From 461bf54f7cb3b0bf785b335364c6506b0ff18848 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:25:38 -0300 Subject: [PATCH 82/88] refactor: add Graph import statement and tidy up import handling in generate_script function --- src/backend/base/langflow/code_gen/component.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/code_gen/component.py b/src/backend/base/langflow/code_gen/component.py index 770a0d33da4..ad585927d82 100644 --- a/src/backend/base/langflow/code_gen/component.py +++ b/src/backend/base/langflow/code_gen/component.py @@ -40,12 +40,13 @@ def generate_script(*, instances, entry=None, exit=None): if call_string: call_strings.append(call_string) - import_code = "\n".join(sorted(import_statements)) - instantiation_code = "\n".join(instantiation_strings) - call_code = "\n".join(call_strings) if entry is None or exit is None: graph_instantiation_code = "" else: graph_instantiation_code = generate_graph_instantiation_string(entry, exit) - + # Add Graph import statement to the beginning of the script + import_statements.add("from langflow.graph.graph.base import Graph") + import_code = "\n".join(sorted(import_statements)) + instantiation_code = "\n".join(instantiation_strings) + call_code = "\n".join(call_strings) return f"{import_code}\n\n{instantiation_code}\n\n{call_code}\n\n{graph_instantiation_code}".strip() From 8644d8f61d0ef34cc10b1d193e9677bfef09cac6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:26:05 -0300 Subject: [PATCH 83/88] refactor: add validation to prevent script generation from JSON in generate_import_statement function --- src/backend/base/langflow/code_gen/generic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/base/langflow/code_gen/generic.py b/src/backend/base/langflow/code_gen/generic.py index 6b0fde10765..8923e139286 100644 --- a/src/backend/base/langflow/code_gen/generic.py +++ b/src/backend/base/langflow/code_gen/generic.py @@ -8,6 +8,8 @@ def get_variable_name(instance): def generate_import_statement(instance): class_name = instance.__class__.__name__ module_path = instance.__class__.__module__ + if module_path == "langflow.utils.validate": + raise ValueError("Generating script from JSON is not yet supported.") parts = module_path.split(".") # Construct the correct import statement From 394ad1b4786a29d21ca56e89cd38312aecad78d9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:26:31 -0300 Subject: [PATCH 84/88] refactor: implement _find_entry method to improve graph entry component resolution logic in Graph class --- src/backend/base/langflow/graph/graph/base.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index f292ad152c7..5f58dbbae37 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -168,10 +168,27 @@ def add_component(self, _id: str, component: "Component"): for _component in component._components: self.add_component(_component._id, _component) + def _find_entry(self): + # Find a vertex with a "ChatInput" component, overriding a "TextInput" component + found_entry = None + for vertex_id in self._is_input_vertices: + if "ChatInput" in vertex_id: + found_entry = vertex_id + break + elif found_entry is None and "TextInput" in vertex_id: + found_entry = vertex_id + vertex = self.get_vertex(found_entry) if found_entry else None + return vertex.get_component_instance() if vertex else None + @property def entry(self): if self._entry is None: - raise ValueError("Graph has no entry component") + if entry := self._find_entry(): + self._entry = entry + else: + raise ValueError( + "Graph has no entry component or couldn't find a suitable entry component (e.g. ChatInput)" + ) return self._entry @property From 13a490c7583555842df55a771fbc2fb9916a10ae Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:26:46 -0300 Subject: [PATCH 85/88] refactor: implement _find_exit method to enhance exit component resolution in Graph class --- src/backend/base/langflow/graph/graph/base.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 5f58dbbae37..d91c19034f5 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -191,10 +191,26 @@ def entry(self): ) return self._entry + def _find_exit(self): + # Find a vertex with a "ChatOutput" component, overriding a "TextOutput" component + found_exit = None + for vertex_id in self._is_output_vertices: + if "ChatOutput" in vertex_id: + found_exit = vertex_id + break + elif found_exit is None and "TextOutput" in vertex_id: + found_exit = vertex_id + vertex = self.get_vertex(found_exit) if found_exit else None + + return vertex.get_component_instance() if vertex else None + @property def exit(self): if self._exit is None: - raise ValueError("Graph has no exit component") + if _exit := self._find_exit(): + self._exit = _exit + else: + raise ValueError("Graph has no exit component") return self._exit def _set_entry_and_exit(self, entry: "Component", exit: "Component"): From 5209ff6320244d88a07da0b10ec53485f0fa758e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:27:05 -0300 Subject: [PATCH 86/88] refactor: update component retrieval to use get_component_instance method in Graph class --- src/backend/base/langflow/graph/graph/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index d91c19034f5..f1dd7a9328e 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1720,7 +1720,7 @@ def sort_components( # Now get all the vertices instances vertices = [self.get_vertex(vertex_id) for vertex_id in vertices_layers] # Now we need to get the components - components = [vertex._custom_component for vertex in vertices if hasattr(vertex, "_custom_component")] + components = [vertex.get_component_instance() for vertex in vertices if hasattr(vertex, "_custom_component")] return components def sort_vertices( From b9d6bfc8c8eae2b32fb2c3887b156ef60c5eb0be Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 7 Aug 2024 17:27:14 -0300 Subject: [PATCH 87/88] refactor: update graph script generation to access entry and exit properties directly in Graph class --- src/backend/base/langflow/code_gen/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/code_gen/graph.py b/src/backend/base/langflow/code_gen/graph.py index 2fac2b52fce..8483d41abbb 100644 --- a/src/backend/base/langflow/code_gen/graph.py +++ b/src/backend/base/langflow/code_gen/graph.py @@ -7,5 +7,5 @@ def generate_script_from_graph(graph: "Graph"): - script = generate_script(entry=graph._entry, exit=graph._exit, instances=graph.sort_components()) + script = generate_script(entry=graph.entry, exit=graph.exit, instances=graph.sort_components()) return script From fdaf96ff852ac29f8390de4e840d0c0092f7582d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:14:30 +0000 Subject: [PATCH 88/88] [autofix.ci] apply automated fixes --- .../base/langflow/custom/custom_component/component.py | 2 +- src/backend/base/langflow/graph/vertex/base.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 2f1c6ed727a..aa792713640 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -85,7 +85,7 @@ def __init__(self, **kwargs): def _set_call_inputs(self, key: str, value: Any): self._call_inputs[key] = value - + def set_event_manager(self, event_manager: EventManager | None = None): self._event_manager = event_manager diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 59755584b62..0efe25241e6 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -6,9 +6,8 @@ import types from collections.abc import AsyncIterator, Callable, Iterator, Mapping from enum import Enum -from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Mapping, Optional, Set +from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Mapping, Optional from uuid import UUID -from typing import TYPE_CHECKING, Any, Optional import pandas as pd from loguru import logger @@ -489,7 +488,9 @@ async def _build_instance(self, user_id: Optional[str | UUID] = None, event_mana """ if self._custom_component is not None: raise ValueError("Component is already built.") - custom_component, custom_params = await initialize.loading.instantiate_class(user_id=user_id, vertex=self, event_manager=event_manager) + custom_component, custom_params = await initialize.loading.instantiate_class( + user_id=user_id, vertex=self, event_manager=event_manager + ) return custom_component, custom_params def get_component_instance(self, user_id: Optional[str | UUID] = None):