From 34a602edc30a16f07235a65a7d567a404c283897 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Sun, 5 Nov 2023 21:01:43 -0500 Subject: [PATCH] [add] basic plugin support --- minyma/__init__.py | 4 +- minyma/plugin.py | 86 ++++++++++++++++++++++++++++++++++++ minyma/plugins/README.md | 3 ++ minyma/plugins/duckduckgo.py | 21 +++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 minyma/plugin.py create mode 100644 minyma/plugins/README.md create mode 100644 minyma/plugins/duckduckgo.py diff --git a/minyma/__init__.py b/minyma/__init__.py index 44b388d..5c1de39 100644 --- a/minyma/__init__.py +++ b/minyma/__init__.py @@ -3,6 +3,7 @@ import click import signal import sys from importlib.metadata import version +from minyma.plugin import PluginLoader from minyma.oai import OpenAIConnector from minyma.vdb import ChromaDB from flask import Flask @@ -15,7 +16,7 @@ def signal_handler(sig, frame): def create_app(): - global oai, vdb + global oai, vdb, pman from minyma.config import Config import minyma.api.common as api_common @@ -24,6 +25,7 @@ def create_app(): app = Flask(__name__) vdb = ChromaDB(path.join(Config.DATA_PATH, "chroma")) oai = OpenAIConnector(Config.OPENAI_API_KEY, vdb) + pman = PluginLoader() app.register_blueprint(api_common.bp) app.register_blueprint(api_v1.bp) diff --git a/minyma/plugin.py b/minyma/plugin.py new file mode 100644 index 0000000..565f4e9 --- /dev/null +++ b/minyma/plugin.py @@ -0,0 +1,86 @@ +import inspect +import os +import importlib.util + +TYPE_DEFS = {str:"string"} + +class MinymaPlugin: + pass + +class PluginLoader: + def __init__(self): + self.plugins = self.get_plugins() + self.definitions = self.derive_function_definitions() + print(self.definitions) + + + def derive_function_definitions(self): + """Dynamically generate function definitions""" + function_definitions = [] + + for plugin in self.plugins: + plugin_name = plugin.name + + for method_obj in plugin.functions: + method_name = method_obj.__name__ + signature = inspect.signature(method_obj) + parameters = list(signature.parameters.values()) + + # Extract Parameter Information + params_info = {} + for param in parameters: + param_name = param.name + param_type = param.annotation + params_info[param_name] = {"type": TYPE_DEFS[param_type]} + + # Store Function Information + method_info = { + "name": "%s_%s" %(plugin_name, method_name), + "description": inspect.getdoc(method_obj), + "parameters": { + "type": "object", + "properties": params_info + } + } + function_definitions.append(method_info) + + return function_definitions + + + def get_plugins(self): + """Dynamically load plugins""" + # Derive Plugin Folder + loader_dir = os.path.dirname(os.path.abspath(__file__)) + plugin_folder = os.path.join(loader_dir, "plugins") + + # Find Minyma Plugins + plugin_classes = [] + for filename in os.listdir(plugin_folder): + + # Exclude Files + if not filename.endswith(".py") or filename == "__init__.py": + continue + + # Derive Module Path + module_name = os.path.splitext(filename)[0] + module_path = os.path.join(plugin_folder, filename) + + # Load Module Dynamically + spec = importlib.util.spec_from_file_location(module_name, module_path) + if spec is None or spec.loader is None: + raise ImportError("Unable to dynamically load plugin - %s" % filename) + + # Load & Exec Module + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Only Process MinymaPlugin SubClasses + for _, member in inspect.getmembers(module): + if inspect.isclass(member) and issubclass(member, MinymaPlugin) and member != MinymaPlugin: + plugin_classes.append(member) + + # Instantiate Plugins + plugins = [] + for cls in plugin_classes: + plugins.append(cls()) + return plugins diff --git a/minyma/plugins/README.md b/minyma/plugins/README.md new file mode 100644 index 0000000..9cec193 --- /dev/null +++ b/minyma/plugins/README.md @@ -0,0 +1,3 @@ +# Plugins + +These are plugins that provide OpenAI with functions. Each plugin can define multiple plugins. The plugin loader will automatically derive the function definition. Each function will have the plugin name prepended. diff --git a/minyma/plugins/duckduckgo.py b/minyma/plugins/duckduckgo.py new file mode 100644 index 0000000..52f889e --- /dev/null +++ b/minyma/plugins/duckduckgo.py @@ -0,0 +1,21 @@ +from minyma.plugin import MinymaPlugin + +class DuckDuckGoPlugin(MinymaPlugin): + """Search DuckDuckGo""" + + def __init__(self): + self.name = "duck_duck_go" + self.functions = [self.search] + + def search(self, query: str): + """Search DuckDuckGo""" + + """ + TODO: + 1. API Query + 2. Parse (BeautifulSoup?) + 3. Return top 10 (Title & Blurb?) + + ENDPOINT: https://html.duckduckgo.com/html/?q=%s + """ + pass