diff --git a/src/backend/langflow/services/getters.py b/src/backend/langflow/services/getters.py index 1da9b8258..769336bbb 100644 --- a/src/backend/langflow/services/getters.py +++ b/src/backend/langflow/services/getters.py @@ -9,9 +9,14 @@ if TYPE_CHECKING: from langflow.services.session.service import SessionService from langflow.services.task.service import TaskService from langflow.services.chat.service import ChatService + from langflow.services.plugins.service import PluginService from sqlmodel import Session +def get_plugins_service() -> "PluginService": + return service_manager.get(ServiceType.PLUGIN_SERVICE) + + def get_settings_service() -> "SettingsService": try: return service_manager.get(ServiceType.SETTINGS_SERVICE) diff --git a/src/backend/langflow/services/plugins/base.py b/src/backend/langflow/services/plugins/base.py new file mode 100644 index 000000000..d5a457b66 --- /dev/null +++ b/src/backend/langflow/services/plugins/base.py @@ -0,0 +1,12 @@ +from typing import Any + + +class BasePlugin: + def initialize(self): + pass + + def teardown(self): + pass + + def get(self) -> Any: + pass diff --git a/src/backend/langflow/services/plugins/factory.py b/src/backend/langflow/services/plugins/factory.py new file mode 100644 index 000000000..2205786ed --- /dev/null +++ b/src/backend/langflow/services/plugins/factory.py @@ -0,0 +1,16 @@ +from typing import TYPE_CHECKING +from langflow.services.plugins.service import PluginService +from langflow.services.factory import ServiceFactory + +if TYPE_CHECKING: + from langflow.services.settings.service import SettingsService + + +class PluginServiceFactory(ServiceFactory): + def __init__(self): + super().__init__(PluginService) + + def create(self, settings_service: "SettingsService"): + service = PluginService(settings_service) + service.load_plugins() + return service diff --git a/src/backend/langflow/services/plugins/langfuse.py b/src/backend/langflow/services/plugins/langfuse.py index 7a1f60a48..08b9176a5 100644 --- a/src/backend/langflow/services/plugins/langfuse.py +++ b/src/backend/langflow/services/plugins/langfuse.py @@ -1,4 +1,5 @@ from langflow.services.getters import get_settings_service +from langflow.services.plugins.base import BasePlugin from langflow.utils.logger import logger ### Temporary implementation @@ -52,3 +53,14 @@ class LangfuseInstance: if cls._instance is not None: cls._instance.flush() cls._instance = None + + +class LangfusePlugin(BasePlugin): + def initialize(self): + LangfuseInstance.create() + + def teardown(self): + LangfuseInstance.teardown() + + def get(self): + return LangfuseInstance.get() diff --git a/src/backend/langflow/services/plugins/service.py b/src/backend/langflow/services/plugins/service.py new file mode 100644 index 000000000..187ec48b6 --- /dev/null +++ b/src/backend/langflow/services/plugins/service.py @@ -0,0 +1,50 @@ +import importlib +import inspect +import os +from langflow.services.base import Service +from langflow.services.plugins.base import BasePlugin +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from langflow.services.settings.service import SettingsService + + +class PluginService(Service): + name = "plugin_service" + + def __init__(self, settings_service: "SettingsService"): + self.plugins = {} + plugin_dir = settings_service.settings.PLUGIN_DIR + self.plugin_dir = plugin_dir or os.path.dirname(__file__) + + def load_plugins(self): + base_files = ["base.py", "service.py", "factory.py", "__init__.py"] + for module in os.listdir(self.plugin_dir): + if module.endswith(".py") and module not in base_files: + plugin_name = module[:-3] + module_path = f"{self.plugin_dir}.{plugin_name}" + mod = importlib.import_module(module_path) + for attr_name in dir(mod): + attr = getattr(mod, attr_name) + if ( + inspect.isclass(attr) + and issubclass(attr, BasePlugin) + and attr is not BasePlugin + ): + self.register_plugin(plugin_name, attr()) + + def register_plugin(self, plugin_name, plugin_instance): + self.plugins[plugin_name] = plugin_instance + plugin_instance.initialize() + + def get_plugin(self, plugin_name) -> Union[BasePlugin, None]: + return self.plugins.get(plugin_name) + + def get(self, plugin_name): + if plugin := self.get_plugin(plugin_name): + return plugin.get() + return None + + def teardown(self): + for plugin in self.plugins.values(): + plugin.teardown() diff --git a/src/backend/langflow/services/schema.py b/src/backend/langflow/services/schema.py index 8b3b41fcb..8194c9125 100644 --- a/src/backend/langflow/services/schema.py +++ b/src/backend/langflow/services/schema.py @@ -14,3 +14,4 @@ class ServiceType(str, Enum): CHAT_SERVICE = "chat_service" SESSION_SERVICE = "session_service" TASK_SERVICE = "task_service" + PLUGIN_SERVICE = "plugin_service" diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index 14e3f9928..779668a11 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -48,6 +48,8 @@ class Settings(BaseSettings): REDIS_DB: int = 0 REDIS_CACHE_EXPIRE: int = 3600 + PLUGIN_DIR: Optional[str] = None + LANGFUSE_SECRET_KEY: Optional[str] = None LANGFUSE_PUBLIC_KEY: Optional[str] = None LANGFUSE_HOST: Optional[str] = None diff --git a/src/backend/langflow/services/utils.py b/src/backend/langflow/services/utils.py index adbf072fe..d055a925e 100644 --- a/src/backend/langflow/services/utils.py +++ b/src/backend/langflow/services/utils.py @@ -19,6 +19,7 @@ def get_factories_and_deps(): from langflow.services.auth import factory as auth_factory from langflow.services.task import factory as task_factory from langflow.services.session import factory as session_service_factory # type: ignore + from langflow.services.plugins import factory as plugins_factory return [ (settings_factory.SettingsServiceFactory(), []), @@ -40,6 +41,7 @@ def get_factories_and_deps(): session_service_factory.SessionServiceFactory(), [ServiceType.CACHE_SERVICE], ), + (plugins_factory.PluginServiceFactory(), [ServiceType.SETTINGS_SERVICE]), ]