From 5c210f609e249cd8017cd24aa04bed8d8272f580 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 10:37:01 -0300 Subject: [PATCH 01/13] Refactor get_files to exclude folders inside the component path --- .../directory_reader/directory_reader.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/backend/langflow/interface/custom/directory_reader/directory_reader.py b/src/backend/langflow/interface/custom/directory_reader/directory_reader.py index 57bacc9bc..278d014c3 100644 --- a/src/backend/langflow/interface/custom/directory_reader/directory_reader.py +++ b/src/backend/langflow/interface/custom/directory_reader/directory_reader.py @@ -1,6 +1,7 @@ import ast import os import zlib +from pathlib import Path from loguru import logger @@ -79,9 +80,13 @@ class DirectoryReader: except Exception as e: logger.error(f"Error while loading component: {e}") continue - items.append({"name": menu["name"], "path": menu["path"], "components": components}) + items.append( + {"name": menu["name"], "path": menu["path"], "components": components} + ) filtered = [menu for menu in items if menu["components"]] - logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}') + logger.debug( + f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}' + ) return {"menu": filtered} def validate_code(self, file_content): @@ -114,15 +119,24 @@ class DirectoryReader: Walk through the directory path and return a list of all .py files. """ if not (safe_path := self.get_safe_path()): - raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.") + raise CustomComponentPathValueError( + f"The path needs to start with '{self.base_path}'." + ) file_list = [] - for root, _, files in os.walk(safe_path): - file_list.extend( - os.path.join(root, filename) - for filename in files - if filename.endswith(".py") and not filename.startswith("__") - ) + safe_path_obj = Path(safe_path) + for file_path in safe_path_obj.rglob("*.py"): + # The other condtion is that it should be + # in the safe_path/[folder]/[file].py format + # any folders below [folder] will be ignored + # basically the parent folder of the file should be a + # folder in the safe_path + if ( + file_path.is_file() + and file_path.parent.parent == safe_path_obj + and not file_path.name.startswith("__") + ): + file_list.append(str(file_path)) return file_list def find_menu(self, response, menu_name): @@ -159,7 +173,9 @@ class DirectoryReader: for node in ast.walk(module): if isinstance(node, ast.FunctionDef): for arg in node.args.args: - if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name): + if self._is_type_hint_in_arg_annotation( + arg.annotation, type_hint_name + ): return True except SyntaxError: # Returns False if the code is not valid Python @@ -177,14 +193,16 @@ class DirectoryReader: and annotation.value.id == type_hint_name ) - def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool: + def is_type_hint_used_but_not_imported( + self, type_hint_name: str, code: str + ) -> bool: """ Check if a type hint is used but not imported in the given code. """ try: - return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported( + return self._is_type_hint_used_in_args( type_hint_name, code - ) + ) and not self._is_type_hint_imported(type_hint_name, code) except SyntaxError: # Returns True if there's something wrong with the code # TODO : Find a better way to handle this @@ -205,9 +223,9 @@ class DirectoryReader: return False, "Syntax error" elif not self.validate_build(file_content): return False, "Missing build function" - elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported( + elif self._is_type_hint_used_in_args( "Optional", file_content - ): + ) and not self._is_type_hint_imported("Optional", file_content): return ( False, "Type hint 'Optional' is used but not imported in the code.", @@ -223,7 +241,9 @@ class DirectoryReader: from the .py files in the directory. """ response = {"menu": []} - logger.debug("-------------------- Building component menu list --------------------") + logger.debug( + "-------------------- Building component menu list --------------------" + ) for file_path in file_paths: menu_name = os.path.basename(os.path.dirname(file_path)) @@ -243,7 +263,9 @@ class DirectoryReader: # first check if it's already CamelCase if "_" in component_name: - component_name_camelcase = " ".join(word.title() for word in component_name.split("_")) + component_name_camelcase = " ".join( + word.title() for word in component_name.split("_") + ) else: component_name_camelcase = component_name @@ -251,7 +273,9 @@ class DirectoryReader: try: output_types = self.get_output_types_from_code(result_content) except Exception as exc: - logger.exception(f"Error while getting output types from code: {str(exc)}") + logger.exception( + f"Error while getting output types from code: {str(exc)}" + ) output_types = [component_name_camelcase] else: output_types = [component_name_camelcase] @@ -267,7 +291,9 @@ class DirectoryReader: if menu_result not in response["menu"]: response["menu"].append(menu_result) - logger.debug("-------------------- Component menu list built --------------------") + logger.debug( + "-------------------- Component menu list built --------------------" + ) return response @staticmethod From 8aaa0ee91a10c427d22c5ed43798b003681b25de Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 10:37:13 -0300 Subject: [PATCH 02/13] Refactor directory_reader utils.py --- .../custom/directory_reader/utils.py | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/backend/langflow/interface/custom/directory_reader/utils.py b/src/backend/langflow/interface/custom/directory_reader/utils.py index f7378b8d0..34defe5c3 100644 --- a/src/backend/langflow/interface/custom/directory_reader/utils.py +++ b/src/backend/langflow/interface/custom/directory_reader/utils.py @@ -1,11 +1,18 @@ -from langflow.interface.custom.directory_reader import DirectoryReader -from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from loguru import logger +from langflow.interface.custom.directory_reader import DirectoryReader +from langflow.template.frontend_node.custom_components import ( + CustomComponentFrontendNode, +) + def merge_nested_dicts_with_renaming(dict1, dict2): for key, value in dict2.items(): - if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict): + if ( + key in dict1 + and isinstance(value, dict) + and isinstance(dict1.get(key), dict) + ): for sub_key, sub_value in value.items(): # if sub_key in dict1[key]: # new_key = get_new_key(dict1[key], sub_key) @@ -62,7 +69,9 @@ def build_custom_component_list_from_path(path: str): file_list = load_files_from_path(path) reader = DirectoryReader(path, False) - valid_components, invalid_components = build_and_validate_all_files(reader, file_list) + valid_components, invalid_components = build_and_validate_all_files( + reader, file_list + ) valid_menu = build_valid_menu(valid_components) invalid_menu = build_invalid_menu(invalid_components) @@ -109,7 +118,9 @@ def build_invalid_menu_items(menu_item): menu_items[component_name] = component_template logger.debug(f"Added {component_name} to invalid menu.") except Exception as exc: - logger.exception(f"Error while creating custom component [{component_name}]: {str(exc)}") + logger.exception( + f"Error while creating custom component [{component_name}]: {str(exc)}" + ) return menu_items @@ -136,12 +147,14 @@ def determine_component_name(component): def build_menu_items(menu_item): """Build menu items for a given menu.""" menu_items = {} + logger.debug(f"Building menu items for {menu_item['name']}") + logger.debug(f"Loading {len(menu_item['components'])} components") for component_name, component_template, component in menu_item["components"]: try: menu_items[component_name] = component_template - logger.debug(f"Added {component_name} to valid menu.") except Exception as exc: logger.error(f"Error loading Component: {component['output_types']}") - logger.exception(f"Error while building custom component {component['output_types']}: {exc}") - return menu_items + logger.exception( + f"Error while building custom component {component['output_types']}: {exc}" + ) return menu_items From f09ebeba340428bff12a97cb1d82dcfbdff85a64 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 11:03:55 -0300 Subject: [PATCH 03/13] Add support for input values in build_vertex API --- src/backend/langflow/api/v1/chat.py | 8 ++++++-- src/backend/langflow/api/v1/schemas.py | 4 ++++ src/backend/langflow/graph/vertex/base.py | 15 +++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 91b4bcd65..85a3a7abe 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -1,10 +1,11 @@ import time import uuid -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Annotated, Optional from fastapi import ( APIRouter, BackgroundTasks, + Body, Depends, HTTPException, WebSocket, @@ -21,6 +22,7 @@ from langflow.api.utils import ( format_exception_message, ) from langflow.api.v1.schemas import ( + InputValueRequest, ResultDataResponse, StreamData, VertexBuildResponse, @@ -139,10 +141,12 @@ async def build_vertex( flow_id: str, vertex_id: str, background_tasks: BackgroundTasks, + inputs: Annotated[InputValueRequest, Body(embed=True)] = None, chat_service: "ChatService" = Depends(get_chat_service), current_user=Depends(get_current_active_user), ): """Build a vertex instead of the entire graph.""" + {"inputs": {"input_value": "some value"}} start_time = time.perf_counter() try: start_time = time.perf_counter() @@ -163,7 +167,7 @@ async def build_vertex( vertex = graph.get_vertex(vertex_id) try: if not vertex.pinned or not vertex._built: - await vertex.build(user_id=current_user.id) + await vertex.build(user_id=current_user.id, inputs=inputs.model_dump()) if vertex.result is not None: params = vertex._built_object_repr() diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 0092efa4e..c85f946da 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -261,3 +261,7 @@ class VertexBuildResponse(BaseModel): class VerticesBuiltResponse(BaseModel): vertices: List[VertexBuildResponse] + + +class InputValueRequest(BaseModel): + input_value: str diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index b917889a4..c5b993f27 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -2,13 +2,16 @@ import ast import inspect import types from enum import Enum -from typing import (TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, - Optional) +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional from loguru import logger -from langflow.graph.schema import (INPUT_COMPONENTS, OUTPUT_COMPONENTS, - InterfaceComponentTypes, ResultData) +from langflow.graph.schema import ( + INPUT_COMPONENTS, + OUTPUT_COMPONENTS, + InterfaceComponentTypes, + ResultData, +) from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.vertex.utils import generate_result from langflow.interface.initialize import loading @@ -608,6 +611,7 @@ class Vertex: async def build( self, user_id=None, + inputs: Optional[Dict[str, Any]] = None, requester: Optional["Vertex"] = None, **kwargs, ) -> Any: @@ -620,6 +624,9 @@ class Vertex: return self.get_requester_result(requester) self._reset() + if inputs and self.is_input: + self.update_raw_params(inputs) + # Run steps for step in self.steps: if step not in self.steps_ran: From 11bf5ee4604f3c42566b83ca7aa71d1388c23d32 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 28 Feb 2024 11:07:16 -0300 Subject: [PATCH 04/13] Refactor build_vertex function to handle inputs dictionary correctly --- src/backend/langflow/api/v1/chat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 85a3a7abe..a4937789e 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -167,7 +167,8 @@ async def build_vertex( vertex = graph.get_vertex(vertex_id) try: if not vertex.pinned or not vertex._built: - await vertex.build(user_id=current_user.id, inputs=inputs.model_dump()) + inputs_dict = inputs.model_dump() if inputs else {} + await vertex.build(user_id=current_user.id, inputs=inputs_dict) if vertex.result is not None: params = vertex._built_object_repr() From 04bbf4eaf0b54ae05df578b2a256148890c33b68 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Wed, 28 Feb 2024 17:20:57 +0100 Subject: [PATCH 05/13] Made vertices be retrieved when opening chat or when changing anything. Added chat input in request. --- .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../src/components/IOInputField/index.tsx | 8 +- .../src/components/IOOutputView/index.tsx | 4 +- src/frontend/src/components/IOview/index.tsx | 83 ++++++++----- .../chatComponent/buildTrigger/index.tsx | 4 +- .../newChatView/chatInput/index.tsx | 18 +-- src/frontend/src/controllers/API/api.tsx | 2 +- src/frontend/src/controllers/API/index.ts | 5 +- src/frontend/src/stores/flowStore.ts | 82 ++++++++----- src/frontend/src/stores/flowsManagerStore.ts | 3 +- src/frontend/src/types/zustand/flow/index.ts | 8 +- src/frontend/src/utils/buildUtils.ts | 115 +++++++++++------- 12 files changed, 203 insertions(+), 131 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 5632f3c7a..a536ec6d6 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -465,7 +465,7 @@ export default function GenericNode({ if (buildStatus === BuildStatus.BUILDING || isBuilding) return; setValidationStatus(null); - buildFlow(data.id); + buildFlow({nodeId: data.id}); }} >
diff --git a/src/frontend/src/components/IOInputField/index.tsx b/src/frontend/src/components/IOInputField/index.tsx index e7aac5928..20c6dceb3 100644 --- a/src/frontend/src/components/IOInputField/index.tsx +++ b/src/frontend/src/components/IOInputField/index.tsx @@ -19,12 +19,12 @@ export default function IOInputField({