diff --git a/.env.example b/.env.example index b3e592bdf..ccab7c28e 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,14 @@ LANGFLOW_REMOVE_API_KEYS= # LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600) LANGFLOW_CACHE_TYPE= +# Auto login +# If set to true then a superuser will be logged in automatically +# and the login page will be skipped, keeping the +# default experience of Langflow +# Values: true, false +# Example: LANGFLOW_AUTO_LOGIN=true +LANGFLOW_AUTO_LOGIN= + # Superuser username # Example: LANGFLOW_SUPERUSER=admin LANGFLOW_SUPERUSER= diff --git a/pyproject.toml b/pyproject.toml index 70c291bdc..e968f1e79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ langfuse = "^1.0.13" pillow = "^10.0.0" metal-sdk = "^2.2.0" markupsafe = "^2.1.3" +numexpr = "^2.8.6" [tool.poetry.group.dev.dependencies] diff --git a/src/backend/langflow/components/llms/HuggingFaceEndpoints.py b/src/backend/langflow/components/llms/HuggingFaceEndpoints.py index ea2b4f20b..0d28d5b9b 100644 --- a/src/backend/langflow/components/llms/HuggingFaceEndpoints.py +++ b/src/backend/langflow/components/llms/HuggingFaceEndpoints.py @@ -1,6 +1,6 @@ from typing import Optional from langflow import CustomComponent -from langchain.llms import HuggingFaceEndpoint +from langchain.llms.huggingface_endpoint import HuggingFaceEndpoint from langchain.llms.base import BaseLLM @@ -13,7 +13,6 @@ class HuggingFaceEndpointsComponent(CustomComponent): "endpoint_url": {"display_name": "Endpoint URL", "password": True}, "task": { "display_name": "Task", - "type": "select", "options": ["text2text-generation", "text-generation", "summarization"], }, "huggingfacehub_api_token": {"display_name": "API token", "password": True}, @@ -27,7 +26,7 @@ class HuggingFaceEndpointsComponent(CustomComponent): def build( self, endpoint_url: str, - task="text2text-generation", + task: str = "text2text-generation", huggingfacehub_api_token: Optional[str] = None, model_kwargs: Optional[dict] = None, ) -> BaseLLM: @@ -36,6 +35,7 @@ class HuggingFaceEndpointsComponent(CustomComponent): endpoint_url=endpoint_url, task=task, huggingfacehub_api_token=huggingfacehub_api_token, + model_kwargs=model_kwargs, ) except Exception as e: raise ValueError("Could not connect to HuggingFace Endpoints API.") from e diff --git a/src/backend/langflow/field_typing/__init__.py b/src/backend/langflow/field_typing/__init__.py index 927716b11..9a2161d3f 100644 --- a/src/backend/langflow/field_typing/__init__.py +++ b/src/backend/langflow/field_typing/__init__.py @@ -1,3 +1,53 @@ -from .base import NestedDict +# LANGCHAIN_BASE_TYPES = { +# "Chain": Chain, +# "AgentExecutor": AgentExecutor, +# "Tool": Tool, +# "BaseLLM": BaseLLM, +# "PromptTemplate": PromptTemplate, +# "BaseLoader": BaseLoader, +# "Document": Document, +# "TextSplitter": TextSplitter, +# "VectorStore": VectorStore, +# "Embeddings": Embeddings, +# "BaseRetriever": BaseRetriever, +# "BaseOutputParser": BaseOutputParser, +# "BaseMemory": BaseMemory, +# "BaseChatMemory": BaseChatMemory, +# } +from .constants import ( + Tool, + PromptTemplate, + Chain, + BaseChatMemory, + BaseLLM, + BaseLoader, + BaseMemory, + BaseOutputParser, + BaseRetriever, + VectorStore, + Embeddings, + TextSplitter, + Document, + AgentExecutor, + NestedDict, + Data, +) -__all__ = ["NestedDict"] +__all__ = [ + "NestedDict", + "Data", + "Tool", + "PromptTemplate", + "Chain", + "BaseChatMemory", + "BaseLLM", + "BaseLoader", + "BaseMemory", + "BaseOutputParser", + "BaseRetriever", + "VectorStore", + "Embeddings", + "TextSplitter", + "Document", + "AgentExecutor", +] diff --git a/src/backend/langflow/field_typing/base.py b/src/backend/langflow/field_typing/base.py deleted file mode 100644 index ed3219888..000000000 --- a/src/backend/langflow/field_typing/base.py +++ /dev/null @@ -1,4 +0,0 @@ -from typing import Union, Dict - -# Type alias for more complex dicts -NestedDict = Dict[str, Union[str, Dict]] diff --git a/src/backend/langflow/field_typing/constants.py b/src/backend/langflow/field_typing/constants.py new file mode 100644 index 000000000..3ce429548 --- /dev/null +++ b/src/backend/langflow/field_typing/constants.py @@ -0,0 +1,50 @@ +from langchain.agents.agent import AgentExecutor +from langchain.chains.base import Chain +from langchain.document_loaders.base import BaseLoader +from langchain.llms.base import BaseLLM +from langchain.memory.chat_memory import BaseChatMemory +from langchain.prompts import PromptTemplate +from langchain.schema import BaseOutputParser, BaseRetriever, Document +from langchain.schema.embeddings import Embeddings +from langchain.schema.memory import BaseMemory +from langchain.text_splitter import TextSplitter +from langchain.tools import Tool +from langchain.vectorstores.base import VectorStore +from typing import Union, Dict + +# Type alias for more complex dicts +NestedDict = Dict[str, Union[str, Dict]] + + +class Data: + pass + + +LANGCHAIN_BASE_TYPES = { + "Chain": Chain, + "AgentExecutor": AgentExecutor, + "Tool": Tool, + "BaseLLM": BaseLLM, + "PromptTemplate": PromptTemplate, + "BaseLoader": BaseLoader, + "Document": Document, + "TextSplitter": TextSplitter, + "VectorStore": VectorStore, + "Embeddings": Embeddings, + "BaseRetriever": BaseRetriever, + "BaseOutputParser": BaseOutputParser, + "BaseMemory": BaseMemory, + "BaseChatMemory": BaseChatMemory, +} +# Langchain base types plus Python base types +CUSTOM_COMPONENT_SUPPORTED_TYPES = { + **LANGCHAIN_BASE_TYPES, + "str": str, + "int": int, + "float": float, + "bool": bool, + "list": list, + "dict": dict, + "NestedDict": NestedDict, + "Data": Data, +} diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index 8ae0bb5f8..de64b3598 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -216,6 +216,16 @@ class Vertex: } elif isinstance(_value, dict): params[key] = _value + elif value.get("type") == "int" and value.get("value") is not None: + try: + params[key] = int(value.get("value")) + except ValueError: + params[key] = value.get("value") + elif value.get("type") == "float" and value.get("value") is not None: + try: + params[key] = float(value.get("value")) + except ValueError: + params[key] = value.get("value") else: params[key] = value.get("value") diff --git a/src/backend/langflow/interface/custom/constants.py b/src/backend/langflow/interface/custom/constants.py index 58ecef637..6df72500c 100644 --- a/src/backend/langflow/interface/custom/constants.py +++ b/src/backend/langflow/interface/custom/constants.py @@ -1,65 +1,33 @@ -from langchain.prompts import PromptTemplate -from langchain.chains.base import Chain -from langchain.document_loaders.base import BaseLoader -from langchain.schema.embeddings import Embeddings -from langchain.llms.base import BaseLLM -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 -from langchain.schema.memory import BaseMemory -from langchain.memory.chat_memory import BaseChatMemory -from langchain.agents.agent import AgentExecutor - -LANGCHAIN_BASE_TYPES = { - "Chain": Chain, - "AgentExecutor": AgentExecutor, - "Tool": Tool, - "BaseLLM": BaseLLM, - "PromptTemplate": PromptTemplate, - "BaseLoader": BaseLoader, - "Document": Document, - "TextSplitter": TextSplitter, - "VectorStore": VectorStore, - "Embeddings": Embeddings, - "BaseRetriever": BaseRetriever, - "BaseOutputParser": BaseOutputParser, - "BaseMemory": BaseMemory, - "BaseChatMemory": BaseChatMemory, -} - -# Langchain base types plus Python base types -CUSTOM_COMPONENT_SUPPORTED_TYPES = { - **LANGCHAIN_BASE_TYPES, - "str": str, - "int": int, - "float": float, - "bool": bool, - "list": list, - "dict": dict, -} - - DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent -from langchain.llms.base import BaseLLM -from langchain.chains import LLMChain -from langchain.prompts import PromptTemplate -from langchain.schema import Document +from langflow.field_typing import ( + Tool, + PromptTemplate, + Chain, + BaseChatMemory, + BaseLLM, + BaseLoader, + BaseMemory, + BaseOutputParser, + BaseRetriever, + VectorStore, + Embeddings, + TextSplitter, + Document, + AgentExecutor, + NestedDict, + Data, +) -import requests -class YourComponent(CustomComponent): +class Component(CustomComponent): display_name: str = "Custom Component" description: str = "Create any custom component you want!" def build_config(self): - return { "url": { "multiline": True, "required": True } } + return {"param": {"display_name": "Parameter"}} + + def build(self, param: Data) -> Data: + return param - def build(self, url: str, llm: BaseLLM, prompt: PromptTemplate) -> Document: - response = requests.get(url) - chain = LLMChain(llm=llm, prompt=prompt) - result = chain.run(response.text[:300]) - return Document(page_content=str(result)) """ diff --git a/src/backend/langflow/interface/custom/custom_component.py b/src/backend/langflow/interface/custom/custom_component.py index ccc0b08b2..4b889e77b 100644 --- a/src/backend/langflow/interface/custom/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component.py @@ -1,7 +1,7 @@ from typing import Any, Callable, List, Optional, Union from uuid import UUID from fastapi import HTTPException -from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES +from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.component import Component from langflow.interface.custom.directory_reader import DirectoryReader from langflow.services.getters import get_db_service @@ -108,6 +108,9 @@ class CustomComponent(Component, extra=Extra.allow): ), }, ) + elif not arg.get("type"): + # Set the type to Data + arg["type"] = "Data" return args @property diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 6708c11c9..0046c84fb 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -4,7 +4,7 @@ from typing import Any, List from langflow.api.utils import get_new_key from langflow.interface.agents.base import agent_creator from langflow.interface.chains.base import chain_creator -from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES +from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.utils import extract_inner_type from langflow.interface.document_loaders.base import documentloader_creator from langflow.interface.embeddings.base import embedding_creator @@ -288,6 +288,24 @@ def add_base_classes(frontend_node, return_types: List[str]): frontend_node.get("base_classes").append(base_class) +def add_output_types(frontend_node, return_types: List[str]): + """Add output types to the frontend node""" + for return_type in return_types: + if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None: + raise HTTPException( + status_code=400, + detail={ + "error": ( + "Invalid return type should be one of: " + f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}" + ), + "traceback": traceback.format_exc(), + }, + ) + + frontend_node.get("output_types").append(return_type) + + def build_langchain_template_custom_component(custom_component: CustomComponent): """Build a custom component template for the langchain""" try: @@ -314,6 +332,9 @@ def build_langchain_template_custom_component(custom_component: CustomComponent) add_base_classes( frontend_node, custom_component.get_function_entrypoint_return_type ) + add_output_types( + frontend_node, custom_component.get_function_entrypoint_return_type + ) logger.debug("Added base classes") return frontend_node except Exception as exc: diff --git a/src/backend/langflow/processing/base.py b/src/backend/langflow/processing/base.py index e5816306c..6c56a59dd 100644 --- a/src/backend/langflow/processing/base.py +++ b/src/backend/langflow/processing/base.py @@ -34,7 +34,9 @@ def get_langfuse_callback(trace_id): if langfuse := LangfuseInstance.get(): logger.debug("Langfuse credentials found") try: - trace = langfuse.trace(CreateTrace(id=trace_id)) + trace = langfuse.trace( + CreateTrace(name="langflow-" + trace_id, id=trace_id) + ) return trace.getNewHandler() except Exception as exc: logger.error(f"Error initializing langfuse callback: {exc}") diff --git a/src/backend/langflow/template/frontend_node/custom_components.py b/src/backend/langflow/template/frontend_node/custom_components.py index 4f36a1c9f..e239775bc 100644 --- a/src/backend/langflow/template/frontend_node/custom_components.py +++ b/src/backend/langflow/template/frontend_node/custom_components.py @@ -2,6 +2,7 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template from langflow.interface.custom.constants import DEFAULT_CUSTOM_COMPONENT_CODE +from typing import Optional class CustomComponentFrontendNode(FrontendNode): @@ -24,7 +25,7 @@ class CustomComponentFrontendNode(FrontendNode): ) ], ) - description: str = "Create any custom component you want!" + description: Optional[str] = None base_classes: list[str] = [] def to_dict(self): diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index b563c5973..c23c559e4 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -191,7 +191,9 @@ def get_base_classes(cls): """Get the base classes of a class. These are used to determine the output of the nodes. """ - if bases := cls.__bases__: + + if hasattr(cls, "__bases__") and cls.__bases__: + bases = cls.__bases__ result = [] for base in bases: if any(type in base.__module__ for type in ["pydantic", "abc"]): diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 620894594..cba2376b5 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -260,9 +260,6 @@ export function TabsProvider({ children }: { children: ReactNode }) { // simulate a click on the link element to trigger the download link.click(); - setNoticeData({ - title: "Warning: Critical data, JSON file may include API keys.", - }); } function downloadFlows() { diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index 01b655b39..89bc292c9 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -4,6 +4,7 @@ import IconComponent from "../../components/genericIconComponent"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants"; +import { alertContext } from "../../contexts/alertContext"; import { TabsContext } from "../../contexts/tabsContext"; import { removeApiKeys } from "../../utils/reactflowUtils"; import BaseModal from "../baseModal"; @@ -11,7 +12,8 @@ import BaseModal from "../baseModal"; const ExportModal = forwardRef( (props: { children: ReactNode }, ref): JSX.Element => { const { flows, tabId, downloadFlow } = useContext(TabsContext); - const [checked, setChecked] = useState(false); + const { setNoticeData } = useContext(alertContext); + const [checked, setChecked] = useState(true); const flow = flows.find((f) => f.id === tabId); useEffect(() => { setName(flow!.name); @@ -44,6 +46,7 @@ const ExportModal = forwardRef(
{ setChecked(event); }} @@ -52,18 +55,26 @@ const ExportModal = forwardRef( Save with my API keys
+ + Caution: Uncheck this box only removes API keys from fields + specifically designated for API keys. +