Merge branch 'logspace-ai:dev' into dev

This commit is contained in:
Dave Morris 2023-08-06 16:02:03 -05:00 • committed by GitHub
commit 4984a1282d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 485 additions and 298 deletions

View file

@ -1,4 +1,3 @@
import os
import sys
import time
import httpx
@ -25,49 +24,25 @@ def update_settings(
config: str,
cache: str,
dev: bool = False,
database_url: Optional[str] = None,
remove_api_keys: bool = False,
components_path: Optional[Path] = None,
):
"""Update the settings from a config file."""
# Check for database_url in the environment variables
database_url = database_url or os.getenv("langflow_database_url")
if config:
logger.debug(f"Loading settings from {config}")
settings.update_from_yaml(config, dev=dev)
if database_url:
settings.update_settings(database_url=database_url)
if remove_api_keys:
logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
settings.update_settings(remove_api_keys=remove_api_keys)
settings.update_settings(REMOVE_API_KEYS=remove_api_keys)
if cache:
logger.debug(f"Setting cache to {cache}")
settings.update_settings(cache=cache)
settings.update_settings(CACHE=cache)
if components_path:
logger.debug(f"Adding component path {components_path}")
settings.update_settings(components_path=components_path)
def load_params():
"""
Load the parameters from the environment variables.
"""
global_vars = globals()
for key, value in global_vars.items():
env_key = f"LANGFLOW_{key.upper()}"
if env_key in os.environ:
if isinstance(value, bool):
# Handle booleans
global_vars[key] = os.getenv(env_key, str(value)).lower() == "true"
elif isinstance(value, int):
# Handle integers
global_vars[key] = int(os.getenv(env_key, str(value)))
elif isinstance(value, str) or value is None:
# Handle strings and None values
global_vars[key] = os.getenv(env_key, str(value))
settings.update_settings(COMPONENTS_PATH=components_path)
def serve_on_jcloud():
@ -134,7 +109,7 @@ def serve(
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
# .env file param
env_file: Path = typer.Option(
".env", help="Path to the .env file containing environment variables."
None, help="Path to the .env file containing environment variables."
),
log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
@ -149,11 +124,13 @@ def serve(
),
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
database_url: str = typer.Option(
None,
help="Database URL to connect to. If not provided, a local SQLite database will be used.",
envvar="LANGFLOW_DATABASE_URL",
),
# This variable does not work but is set by the .env file
# and works with Pydantic
# database_url: str = typer.Option(
# None,
# help="Database URL to connect to. If not provided, a local SQLite database will be used.",
# envvar="LANGFLOW_DATABASE_URL",
# ),
path: str = typer.Option(
None,
help="Path to the frontend directory containing build files. This is for development purposes only.",
@ -176,7 +153,6 @@ def serve(
# override env variables with .env file
if env_file:
load_dotenv(env_file, override=True)
load_params()
if jcloud:
return serve_on_jcloud()
@ -185,7 +161,6 @@ def serve(
update_settings(
config,
dev=dev,
database_url=database_url,
remove_api_keys=remove_api_keys,
cache=cache,
components_path=components_path,

View file

@ -125,9 +125,8 @@ async def stream_build(flow_id: str):
vertex.build()
params = vertex._built_object_repr()
valid = True
logger.debug(
f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
)
logger.debug(f"Building node {str(vertex.vertex_type)}")
logger.debug(f"Output: {params}")
if vertex.artifacts:
# The artifacts will be prompt variables
# passed to build_input_keys_response

View file

@ -11,9 +11,6 @@ from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body
from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import (
CustomComponentPathValueError,
)
from langflow.api.v1.schemas import (
ProcessResponse,
@ -38,49 +35,27 @@ router = APIRouter(tags=["Base"])
@router.get("/all")
def get_all():
logger.debug("Building langchain types dict")
native_components = build_langchain_types_dict()
# custom_components is a list of dicts
# need to merge all the keys into one dict
custom_components_from_file = {}
if settings.components_path:
if settings.COMPONENTS_PATH:
logger.info(f"Building custom components from {settings.COMPONENTS_PATH}")
custom_component_dicts = [
build_langchain_custom_component_list_from_path(str(path))
for path in settings.components_path
for path in settings.COMPONENTS_PATH
]
logger.info(f"Loading {len(custom_component_dicts)} custom components")
for custom_component_dict in custom_component_dicts:
custom_components_from_file = merge_nested_dicts(
custom_components_from_file, custom_component_dict
)
logger.info(f"Loaded {custom_component_dict}")
return merge_nested_dicts(native_components, custom_components_from_file)
@router.get("/load_custom_component_from_path")
def get_load_custom_component_from_path(path: str):
try:
data = build_langchain_custom_component_list_from_path(path)
except CustomComponentPathValueError as err:
raise HTTPException(
status_code=400,
detail={"error": type(err).__name__, "traceback": str(err)},
) from err
return data
@router.get("/load_custom_component_from_path_TEST")
def get_load_custom_component_from_path_test(path: str):
from langflow.interface.custom.directory_reader import (
DirectoryReader,
)
reader = DirectoryReader(path, False)
file_list = reader.get_files()
data = reader.build_component_menu_list(file_list)
return reader.filter_loaded_components(data, True)
# For backwards compatibility we will keep the old endpoint
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
@router.post("/process/{flow_id}", response_model=ProcessResponse)

View file

@ -61,7 +61,7 @@ def update_flow(
if not db_flow:
raise HTTPException(status_code=404, detail="Flow not found")
flow_data = flow.dict(exclude_unset=True)
if settings.remove_api_keys:
if settings.REMOVE_API_KEYS:
flow_data = remove_api_keys(flow_data)
for key, value in flow_data.items():
setattr(db_flow, key, value)

View file

@ -0,0 +1,4 @@
from langflow.interface.custom.custom_component import CustomComponent
__all__ = ["CustomComponent"]

View file

@ -0,0 +1,33 @@
from langflow import CustomComponent
from langchain.llms.base import BaseLLM
from langchain import PromptTemplate
from langchain.schema import Document
class PromptRunner(CustomComponent):
display_name: str = "Prompt Runner"
description: str = "Run a Chain with the given PromptTemplate"
beta = True
field_config = {
"llm": {"display_name": "LLM"},
"prompt": {
"display_name": "Prompt Template",
"info": "Make sure the prompt has all variables filled.",
},
"code": {"show": False},
"inputs": {"field_type": "code"},
}
def build(
self,
llm: BaseLLM,
prompt: PromptTemplate,
) -> Document:
chain = prompt | llm
# The input is an empty dict because the prompt is already filled
result = chain.invoke({})
if hasattr(result, "content"):
result = result.content
self.repr_value = result
return Document(page_content=str(result))

View file

@ -1,21 +1,48 @@
from contextlib import contextmanager
from langflow.settings import settings
import os
from sqlmodel import SQLModel, Session, create_engine
from langflow.utils.logger import logger
if settings.database_url and settings.database_url.startswith("sqlite"):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
if not settings.database_url:
raise RuntimeError("No database_url provided")
engine = create_engine(settings.database_url, connect_args=connect_args)
class Engine:
_instance = None
@classmethod
def get(cls):
logger.debug("Getting database engine")
if cls._instance is None:
cls.create()
return cls._instance
@classmethod
def create(cls):
logger.debug("Creating database engine")
from langflow.settings import settings
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
settings.DATABASE_URL = langflow_database_url
logger.debug("Using LANGFLOW_DATABASE_URL")
if settings.DATABASE_URL and settings.DATABASE_URL.startswith("sqlite"):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
if not settings.DATABASE_URL:
raise RuntimeError("No database_url provided")
cls._instance = create_engine(settings.DATABASE_URL, connect_args=connect_args)
@classmethod
def update(cls):
logger.debug("Updating database engine")
cls._instance = None
cls.create()
def create_db_and_tables():
logger.debug("Creating database and tables")
try:
SQLModel.metadata.create_all(engine)
SQLModel.metadata.create_all(Engine.get())
except Exception as exc:
logger.error(f"Error creating database and tables: {exc}")
raise RuntimeError("Error creating database and tables") from exc
@ -23,7 +50,7 @@ def create_db_and_tables():
# and we need to create the tables again.
from sqlalchemy import inspect
inspector = inspect(engine)
inspector = inspect(Engine.get())
if "flow" not in inspector.get_table_names():
logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.")
@ -36,7 +63,7 @@ def create_db_and_tables():
@contextmanager
def session_getter():
try:
session = Session(engine)
session = Session(Engine.get())
yield session
except Exception as e:
print("Session rollback because of exception:", e)

View file

@ -1,3 +1,4 @@
import ast
from langflow.interface.initialize import loading
from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.constants import DIRECT_TYPES
@ -100,7 +101,9 @@ class Vertex:
params[param_key] = edge.source
for key, value in template_dict.items():
if key == "_type" or not value.get("show"):
# Skip _type and any value that has show == False and is not code
# If we don't want to show code but we want to use it
if key == "_type" or (not value.get("show") and key != "code"):
continue
# If the type is not transformable to a python base class
# then we need to get the edge that connects to this node
@ -112,7 +115,14 @@ class Vertex:
params[key] = file_path
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
params[key] = value.get("value")
if value.get("type") == "code":
try:
params[key] = ast.literal_eval(value.get("value"))
except Exception as exc:
logger.debug(f"Error parsing code: {exc}")
params[key] = value.get("value")
else:
params[key] = value.get("value")
if not value.get("required") and params.get(key) is None:
if value.get("default"):
@ -259,4 +269,8 @@ class Vertex:
def _built_object_repr(self):
# Add a message with an emoji, stars for sucess,
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵‍💫"
return (
"Built sucessfully ✨"
if self._built_object is not None
else "Failed to build 😵‍💫"
)

View file

@ -226,7 +226,11 @@ class PromptVertex(Vertex):
# so the prompt format doesn't break
artifacts.pop("handle_keys", None)
try:
template = self._built_object.format(**artifacts)
template = self._built_object.template
for key, value in artifacts.items():
if value:
replace_key = "{" + key + "}"
template = template.replace(replace_key, value)
return (
template
if isinstance(template, str)

View file

@ -59,7 +59,7 @@ class AgentCreator(LangChainTypeCreator):
if hasattr(agent, "function_name")
else agent.__name__
)
if agent_name in settings.agents or settings.dev:
if agent_name in settings.AGENTS or settings.DEV:
names.append(agent_name)
return names

View file

@ -28,7 +28,7 @@ class LangChainTypeCreator(BaseModel, ABC):
"""A dict with the name of the component as key and the documentation link as value."""
if self.name_docs_dict is None:
try:
type_settings = getattr(settings, self.type_name)
type_settings = getattr(settings, self.type_name.upper())
self.name_docs_dict = {
name: value_dict["documentation"]
for name, value_dict in type_settings.items()

View file

@ -8,6 +8,7 @@ from langflow.template.frontend_node.chains import ChainFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class, build_template_from_method
from langchain import chains
from langchain_experimental.sql import SQLDatabaseChain # type: ignore
# Assuming necessary imports for Field, Template, and FrontendNode classes
@ -35,12 +36,14 @@ class ChainCreator(LangChainTypeCreator):
}
from langflow.interface.chains.custom import CUSTOM_CHAINS
self.type_dict["SQLDatabaseChain"] = SQLDatabaseChain
self.type_dict.update(CUSTOM_CHAINS)
# Filter according to settings.chains
self.type_dict = {
name: chain
for name, chain in self.type_dict.items()
if name in settings.chains or settings.dev
if name in settings.CHAINS or settings.DEV
}
return self.type_dict

View file

@ -1,5 +1,5 @@
import ast
from typing import Optional
from typing import Any, Optional
from pydantic import BaseModel
from fastapi import HTTPException
@ -63,10 +63,10 @@ class Component(BaseModel):
elif "description" in item_name:
template_config["description"] = ast.literal_eval(item_value)
elif "field_config" in item_name:
template_config["field_config"] = ast.literal_eval(item_value)
elif "beta" in item_name:
template_config["beta"] = ast.literal_eval(item_value)
return template_config
def build(self):
def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError

View file

@ -7,6 +7,7 @@ from langchain.schema import BaseRetriever, Document
from langchain.text_splitter import TextSplitter
from langchain.tools import Tool
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseOutputParser
LANGCHAIN_BASE_TYPES = {
@ -20,6 +21,7 @@ LANGCHAIN_BASE_TYPES = {
"VectorStore": VectorStore,
"Embeddings": Embeddings,
"BaseRetriever": BaseRetriever,
"BaseOutputParser": BaseOutputParser,
}
# Langchain base types plus Python base types

View file

@ -9,6 +9,7 @@ from langflow.utils import validate
from langflow.database.base import session_getter
from langflow.database.models.flow import Flow
from pydantic import Extra
import yaml
class CustomComponent(Component, extra=Extra.allow):
@ -24,6 +25,10 @@ class CustomComponent(Component, extra=Extra.allow):
super().__init__(**data)
def custom_repr(self):
if isinstance(self.repr_value, dict):
return yaml.dump(self.repr_value)
if isinstance(self.repr_value, str):
return self.repr_value
return str(self.repr_value)
def build_config(self):
@ -190,5 +195,5 @@ class CustomComponent(Component, extra=Extra.allow):
raise ValueError(f"Flow {flow_name or flow_id} not found")
return self.load_flow(flow.id, tweaks)
def build(self):
def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError

View file

@ -1,6 +1,7 @@
import os
import ast
import zlib
from langflow.utils.logger import logger
class CustomComponentPathValueError(ValueError):
@ -74,8 +75,11 @@ class DirectoryReader:
}
for menu in data["menu"]
]
filtred = [menu for menu in items if menu["components"]]
return {"menu": filtred}
filtered = [menu for menu in items if menu["components"]]
logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {filtered}'
)
return {"menu": filtered}
def validate_code(self, file_content):
"""
@ -116,7 +120,7 @@ class DirectoryReader:
file_list.extend(
os.path.join(root, filename)
for filename in files
if filename.endswith(".py")
if filename.endswith(".py") and not filename.startswith("__")
)
return file_list
@ -213,27 +217,47 @@ class DirectoryReader:
from the .py files in the directory.
"""
response = {"menu": []}
logger.debug(
"-------------------- Building component menu list --------------------"
)
for file_path in file_paths:
menu_name = os.path.basename(os.path.dirname(file_path))
logger.debug(f"Menu name: {menu_name}")
filename = os.path.basename(file_path)
validation_result, result_content = self.process_file(file_path)
logger.debug(f"Validation result: {validation_result}")
menu_result = self.find_menu(response, menu_name) or {
"name": menu_name,
"path": os.path.dirname(file_path),
"components": [],
}
component_name = filename.split(".")[0]
# This is the name of the file which will be displayed in the UI
# We need to change it from snake_case to CamelCase
# first check if it's already CamelCase
if "_" in component_name:
component_name_camelcase = " ".join(
word.title() for word in component_name.split("_")
)
else:
component_name_camelcase = component_name
component_info = {
"name": filename.split(".")[0],
"name": "CustomComponent",
"output_types": [component_name_camelcase],
"file": filename,
"code": result_content if validation_result else "",
"error": "" if validation_result else result_content,
}
menu_result["components"].append(component_info)
logger.debug(f"Component info: {component_info}")
if menu_result not in response["menu"]:
response["menu"].append(menu_result)
logger.debug(
"-------------------- Component menu list built --------------------"
)
return response

View file

@ -33,7 +33,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
return [
documentloader.__name__
for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev
if documentloader.__name__ in settings.DOCUMENTLOADERS or settings.DEV
]

View file

@ -35,7 +35,7 @@ class EmbeddingCreator(LangChainTypeCreator):
return [
embedding.__name__
for embedding in self.type_to_loader_dict.values()
if embedding.__name__ in settings.embeddings or settings.dev
if embedding.__name__ in settings.EMBEDDINGS or settings.DEV
]

View file

@ -61,9 +61,7 @@ def import_by_type(_type: str, name: str) -> Any:
def import_custom_component(custom_component: str) -> CustomComponent:
"""Import custom component from custom component name"""
return import_class(
f"langflow.interface.custom.custom_component.{custom_component}"
)
return import_class("langflow.interface.custom.custom_component.CustomComponent")
def import_output_parser(output_parser: str) -> Any:

View file

@ -6,7 +6,11 @@ from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool
from langflow.interface.initialize.llm import initialize_vertexai
from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type
from langflow.interface.initialize.utils import (
handle_format_kwargs,
handle_node_type,
handle_partial_variables,
)
from langflow.interface.initialize.vector_store import vecstore_initializer
@ -29,6 +33,7 @@ from langflow.utils import validate
from langchain.chains.base import Chain
from langchain.vectorstores.base import VectorStore
from langchain.document_loaders.base import BaseLoader
from langflow.utils.logger import logger
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
@ -40,7 +45,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
if hasattr(custom_node, "initialize"):
return custom_node.initialize(**params)
return custom_node(**params)
logger.debug(f"Instantiating {node_type} of type {base_type}")
class_object = import_by_type(_type=base_type, name=node_type)
return instantiate_based_on_type(class_object, base_type, node_type, params)
@ -217,6 +222,9 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
def instantiate_prompt(node_type, class_object, params: Dict):
params, prompt = handle_node_type(node_type, class_object, params)
format_kwargs = handle_format_kwargs(prompt, params)
# Now we'll use partial_format to format the prompt
if format_kwargs:
prompt = handle_partial_variables(prompt, format_kwargs)
return prompt, format_kwargs

View file

@ -44,6 +44,16 @@ def handle_format_kwargs(prompt, params: Dict):
return format_kwargs
def handle_partial_variables(prompt, format_kwargs: Dict):
partial_variables = format_kwargs.copy()
partial_variables = {
key: value for key, value in partial_variables.items() if value
}
# Remove handle_keys otherwise LangChain raises an error
partial_variables.pop("handle_keys", None)
return prompt.partial(**partial_variables)
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
variable = params[input_variable]
if isinstance(variable, str):

View file

@ -36,7 +36,7 @@ class LLMCreator(LangChainTypeCreator):
return [
llm.__name__
for llm in self.type_to_loader_dict.values()
if llm.__name__ in settings.llms or settings.dev
if llm.__name__ in settings.LLMS or settings.DEV
]

View file

@ -51,7 +51,7 @@ class MemoryCreator(LangChainTypeCreator):
return [
memory.__name__
for memory in self.type_to_loader_dict.values()
if memory.__name__ in settings.memories or settings.dev
if memory.__name__ in settings.MEMORIES or settings.DEV
]

View file

@ -33,7 +33,7 @@ class OutputParserCreator(LangChainTypeCreator):
self.type_dict = {
name: output_parser
for name, output_parser in self.type_dict.items()
if name in settings.output_parsers or settings.dev
if name in settings.OUTPUT_PARSERS or settings.DEV
}
return self.type_dict

View file

@ -34,7 +34,7 @@ class PromptCreator(LangChainTypeCreator):
self.type_dict = {
name: prompt
for name, prompt in self.type_dict.items()
if name in settings.prompts or settings.dev
if name in settings.PROMPTS or settings.DEV
}
return self.type_dict

View file

@ -51,7 +51,7 @@ class RetrieverCreator(LangChainTypeCreator):
return [
retriever
for retriever in self.type_to_loader_dict.keys()
if retriever in settings.retrievers or settings.dev
if retriever in settings.RETRIEVERS or settings.DEV
]

View file

@ -33,7 +33,7 @@ class TextSplitterCreator(LangChainTypeCreator):
return [
textsplitter.__name__
for textsplitter in self.type_to_loader_dict.values()
if textsplitter.__name__ in settings.textsplitters or settings.dev
if textsplitter.__name__ in settings.TEXTSPLITTERS or settings.DEV
]

View file

@ -35,7 +35,7 @@ class ToolkitCreator(LangChainTypeCreator):
)
# if toolkit_name is not lower case it is a class
for toolkit_name in agent_toolkits.__all__
if not toolkit_name.islower() and toolkit_name in settings.toolkits
if not toolkit_name.islower() and toolkit_name in settings.TOOLKITS
}
return self.type_dict

View file

@ -74,7 +74,7 @@ class ToolCreator(LangChainTypeCreator):
tool_name = tool_params.get("name") or tool
if tool_name in settings.tools or settings.dev:
if tool_name in settings.TOOLS or settings.DEV:
if tool_name == "JsonSpec":
tool_params["path"] = tool_params.pop("dict_") # type: ignore
all_tools[tool_name] = {

View file

@ -1,3 +1,5 @@
import ast
import contextlib
from typing import Any
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
@ -145,7 +147,7 @@ def add_code_field(template, raw_code, field_config):
"dynamic": True,
"required": True,
"placeholder": "",
"show": True,
"show": field_config.pop("show", True),
"multiline": True,
"value": raw_code,
"password": False,
@ -186,7 +188,7 @@ def build_frontend_node(custom_component: CustomComponent):
return None
def update_display_name_and_description(frontend_node, template_config):
def update_attributes(frontend_node, template_config):
"""Update the display name and description of a frontend node"""
if "display_name" in template_config:
frontend_node["display_name"] = template_config["display_name"]
@ -194,6 +196,9 @@ def update_display_name_and_description(frontend_node, template_config):
if "description" in template_config:
frontend_node["description"] = template_config["description"]
if "beta" in template_config:
frontend_node["beta"] = template_config["beta"]
def build_field_config(custom_component: CustomComponent):
"""Build the field configuration for a custom component"""
@ -247,6 +252,8 @@ def get_field_properties(extra_field):
if not field_required:
field_type = extract_type_from_optional(field_type)
with contextlib.suppress(Exception):
field_value = ast.literal_eval(field_value)
return field_name, field_type, field_value, field_required
@ -274,28 +281,30 @@ def add_base_classes(frontend_node, return_type):
def build_langchain_template_custom_component(custom_component: CustomComponent):
"""Build a custom component template for the langchain"""
logger.debug("Building custom component template")
frontend_node = build_frontend_node(custom_component)
if frontend_node is None:
return None
logger.debug("Built base frontend node")
template_config = custom_component.build_template_config
update_display_name_and_description(frontend_node, template_config)
update_attributes(frontend_node, template_config)
logger.debug("Updated attributes")
field_config = build_field_config(custom_component)
logger.debug("Built field config")
add_extra_fields(
frontend_node, field_config, custom_component.get_function_entrypoint_args
)
logger.debug("Added extra fields")
frontend_node = add_code_field(
frontend_node, custom_component.code, field_config.get("code", {})
)
logger.debug("Added code field")
add_base_classes(
frontend_node, custom_component.get_function_entrypoint_return_type
)
logger.debug("Added base classes")
return frontend_node
@ -306,7 +315,7 @@ def load_files_from_path(path: str):
return reader.get_files()
def build_and_validate_all_files(reader, file_list):
def build_and_validate_all_files(reader: DirectoryReader, file_list):
"""Build and validate all files"""
data = reader.build_component_menu_list(file_list)
@ -319,31 +328,53 @@ def build_and_validate_all_files(reader, file_list):
def build_valid_menu(valid_components):
"""Build the valid menu"""
valid_menu = {}
logger.debug("------------------- VALID COMPONENTS -------------------")
for menu_item in valid_components["menu"]:
menu_name = menu_item["name"]
valid_menu[menu_name] = {}
for component in menu_item["components"]:
logger.debug(f"Building component: {component}")
try:
component_name = component["name"]
component_code = component["code"]
component_output_types = component["output_types"]
component_extractor = CustomComponent(code=component_code)
component_extractor.is_check_valid()
component_template = build_langchain_template_custom_component(
component_extractor
)
component_template["output_types"] = component_output_types
if len(component_output_types) == 1:
component_name = component_output_types[0]
else:
file_name = component.get("file").split(".")[0]
if "_" in file_name:
# turn .py file into camelcase
component_name = "".join(
[word.capitalize() for word in file_name.split("_")]
)
else:
component_name = file_name
valid_menu[menu_name][component_name] = component_template
logger.debug(f"Added {component_name} to valid menu to {menu_name}")
except Exception as exc:
logger.error(f"Error while building custom component: {exc}")
logger.error(f"Error loading Component: {component['output_types']}")
logger.exception(
f"Error while building custom component {component_output_types}: {exc}"
)
return valid_menu
def build_invalid_menu(invalid_components):
"""Build the invalid menu"""
if invalid_components.get("menu"):
logger.debug("------------------- INVALID COMPONENTS -------------------")
invalid_menu = {}
for menu_item in invalid_components["menu"]:
menu_name = menu_item["name"]
@ -364,12 +395,16 @@ def build_invalid_menu(invalid_components):
)
component_template["error"] = component.get("error", None)
logger.debug(component)
logger.debug(f"Component Path: {component.get('path', None)}")
logger.debug(f"Component Error: {component.get('error', None)}")
component_template.get("template").get("code")["value"] = component_code
invalid_menu[menu_name][component_name] = component_template
logger.debug(f"Added {component_name} to invalid menu to {menu_name}")
except Exception as exc:
logger.error(
logger.exception(
f"Error while creating custom component [{component_name}]: {str(exc)}"
)

View file

@ -35,7 +35,7 @@ class UtilityCreator(LangChainTypeCreator):
self.type_dict = {
name: utility
for name, utility in self.type_dict.items()
if name in settings.utilities or settings.dev
if name in settings.UTILITIES or settings.DEV
}
return self.type_dict

View file

@ -69,18 +69,17 @@ def setup_llm_caching():
try:
set_langchain_cache(settings)
except ImportError:
logger.warning(f"Could not import {settings.cache}. ")
logger.warning(f"Could not import {settings.CACHE}. ")
except Exception as exc:
logger.warning(f"Could not setup LLM caching. Error: {exc}")
# TODO Rename this here and in `setup_llm_caching`
def set_langchain_cache(settings):
import langchain
from langflow.interface.importing.utils import import_class
cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE")
cache_class = import_class(f"langchain.cache.{cache_type or settings.cache}")
cache_class = import_class(f"langchain.cache.{cache_type or settings.CACHE}")
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
langchain.llm_cache = cache_class()

View file

@ -46,7 +46,7 @@ class VectorstoreCreator(LangChainTypeCreator):
return [
vectorstore
for vectorstore in self.type_to_loader_dict.keys()
if vectorstore in settings.vectorstores or settings.dev
if vectorstore in settings.VECTORSTORES or settings.DEV
]

View file

@ -6,7 +6,7 @@ from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from langflow.api import router
from langflow.database.base import create_db_and_tables
from langflow.database.base import create_db_and_tables, Engine
from langflow.interface.utils import setup_llm_caching
from langflow.utils.logger import configure
@ -34,6 +34,7 @@ def create_app():
)
app.include_router(router)
app.on_event("startup")(Engine.update)
app.on_event("startup")(create_db_and_tables)
app.on_event("startup")(setup_llm_caching)
return app

View file

@ -1,71 +1,91 @@
import contextlib
import json
import os
from typing import Optional, List
from pathlib import Path
import yaml
from pydantic import BaseSettings, root_validator
from pydantic import BaseSettings, root_validator, validator
from langflow.utils.logger import logger
BASE_COMPONENTS_PATH = Path(__file__).parent / "components"
BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
class Settings(BaseSettings):
chains: dict = {}
agents: dict = {}
prompts: dict = {}
llms: dict = {}
tools: dict = {}
memories: dict = {}
embeddings: dict = {}
vectorstores: dict = {}
documentloaders: dict = {}
wrappers: dict = {}
retrievers: dict = {}
toolkits: dict = {}
textsplitters: dict = {}
utilities: dict = {}
output_parsers: dict = {}
custom_components: dict = {}
CHAINS: dict = {}
AGENTS: dict = {}
PROMPTS: dict = {}
LLMS: dict = {}
TOOLS: dict = {}
MEMORIES: dict = {}
EMBEDDINGS: dict = {}
VECTORSTORES: dict = {}
DOCUMENTLOADERS: dict = {}
WRAPPERS: dict = {}
RETRIEVERS: dict = {}
TOOLKITS: dict = {}
TEXTSPLITTERS: dict = {}
UTILITIES: dict = {}
OUTPUT_PARSERS: dict = {}
CUSTOM_COMPONENTS: dict = {}
dev: bool = False
database_url: Optional[str] = None
cache: str = "InMemoryCache"
remove_api_keys: bool = False
components_path: List[Path]
DEV: bool = False
DATABASE_URL: Optional[str] = None
CACHE: str = "InMemoryCache"
REMOVE_API_KEYS: bool = False
COMPONENTS_PATH: List[str] = []
@root_validator(pre=True)
def set_env_variables(cls, values):
if "database_url" not in values:
@validator("DATABASE_URL", pre=True)
def set_database_url(cls, value):
if not value:
logger.debug(
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
)
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
values["database_url"] = langflow_database_url
value = langflow_database_url
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
else:
logger.debug("No DATABASE_URL env variable, using sqlite database")
values["database_url"] = "sqlite:///./langflow.db"
value = "sqlite:///./langflow.db"
if not values.get("components_path"):
values["components_path"] = [BASE_COMPONENTS_PATH]
logger.debug("No components_path provided, using default components path")
elif BASE_COMPONENTS_PATH not in values["components_path"]:
values["components_path"].append(BASE_COMPONENTS_PATH)
logger.debug("Adding default components path to components_path")
return value
@validator("COMPONENTS_PATH", pre=True)
def set_components_path(cls, value):
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
langflow_component_path = Path(os.getenv("LANGFLOW_COMPONENTS_PATH"))
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
if (
langflow_component_path.exists()
and langflow_component_path not in values["components_path"]
Path(langflow_component_path).exists()
and langflow_component_path not in value
):
values["components_path"].append(langflow_component_path)
logger.debug(f"Adding {langflow_component_path} to components_path")
return values
if isinstance(langflow_component_path, list):
for path in langflow_component_path:
if path not in value:
value.append(path)
logger.debug(
f"Extending {langflow_component_path} to components_path"
)
elif langflow_component_path not in value:
value.append(langflow_component_path)
logger.debug(
f"Appending {langflow_component_path} to components_path"
)
if not value:
value = [BASE_COMPONENTS_PATH]
logger.debug("Setting default components path to components_path")
elif BASE_COMPONENTS_PATH not in value:
value.append(BASE_COMPONENTS_PATH)
logger.debug("Adding default components path to components_path")
logger.debug(f"Components path: {value}")
return value
class Config:
validate_assignment = True
extra = "ignore"
env_prefix = "LANGFLOW_"
@root_validator(allow_reuse=True)
def validate_lists(cls, values):
@ -76,35 +96,50 @@ class Settings(BaseSettings):
def update_from_yaml(self, file_path: str, dev: bool = False):
new_settings = load_settings_from_yaml(file_path)
self.chains = new_settings.chains or {}
self.agents = new_settings.agents or {}
self.prompts = new_settings.prompts or {}
self.llms = new_settings.llms or {}
self.tools = new_settings.tools or {}
self.memories = new_settings.memories or {}
self.wrappers = new_settings.wrappers or {}
self.toolkits = new_settings.toolkits or {}
self.textsplitters = new_settings.textsplitters or {}
self.utilities = new_settings.utilities or {}
self.embeddings = new_settings.embeddings or {}
self.vectorstores = new_settings.vectorstores or {}
self.documentloaders = new_settings.documentloaders or {}
self.retrievers = new_settings.retrievers or {}
self.output_parsers = new_settings.output_parsers or {}
self.custom_components = new_settings.custom_components or {}
self.components_path = new_settings.components_path or []
self.dev = dev
self.CHAINS = new_settings.CHAINS or {}
self.AGENTS = new_settings.AGENTS or {}
self.PROMPTS = new_settings.PROMPTS or {}
self.LLMS = new_settings.LLMS or {}
self.TOOLS = new_settings.TOOLS or {}
self.MEMORIES = new_settings.MEMORIES or {}
self.WRAPPERS = new_settings.WRAPPERS or {}
self.TOOLKITS = new_settings.TOOLKITS or {}
self.TEXTSPLITTERS = new_settings.TEXTSPLITTERS or {}
self.UTILITIES = new_settings.UTILITIES or {}
self.EMBEDDINGS = new_settings.EMBEDDINGS or {}
self.VECTORSTORES = new_settings.VECTORSTORES or {}
self.DOCUMENTLOADERS = new_settings.DOCUMENTLOADERS or {}
self.RETRIEVERS = new_settings.RETRIEVERS or {}
self.OUTPUT_PARSERS = new_settings.OUTPUT_PARSERS or {}
self.CUSTOM_COMPONENTS = new_settings.CUSTOM_COMPONENTS or {}
self.COMPONENTS_PATH = new_settings.COMPONENTS_PATH or []
self.DEV = dev
def update_settings(self, **kwargs):
logger.debug("Updating settings")
for key, value in kwargs.items():
if hasattr(self, key):
if isinstance(getattr(self, key), list):
if isinstance(value, list):
getattr(self, key).extend(value)
else:
getattr(self, key).append(value)
# value may contain sensitive information, so we don't want to log it
if not hasattr(self, key):
logger.debug(f"Key {key} not found in settings")
continue
logger.debug(f"Updating {key}")
if isinstance(getattr(self, key), list):
# value might be a '[something]' string
with contextlib.suppress(json.decoder.JSONDecodeError):
value = json.loads(str(value))
if isinstance(value, list):
for item in value:
if item not in getattr(self, key):
getattr(self, key).append(item)
logger.debug(f"Extended {key}")
else:
setattr(self, key, value)
getattr(self, key).append(value)
logger.debug(f"Appended {key}")
else:
setattr(self, key, value)
logger.debug(f"Updated {key}")
logger.debug(f"{key}: {getattr(self, key)}")
def save_settings_to_yaml(settings: Settings, file_path: str):
@ -123,6 +158,12 @@ def load_settings_from_yaml(file_path: str) -> Settings:
with open(file_path, "r") as f:
settings_dict = yaml.safe_load(f)
settings_dict = {k.upper(): v for k, v in settings_dict.items()}
for key in settings_dict:
if key not in Settings.__fields__.keys():
raise KeyError(f"Key {key} not found in settings")
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
return Settings(**settings_dict)

View file

@ -0,0 +1 @@
**/node_modules

View file

@ -87,7 +87,7 @@ export default function ParameterComponent({
useEffect(() => {
if (name === "openai_api_base") console.log(info);
infoHtml.current = (
<div className="h-full w-full break-words">
<div className="h-full w-full word-break-break-word">
{info.split("\n").map((line, i) => (
<p key={i} className="block">
{line}

View file

@ -36,14 +36,14 @@ export default function SingleAlert({
/>
</div>
<div className="ml-3">
<h3 className="break-words text-sm font-medium text-error-foreground">
<h3 className="text-sm font-medium text-error-foreground word-break-break-word">
{dropItem.title}
</h3>
{dropItem.list ? (
<div className="mt-2 text-sm text-error-foreground">
<ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => (
<li className="break-words" key={idx}>
<li className="word-break-break-word" key={idx}>
{item}
</li>
))}

View file

@ -47,7 +47,9 @@ export default function NoticeAlert({
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground">{title}</p>
<p className="text-sm text-info-foreground word-break-break-word">
{title}
</p>
<p className="mt-3 text-sm md:ml-6 md:mt-0">
{link !== "" ? (
<Link

View file

@ -132,21 +132,25 @@ export default function CodeTabsComponent({
}}
>
<div className="api-modal-tablist-div">
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger
className={
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
}
key={index}
value={index.toString()}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.length > 0 && tabs[0].name !== "" ? (
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger
className={
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
}
key={index}
value={index.toString()}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
) : (
<div></div>
)}
{Number(activeTab) < 4 && (
<div className="float-right mx-1 flex gap-2">
<div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}

View file

@ -180,8 +180,8 @@ export default function CodeAreaModal({
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full break-all text-sm text-status-red">
<pre className="w-full whitespace-pre-wrap break-all">
<div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<pre className="w-full word-break-break-word">
{error?.detail?.traceback}
</pre>
</div>

View file

@ -13,6 +13,7 @@ const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
useContext(TabsContext);
const [checked, setChecked] = useState(false);
const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
const [invalidName, setInvalidName] = useState(false);
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
@ -30,6 +31,8 @@ const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
invalidName={invalidName}
setInvalidName={setInvalidName}
name={name}
description={description}
flows={flows}

View file

@ -88,8 +88,8 @@ export default function ChatMessage({
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary dark:prose-invert
sm:w-[30vw] sm:max-w-[30vw] lg:w-[40vw] lg:max-w-[40vw]"
className="markdown prose min-w-full text-primary word-break-break-word
dark:prose-invert"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
@ -187,7 +187,7 @@ export default function ChatMessage({
}
/>
</button>
<span className="prose inline-block break-words text-primary dark:prose-invert">
<span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;

View file

@ -96,7 +96,7 @@ export default function ButtonBox({
<div className="mb-auto mt-auto w-full">
<h3
className={classNames(
"w-full break-words font-semibold text-background truncate-multiline",
"w-full font-semibold text-background truncate-multiline word-break-break-word",
titleFontSize,
marginTop
)}

View file

@ -301,13 +301,13 @@
}
.error-build-message {
@apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl;
@apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl;
}
.error-build-message-circle {
@apply alert-icon text-status-red;
}
.error-build-text {
@apply text-error-foreground;
@apply text-error-foreground word-break-break-word;
}
.error-build-foreground {
@apply error-build-text alert-font-size;
@ -326,7 +326,7 @@
@apply alert-icon text-status-green;
}
.success-alert-message {
@apply alert-font-size text-success-foreground;
@apply word-break-break-word alert-font-size text-success-foreground;
}
.card-component-title-display {
@ -336,7 +336,7 @@
@apply flex h-7 w-7 items-center justify-center rounded-full text-2xl;
}
.card-component-title-size {
@apply inline-block w-full flex-1 break-words truncate-doubleline;
@apply w-full flex-1 word-break-break-word truncate-doubleline;
}
.card-component-delete-button {
@apply flex self-start;
@ -544,7 +544,7 @@
@apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1;
}
.toggle-component-span {
@apply pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
@apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
}
.toggle-component-second-span {
@apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity;
@ -590,13 +590,13 @@
@apply flex-max-width items-center text-start;
}
.chat-message-modal-text {
@apply relative inline-block w-full text-start text-sm font-normal text-muted-foreground;
@apply relative w-full text-start text-sm font-normal text-muted-foreground;
}
.chat-message-modal-icon-div {
@apply absolute -left-2 -top-1 cursor-pointer;
}
.chat-message-modal-thought {
@apply chat-message-modal-thought-cursor ml-3 inline-block h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
@apply chat-message-modal-thought-cursor ml-3 h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
}
.chat-message-modal-thought-cursor {
@apply cursor-pointer overflow-scroll scrollbar-hide;
@ -608,7 +608,7 @@
@apply mt-1 animate-pulse cursor-default;
}
.chat-message-modal-alert {
@apply inline-block px-3 text-start text-muted-foreground;
@apply px-3 text-start text-muted-foreground;
}
.file-card-modal-image-div {
@ -852,11 +852,11 @@
@apply bg-white text-primary;
}
.code-highlight {
@apply block max-h-[64vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0;
@apply block max-h-[64vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
}
.code-nohighlight {
@apply block max-h-[70vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0;
@apply block max-h-[70vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
}
.form-modal-lockchat {
@apply form-input block w-full rounded-md border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-ring sm:text-sm;
@ -913,7 +913,7 @@
@apply rounded-md border border-ring/60;
}
.form-modal-chat-thought-size {
@apply inline-block h-full w-[95%];
@apply h-full w-[95%];
}
.form-modal-chat-thought {
@apply form-modal-chat-thought-border form-modal-chat-thought-size cursor-pointer overflow-scroll bg-background px-2 py-2 text-start text-primary scrollbar-hide;

View file

@ -155,7 +155,9 @@ module.exports = {
overflow: "hidden",
"text-overflow": "ellipsis",
},
".word-break-break-word": {
wordBreak: "break-word",
},
".arrow-hide": {
"&::-webkit-inner-spin-button": {
"-webkit-appearance": "none",