From 719015b5bb82f806347eecfa4c7f47f8d152c3a5 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Mon, 10 Jul 2023 23:38:01 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(custom.py):=20import=20re=20?= =?UTF-8?q?module=20to=20fix=20NameError=20when=20using=20re.split=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(custom.py):=20fix=20indentation=20of=20class?= =?UTF-8?q?=5Ftemplate=20dictionary=20to=20improve=20readability=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(custom.py):=20fix=20indentation=20of=20class?= =?UTF-8?q?=20dictionary=20to=20improve=20readability=20=F0=9F=90=9B=20fix?= =?UTF-8?q?(custom.py):=20fix=20indentation=20of=20=5Fhandle=5Ffunction=20?= =?UTF-8?q?method=20to=20improve=20readability=20=F0=9F=90=9B=20fix(custom?= =?UTF-8?q?.py):=20fix=20indentation=20of=20transform=5Flist=20method=20to?= =?UTF-8?q?=20improve=20readability=20=F0=9F=90=9B=20fix(custom.py):=20fix?= =?UTF-8?q?=20indentation=20of=20extract=5Fclass=5Finfo=20method=20to=20im?= =?UTF-8?q?prove=20readability=20=F0=9F=90=9B=20fix(custom.py):=20fix=20in?= =?UTF-8?q?dentation=20of=20=5Fclass=5Ftemplate=5Fvalidation=20method=20to?= =?UTF-8?q?=20improve=20readability=20=F0=9F=90=9B=20fix(custom.py):=20fix?= =?UTF-8?q?=20indentation=20of=20build=5Flangchain=5Ftemplate=5Fcustom=5Fc?= =?UTF-8?q?omponent=20method=20to=20improve=20readability=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(custom.py):=20fix=20indentation=20of=20add=5Fnew=5Fcusto?= =?UTF-8?q?m=5Ffield=20method=20to=20improve=20readability=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(custom.py):=20fix=20indentation=20of=20add=5Fcode=5Ffiel?= =?UTF-8?q?d=20method=20to=20improve=20readability=20=F0=9F=90=9B=20fix(cu?= =?UTF-8?q?stom.py):=20fix=20indentation=20of=20extract=5Ftype=5Ffrom=5Fop?= =?UTF-8?q?tional=20method=20to=20improve=20readability=20=F0=9F=90=9B=20f?= =?UTF-8?q?ix(custom.py):=20fix=20indentation=20of=20build=5Flangchain=5Ft?= =?UTF-8?q?emplate=5Fcustom=5Fcomponent=20method=20to=20improve=20readabil?= =?UTF-8?q?ity=20=F0=9F=94=A5=20chore(custom.py):=20remove=20unused=20impo?= =?UTF-8?q?rts=20and=20variables=20=E2=9C=A8=20feat(custom.py):=20add=20su?= =?UTF-8?q?pport=20for=20splitting=20a=20string=20by=20':'=20or=20'=3D'=20?= =?UTF-8?q?and=20padding=20with=20None=20until=20length=20is=203=20in=20?= =?UTF-8?q?=5Fsplit=5Fstring=20method=20=E2=9C=A8=20feat(custom.py):=20add?= =?UTF-8?q?=20support=20for=20transforming=20a=20list=20of=20strings=20by?= =?UTF-8?q?=20splitting=20each=20string=20and=20padding=20with=20None=20in?= =?UTF-8?q?=20transform=5Flist=20method=20=E2=9C=A8=20feat(custom.py):=20a?= =?UTF-8?q?dd=20support=20for=20extracting=20the=20type=20from=20a=20strin?= =?UTF-8?q?g=20formatted=20as=20"Optional[]"=20in=20extract=5Ftype?= =?UTF-8?q?=5Ffrom=5Foptional=20method=20=E2=9C=A8=20feat(custom.py):=20ad?= =?UTF-8?q?d=20support=20for=20passing=20field=5Fvalue=20and=20field=5Freq?= =?UTF-8?q?uired=20parameters=20to=20add=5Fnew=5Fcustom=5Ffield=20method?= =?UTF-8?q?=20=E2=9C=A8=20feat(custom.py):=20add=20support=20for=20passing?= =?UTF-8?q?=20field=5Fvalue=20and=20field=5Frequired=20parameters=20to=20b?= =?UTF-8?q?uild=5Flangchain=5Ftemplate=5Fcustom=5Fcomponent=20method=20?= =?UTF-8?q?=E2=9C=A8=20feat(custom.py):=20add=20support=20for=20passing=20?= =?UTF-8?q?field=5Fvalue=20and=20field=5Frequired=20parameters=20to=20add?= =?UTF-8?q?=5Fnew=5Fcustom=5Ffield=20method=20=E2=9C=A8=20feat(custom.py):?= =?UTF-8?q?=20add=20support=20for=20passing=20field=5Fvalue=20and=20field?= =?UTF-8?q?=5Frequired=20parameters=20to=20build=5Flangchain=5Ftemplate=5F?= =?UTF-8?q?custom=5Fcomponent=20method=20=E2=9C=A8=20feat(custom.py):=20ad?= =?UTF-8?q?d=20support=20for=20passing=20field=5Fvalue=20and=20field=5Freq?= =?UTF-8?q?uired=20parameters=20to=20add=5Fnew=5Fcustom=5Ffield=20method?= =?UTF-8?q?=20=E2=9C=A8=20feat(custom.py):=20add=20support=20for=20passing?= =?UTF-8?q?=20field=5Fvalue=20and=20field=5Frequired=20parameters=20to=20b?= =?UTF-8?q?uild=5Flangchain=5Ftemplate=5Fcustom=5Fcomponent=20method=20?= =?UTF-8?q?=E2=9C=A8=20feat(custom.py):=20add=20support=20for=20passing=20?= =?UTF-8?q?field=5Fvalue=20and=20field=5Frequired=20parameters=20to=20add?= =?UTF-8?q?=5Fnew=5Fcustom=5Ffield=20method=20=E2=9C=A8=20feat(custom.py):?= =?UTF-8?q?=20add=20support=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langflow/interface/custom/custom.py | 46 ++++++++------ src/backend/langflow/interface/types.py | 62 +++++++++++++++---- 2 files changed, 77 insertions(+), 31 deletions(-) diff --git a/src/backend/langflow/interface/custom/custom.py b/src/backend/langflow/interface/custom/custom.py index 91ec84e84..6d46c5d18 100644 --- a/src/backend/langflow/interface/custom/custom.py +++ b/src/backend/langflow/interface/custom/custom.py @@ -1,5 +1,5 @@ +import re import ast -import contextlib import traceback from typing import Callable, Optional from fastapi import HTTPException @@ -77,26 +77,34 @@ class CustomComponent(BaseModel): else: self.class_template["functions"].append(function_data) + def _split_string(self, text): + """ + Split a string by ':' or '=' and append None until the resulting list has 3 items. + + Parameters: + text (str): The string to be split. + + Returns: + list: A list of strings resulting from the split operation, + padded with None until its length is 3. + """ + items = [item.strip() for item in re.split(r"[:=]", text) if item.strip()] + while len(items) < 3: + items.append(None) + + return items + def transform_list(self, input_list): - output_list = [] - for item in input_list: - # Split each item on ':' to separate variable name and type - split_item = item.split(":") + """ + Transform a list of strings by splitting each string and padding with None. - # If there is a type, strip any leading/trailing spaces from it - if len(split_item) > 1: - split_item[1] = split_item[1].strip() - # If there isn't a type, append None - else: - split_item.append(None) - for i in range(len(split_item)): - with contextlib.suppress(ValueError): - # Try to evaluate the item - split_item[i] = ast.literal_eval(split_item[i]) + Parameters: + input_list (list): The list of strings to be transformed. - output_list.append(split_item) - - return output_list + Returns: + list: A list of lists, each containing the result of the split operation. + """ + return [self._split_string(item) for item in input_list] def extract_class_info(self): try: @@ -120,6 +128,7 @@ class CustomComponent(BaseModel): attributes = data.get("class", {}).get("attributes", {}) functions = data.get("functions", []) template_config = self._build_template_config(attributes) + if build_function := next( (f for f in functions if f["name"] == self.function_entrypoint_name), None, @@ -146,6 +155,7 @@ class CustomComponent(BaseModel): ) if "description" in attributes: template_config["description"] = ast.literal_eval(attributes["description"]) + return template_config def _class_template_validation(self, code: dict): diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 8b6d78f9a..08bfe77c7 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -20,14 +20,14 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.tools import CustomComponentNode from langflow.interface.retrievers.base import retriever_creator -from langflow.utils.util import get_base_classes +import re import warnings -from fastapi import HTTPException import traceback +from fastapi import HTTPException +from langflow.utils.util import get_base_classes + # Used to get the base_classes list - - def get_type_list(): """Get a list of all langchain types""" all_types = build_langchain_types_dict() @@ -69,6 +69,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union created_types = creator.to_dict() if created_types[creator.type_name].values(): all_types.update(created_types) + return all_types @@ -78,25 +79,35 @@ def process_type(field_type: str): # TODO: Move to correct place def add_new_custom_field( - template, field_name: str, field_type: str, field_config: dict + template, + field_name: str, + field_type: str, + field_value: str, + field_required: bool, + field_config: dict, ): # Check field_config if any of the keys are in it # if it is, update the value display_name = field_config.pop("display_name", field_name) field_type = field_config.pop("field_type", field_type) field_type = process_type(field_type) + + if field_value is not None: + field_value = field_value.replace("'", "").replace('"', "") + if "name" in field_config: warnings.warn( "The 'name' key in field_config is used to build the object and can't be changed." ) field_config.pop("name", None) - required = field_config.pop("required", True) + required = field_config.pop("required", field_required) placeholder = field_config.pop("placeholder", "") new_field = TemplateField( name=field_name, field_type=field_type, + value=field_value, show=True, required=required, advanced=False, @@ -133,6 +144,20 @@ def add_code_field(template, raw_code): return template +def extract_type_from_optional(field_type): + """ + Extract the type from a string formatted as "Optional[]". + + Parameters: + field_type (str): The string from which to extract the type. + + Returns: + str: The extracted type, or an empty string if no type was found. + """ + match = re.search(r"\[(.*?)\]", field_type) + return match[1] if match else None + + def build_langchain_template_custom_component(extractor: CustomComponent): # Build base "CustomComponent" template frontend_node = CustomComponentNode().to_dict().get(type(extractor).__name__) @@ -145,19 +170,30 @@ def build_langchain_template_custom_component(extractor: CustomComponent): frontend_node["description"] = template_config["description"] raw_code = extractor.code field_config = template_config.get("field_config", {}) + if function_args is not None: # Add extra fields for extra_field in function_args: - def_field = extra_field[0] - def_type = extra_field[1] + field_required = True + field_name, field_type, field_value = extra_field - if def_field != "self": + if field_name != "self": # TODO: Validate type - if is possible to render into frontend - if not def_type: - def_type = "str" - config = field_config.get(def_field, {}) + if "optional" in field_type.lower(): + field_type = extract_type_from_optional(field_type) + field_required = False + + if not field_type: + field_type = "str" + + config = field_config.get(field_name, {}) frontend_node = add_new_custom_field( - frontend_node, def_field, def_type, config + frontend_node, + field_name, + field_type, + field_value, + field_required, + config, ) frontend_node = add_code_field(frontend_node, raw_code)