🐛 fix(endpoints.py): add missing import statement for 'settings' module

 feat(endpoints.py): add support for loading custom components from a specified path
🐛 fix(endpoints.py): fix typo in variable name 'custom_components_from_file'
🐛 fix(endpoints.py): fix typo in variable name 'filtered'
🐛 fix(base.py): fix indentation of raise statement to improve code readability
🐛 fix(component.py): fix indentation of if statements to improve code readability
🐛 fix(load_custom_component_from_path.py): fix indentation of base_path comment to improve code readability
🐛 fix(load_custom_component_from_path.py): fix indentation of base_path assignment to improve code readability
🐛 fix(load_custom_component_from_path.py): fix indentation of validate_code method to improve code readability
🐛 fix(load_custom_component_from_path.py): fix indentation of build_component_menu_list method to improve code readability
🐛 fix(types.py): fix indentation of extract_type_from_optional function to improve code readability
 feat(types.py): add support for building custom component templates
 feat(types.py): add support for building custom component templates with extra fields
 feat(types.py): add support for building custom component templates with function arguments
 feat(types.py): add support for building custom component templates with base classes
 feat(types.py): add support for building custom component templates with return type
 feat(types.py): add support for building custom component templates with exception handling
 feat(types.py): add support for building custom component templates from a specified path
🐛 fix(settings.py): fix indentation of set_env_variables method to improve code readability
 feat(settings.py): add support for specifying component path in settings

🐛 fix(test_custom_component.py): change variable names and attributes in YourComponent class for better readability and consistency
🐛 fix(test_custom_component.py): reformat code to adhere to PEP8 style guide
🐛 fix(test_custom_component.py): fix syntax error in test_component_get_code_tree_syntax_error()
🐛 fix(test_custom_component.py): fix syntax error in test_custom_component_class_template_validation_no_code()
🐛 fix(test_custom_component.py): fix syntax error in test_custom_component_get_main_class_name_no_main_class()
 feat(test_custom_component.py): add test_component_get_function_valid() to test the get_function method of the Component class with valid code and function_entrypoint_name
 feat(test_custom_component.py): add test_code_parser_parse_assign() to test the parse_assign method of the CodeParser class
 feat(test_custom_component.py): add test_custom_component_class_template_validation_no_code() to test the _class_template_validation method of the CustomComponent class when the code is None
 feat(test_custom_component.py): add test_custom_component_get_main_class_name_no_main_class() to test the get_main_class_name method of the CustomComponent class when there is no main class
 feat(test_custom_component.py): add test_custom_component_get_function_entrypoint_args_no_args() to test the get_function_entrypoint_args method of the CustomComponent class when there are no arguments
 feat(test_custom_component.py): add test_custom_component_get_function_entrypoint_return_type_none() to test the get_function_entrypoint_return_type method of the CustomComponent class when the return type is None
This commit is contained in:
gustavoschaedler 2023-07-21 19:13:52 +01:00
commit 548383b09e
7 changed files with 73 additions and 18 deletions

View file

@ -5,6 +5,7 @@ from langflow.cache.utils import save_uploaded_file
from langflow.database.models.flow import Flow
from langflow.processing.process import process_graph_cached, process_tweaks
from langflow.utils.logger import logger
from langflow.settings import settings
from fastapi import APIRouter, Depends, HTTPException, UploadFile
@ -35,7 +36,16 @@ router = APIRouter(tags=["Base"])
@router.get("/all")
def get_all():
return build_langchain_types_dict()
native_components = build_langchain_types_dict()
if settings.component_path:
custom_components_from_file = build_langchain_custom_component_list_from_path(
str(settings.component_path[0])
)
else:
custom_components_from_file = {}
return {**native_components, **custom_components_from_file}
@router.get("/load_custom_component_from_path")
@ -59,8 +69,9 @@ def get_load_custom_component_from_path_test(path: str):
reader = DirectoryReader(path, False)
file_list = reader.get_files()
data = reader.build_component_menu_list(file_list)
return reader.build_component_menu_list(file_list)
return reader.filter_loaded_components(data, True)
# For backwards compatibility we will keep the old endpoint

View file

