From a65965fcb3b5af6ee9964f3cffa9e0aaa693f132 Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Tue, 4 Jun 2024 13:35:00 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20(endpoints.py):=20Add=20support?= =?UTF-8?q?=20for=20caching=20components=20to=20improve=20performance=20an?= =?UTF-8?q?d=20reduce=20load=20on=20the=20server=20=F0=9F=93=9D=20(setup.p?= =?UTF-8?q?y):=20Change=20function=20create=5For=5Fupdate=5Fstarter=5Fproj?= =?UTF-8?q?ects=20to=20be=20asynchronous=20to=20handle=20await=20calls=20?= =?UTF-8?q?=F0=9F=93=9D=20(types.py):=20Add=20caching=20mechanism=20to=20f?= =?UTF-8?q?unction=20aget=5Fall=5Fcomponents=20to=20improve=20performance?= =?UTF-8?q?=20and=20reduce=20redundant=20calls=20=F0=9F=93=9D=20(main.py):?= =?UTF-8?q?=20Change=20call=20to=20create=5For=5Fupdate=5Fstarter=5Fprojec?= =?UTF-8?q?ts=20to=20be=20awaited=20to=20handle=20asynchronous=20operation?= =?UTF-8?q?=20=F0=9F=93=9D=20(index.tsx):=20Add=20functionality=20to=20rel?= =?UTF-8?q?oad=20components=20in=20the=20menu=20bar=20to=20update=20compon?= =?UTF-8?q?ent=20data=20dynamically=20=F0=9F=93=9D=20(index.ts):=20Modify?= =?UTF-8?q?=20getAll=20function=20to=20accept=20a=20force=5Frefresh=20para?= =?UTF-8?q?meter=20to=20force=20a=20refresh=20of=20data=20=F0=9F=93=9D=20(?= =?UTF-8?q?typesStore.ts):=20Update=20getTypes=20function=20to=20accept=20?= =?UTF-8?q?a=20force=5Frefresh=20parameter=20and=20pass=20it=20to=20the=20?= =?UTF-8?q?getAll=20function=20=F0=9F=93=9D=20(index.ts):=20Update=20getTy?= =?UTF-8?q?pes=20function=20in=20TypesStoreType=20to=20accept=20a=20force?= =?UTF-8?q?=5Frefresh=20parameter=20=F0=9F=93=9D=20(test=5Finitial=5Fsetup?= =?UTF-8?q?.py):=20Update=20test=5Fcreate=5For=5Fupdate=5Fstarter=5Fprojec?= =?UTF-8?q?ts=20to=20await=20the=20asynchronous=20function=20create=5For?= =?UTF-8?q?=5Fupdate=5Fstarter=5Fprojects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/base/langflow/api/v1/endpoints.py | 20 ++++++++++++--- .../base/langflow/initial_setup/setup.py | 6 ++--- src/backend/base/langflow/interface/types.py | 25 +++++++++++++++++++ src/backend/base/langflow/main.py | 2 +- .../components/menuBar/index.tsx | 20 +++++++++++++++ src/frontend/src/controllers/API/index.ts | 7 ++++-- src/frontend/src/stores/typesStore.ts | 4 +-- src/frontend/src/types/zustand/types/index.ts | 2 +- tests/test_initial_setup.py | 2 +- 9 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index d9cbb1bd3..db89a0e7f 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -1,3 +1,4 @@ +from asyncio import Lock from http import HTTPStatus from typing import TYPE_CHECKING, Annotated, List, Optional, Union from uuid import UUID @@ -32,11 +33,18 @@ from langflow.services.cache.utils import save_uploaded_file from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow, get_flow_by_id from langflow.services.database.models.user.model import User -from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service +from langflow.services.deps import ( + get_cache_service, + get_session, + get_session_service, + get_settings_service, + get_task_service, +) from langflow.services.session.service import SessionService from langflow.services.task.service import TaskService if TYPE_CHECKING: + from langflow.services.cache.base import CacheService from langflow.services.settings.manager import SettingsService router = APIRouter(tags=["Base"]) @@ -45,13 +53,19 @@ router = APIRouter(tags=["Base"]) @router.get("/all", dependencies=[Depends(get_current_active_user)]) async def get_all( settings_service=Depends(get_settings_service), + cache_service: "CacheService" = Depends(dependency=get_cache_service), + force_refresh: bool = False, ): from langflow.interface.types import aget_all_types_dict logger.debug("Building langchain types dict") try: - all_types_dict = await aget_all_types_dict(settings_service.settings.components_path) - return all_types_dict + async with Lock() as lock: + all_types_dict = await cache_service.get(key="all_types_dict", lock=lock) + if not all_types_dict or force_refresh: + all_types_dict = await aget_all_types_dict(settings_service.settings.components_path) + await cache_service.set(key="all_types_dict", value=all_types_dict, lock=lock) + return all_types_dict except Exception as exc: logger.exception(exc) raise HTTPException(status_code=500, detail=str(exc)) from exc diff --git a/src/backend/base/langflow/initial_setup/setup.py b/src/backend/base/langflow/initial_setup/setup.py index 6194c3973..9ed8c8b9f 100644 --- a/src/backend/base/langflow/initial_setup/setup.py +++ b/src/backend/base/langflow/initial_setup/setup.py @@ -14,7 +14,7 @@ from loguru import logger from sqlmodel import select from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES -from langflow.interface.types import get_all_components +from langflow.interface.types import aget_all_components from langflow.services.auth.utils import create_super_user from langflow.services.database.models.flow.model import Flow, FlowCreate from langflow.services.database.models.folder.model import Folder, FolderCreate @@ -364,10 +364,10 @@ def find_existing_flow(session, flow_id, flow_endpoint_name): return None -def create_or_update_starter_projects(): +async def create_or_update_starter_projects(): components_paths = get_settings_service().settings.components_path try: - all_types_dict = get_all_components(components_paths, as_dict=True) + all_types_dict = await aget_all_components(components_paths, as_dict=True) except Exception as e: logger.exception(f"Error loading components: {e}") raise e diff --git a/src/backend/base/langflow/interface/types.py b/src/backend/base/langflow/interface/types.py index 812b1e78f..2fe796c65 100644 --- a/src/backend/base/langflow/interface/types.py +++ b/src/backend/base/langflow/interface/types.py @@ -1,3 +1,7 @@ +import json + +from cachetools import TTLCache, cached + from langflow.custom.utils import abuild_custom_components, build_custom_components @@ -13,6 +17,27 @@ def get_all_types_dict(components_paths): return custom_components_from_file +# TypeError: unhashable type: 'list' +def key_func(*args, **kwargs): + # components_paths is a list of paths + return json.dumps(args) + json.dumps(kwargs) + + +@cached(cache=TTLCache(maxsize=1, ttl=15), key=key_func) +async def aget_all_components(components_paths, as_dict=False): + """Get all components names combining native and custom components.""" + all_types_dict = await aget_all_types_dict(components_paths) + components = {} if as_dict else [] + for category in all_types_dict.values(): + for component in category.values(): + component["name"] = component["display_name"] + if as_dict: + components[component["name"]] = component + else: + components.append(component) + return components + + def get_all_components(components_paths, as_dict=False): """Get all components names combining native and custom components.""" all_types_dict = get_all_types_dict(components_paths) diff --git a/src/backend/base/langflow/main.py b/src/backend/base/langflow/main.py index c81c014e2..bb4a4200e 100644 --- a/src/backend/base/langflow/main.py +++ b/src/backend/base/langflow/main.py @@ -51,7 +51,7 @@ def get_lifespan(fix_migration=False, socketio_server=None, version=None): setup_llm_caching() LangfuseInstance.update() initialize_super_user_if_needed() - create_or_update_starter_projects() + await create_or_update_starter_projects() load_flows_from_directory() yield except Exception as exc: diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index 7f7130090..37c57a2b5 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -16,6 +16,7 @@ import FlowSettingsModal from "../../../../modals/flowSettingsModal"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { useTypesStore } from "../../../../stores/typesStore"; import { cn } from "../../../../utils/utils"; import IconComponent from "../../../genericIconComponent"; import ShadTooltip from "../../../shadTooltipComponent"; @@ -34,6 +35,7 @@ export const MenuBar = ({}: {}): JSX.Element => { const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const navigate = useNavigate(); const isBuilding = useFlowStore((state) => state.isBuilding); + const getTypes = useTypesStore((state) => state.getTypes); function handleAddFlow(duplicate?: boolean) { try { @@ -55,6 +57,12 @@ export const MenuBar = ({}: {}): JSX.Element => { } } + function handleReloadComponents() { + getTypes(true).then(() => { + setSuccessData({ title: "Components reloaded successfully" }); + }); + } + function printByBuildStatus() { if (isBuilding) { return "Building..."; @@ -188,6 +196,18 @@ export const MenuBar = ({}: {}): JSX.Element => { )} Y + { + handleReloadComponents(); + }} + className="cursor-pointer" + > + + Reload Components + >} A promise that resolves to an AxiosResponse containing all the objects. */ -export async function getAll(): Promise> { - return await api.get(`${BASE_URL_API}all`); +export async function getAll( + force_refresh: boolean = true +): Promise> { + return await api.get(`${BASE_URL_API}all?force_refresh=${force_refresh}`); } const GITHUB_API_URL = "https://api.github.com"; diff --git a/src/frontend/src/stores/typesStore.ts b/src/frontend/src/stores/typesStore.ts index c92236e03..cc73b15d6 100644 --- a/src/frontend/src/stores/typesStore.ts +++ b/src/frontend/src/stores/typesStore.ts @@ -21,11 +21,11 @@ export const useTypesStore = create((set, get) => ({ types: {}, templates: {}, data: {}, - getTypes: () => { + getTypes: (force_refresh: boolean = false) => { return new Promise(async (resolve, reject) => { const setLoading = useFlowsManagerStore.getState().setIsLoading; setLoading(true); - getAll() + getAll(force_refresh) .then((response) => { const data = response?.data; useAlertStore.setState({ loading: false }); diff --git a/src/frontend/src/types/zustand/types/index.ts b/src/frontend/src/types/zustand/types/index.ts index 4f817de47..1f2fa1ed2 100644 --- a/src/frontend/src/types/zustand/types/index.ts +++ b/src/frontend/src/types/zustand/types/index.ts @@ -7,7 +7,7 @@ export type TypesStoreType = { setTemplates: (newState: {}) => void; data: APIDataType; setData: (newState: {}) => void; - getTypes: () => Promise; + getTypes: (force_refresh?: boolean) => Promise; ComponentFields: Set; setComponentFields: (fields: Set) => void; addComponentField: (field: string) => void; diff --git a/tests/test_initial_setup.py b/tests/test_initial_setup.py index 9773b9ca4..dfce6e2c6 100644 --- a/tests/test_initial_setup.py +++ b/tests/test_initial_setup.py @@ -46,7 +46,7 @@ def test_get_project_data(): async def test_create_or_update_starter_projects(client): with session_scope() as session: # Run the function to create or update projects - create_or_update_starter_projects() + await create_or_update_starter_projects() # Get the number of projects returned by load_starter_projects num_projects = len(load_starter_projects())