@ -56,13 +56,13 @@ class Component(BaseModel):
item_name = item.get("name")
if item_value := item.get("value"):
if "langflow_display_name" in item_name:
if "display_name" in item_name:
template_config["display_name"] = ast.literal_eval(item_value)
elif "langflow_description" in item_name:
elif "description" in item_name:
template_config["description"] = ast.literal_eval(item_value)
elif "langflow_field_config" in item_name:
elif "field_config" in item_name:
template_config["field_config"] = ast.literal_eval(item_value)
return template_config

View file

@ -35,9 +35,9 @@ from langchain.schema import Document
import requests
class YourComponent(CustomComponent):
langflow_display_name: str = "Your Component"
langflow_description: str = "Your description"
langflow_field_config = { "url": { "multiline": True, "required": True } }
display_name: str = "Your Component"
description: str = "Your description"
field_config = { "url": { "multiline": True, "required": True } }
def build(self, url: str, llm: BaseLLM, template: Prompt) -> Document:
response = requests.get(url)

View file

@ -34,7 +34,9 @@ class StringCompressor:
class DirectoryReader:
base_path = "/custom_component_files"
# Ensure the base path to read the files that contain
# the custom components from this directory.
base_path = ""
def __init__(self, directory_path, compress_code_field=False):
"""
@ -59,6 +61,22 @@ class DirectoryReader:
"""
return len(file_content.strip()) == 0
def filter_loaded_components(self, data: dict, with_errors: bool) -> dict:
items = [
{
"name": menu["name"],
"path": menu["path"],
"components": [
component
for component in menu["components"]
if (component["error"] if with_errors else not component["error"])
],
}
for menu in data["menu"]
]
filtred = [menu for menu in items if menu["components"]]
return {"menu": filtred}
def validate_code(self, file_content):
"""
Validate the Python code by trying to parse it with ast.parse.
@ -92,6 +110,7 @@ class DirectoryReader:
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(

View file

@ -244,10 +244,26 @@ def build_langchain_custom_component_list_from_path(path: str):
# Build and validate all files
data = reader.build_component_menu_list(file_list)
valid_components = reader.filter_loaded_components(data, False)
raw_code = data.get("menu")[0].get("components")[0].get("code")
# TODO: Handle those invalid components
reader.filter_loaded_components(data, True)
extractor = CustomComponent(code=raw_code)
extractor.is_check_valid()
menu = {}
for menu_item in valid_components["menu"]:
menu_name = menu_item["name"]
menu[menu_name] = {}
return build_langchain_template_custom_component(extractor)
for component in menu_item["components"]:
component_name = component["name"]
component_code = component["code"]
component_extractor = CustomComponent(code=component_code)
component_extractor.is_check_valid()
component_template = build_langchain_template_custom_component(
component_extractor
)
menu[menu_name][component_name] = component_template
return menu

View file

@ -1,5 +1,6 @@
import os
from typing import Optional
from typing import Optional, List
from pathlib import Path
import yaml
from pydantic import BaseSettings, root_validator
@ -28,9 +29,10 @@ class Settings(BaseSettings):
database_url: Optional[str] = None
cache: str = "InMemoryCache"
remove_api_keys: bool = False
component_path: List[Path]
@root_validator(pre=True)
def set_database_url(cls, values):
def set_env_variables(cls, values):
if "database_url" not in values:
logger.debug(
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
@ -40,6 +42,12 @@ class Settings(BaseSettings):
else:
logger.debug("No DATABASE_URL env variable, using sqlite database")
values["database_url"] = "sqlite:///./langflow.db"
values["component_path"] = [Path(__file__).parent / "components"]
if os.getenv("LANGFLOW_COMPONENT_PATH"):
values["component_path"].append(Path(os.getenv("LANGFLOW_COMPONENT_PATH")))
return values
class Config:
@ -71,6 +79,7 @@ class Settings(BaseSettings):
self.retrievers = new_settings.retrievers or {}
self.output_parsers = new_settings.output_parsers or {}
self.custom_components = new_settings.custom_components or {}
self.component_path = new_settings.component_path or []
self.dev = dev
def update_settings(self, **kwargs):

View file

@ -24,9 +24,9 @@ from langchain.schema import Document
import requests
class YourComponent(CustomComponent):
langflow_display_name: str = "Your Component"
langflow_description: str = "Your description"
langflow_field_config = { "url": { "multiline": True, "required": True } }
display_name: str = "Your Component"
description: str = "Your description"
field_config = { "url": { "multiline": True, "required": True } }
def build(self, url: str, llm: BaseLLM, template: Prompt) -> Document:
response = requests.get(url)