Selaa lähdekoodia

enh: tools & functions frontmatter

Timothy J. Baek 10 kuukautta sitten
vanhempi
commit
abf212c28f

+ 3 - 1
backend/apps/webui/main.py

@@ -109,7 +109,9 @@ async def get_pipe_models():
     for pipe in pipes:
     for pipe in pipes:
         # Check if function is already loaded
         # Check if function is already loaded
         if pipe.id not in app.state.FUNCTIONS:
         if pipe.id not in app.state.FUNCTIONS:
-            function_module, function_type = load_function_module_by_id(pipe.id)
+            function_module, function_type, frontmatter = load_function_module_by_id(
+                pipe.id
+            )
             app.state.FUNCTIONS[pipe.id] = function_module
             app.state.FUNCTIONS[pipe.id] = function_module
         else:
         else:
             function_module = app.state.FUNCTIONS[pipe.id]
             function_module = app.state.FUNCTIONS[pipe.id]

+ 1 - 0
backend/apps/webui/models/functions.py

@@ -39,6 +39,7 @@ class Function(Model):
 
 
 class FunctionMeta(BaseModel):
 class FunctionMeta(BaseModel):
     description: Optional[str] = None
     description: Optional[str] = None
+    manifest: Optional[dict] = {}
 
 
 
 
 class FunctionModel(BaseModel):
 class FunctionModel(BaseModel):

+ 1 - 0
backend/apps/webui/models/tools.py

@@ -38,6 +38,7 @@ class Tool(Model):
 
 
 class ToolMeta(BaseModel):
 class ToolMeta(BaseModel):
     description: Optional[str] = None
     description: Optional[str] = None
+    manifest: Optional[dict] = {}
 
 
 
 
 class ToolModel(BaseModel):
 class ToolModel(BaseModel):

+ 99 - 95
backend/apps/webui/routers/functions.py

@@ -69,7 +69,10 @@ async def create_new_function(
             with open(function_path, "w") as function_file:
             with open(function_path, "w") as function_file:
                 function_file.write(form_data.content)
                 function_file.write(form_data.content)
 
 
-            function_module, function_type = load_function_module_by_id(form_data.id)
+            function_module, function_type, frontmatter = load_function_module_by_id(
+                form_data.id
+            )
+            form_data.meta.manifest = frontmatter
 
 
             FUNCTIONS = request.app.state.FUNCTIONS
             FUNCTIONS = request.app.state.FUNCTIONS
             FUNCTIONS[form_data.id] = function_module
             FUNCTIONS[form_data.id] = function_module
@@ -117,6 +120,97 @@ async def get_function_by_id(id: str, user=Depends(get_admin_user)):
         )
         )
 
 
 
 
+############################
+# ToggleFunctionById
+############################
+
+
+@router.post("/id/{id}/toggle", response_model=Optional[FunctionModel])
+async def toggle_function_by_id(id: str, user=Depends(get_admin_user)):
+    function = Functions.get_function_by_id(id)
+    if function:
+        function = Functions.update_function_by_id(
+            id, {"is_active": not function.is_active}
+        )
+
+        if function:
+            return function
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateFunctionById
+############################
+
+
+@router.post("/id/{id}/update", response_model=Optional[FunctionModel])
+async def update_function_by_id(
+    request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
+):
+    function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
+
+    try:
+        with open(function_path, "w") as function_file:
+            function_file.write(form_data.content)
+
+        function_module, function_type, frontmatter = load_function_module_by_id(id)
+        form_data.meta.manifest = frontmatter
+
+        FUNCTIONS = request.app.state.FUNCTIONS
+        FUNCTIONS[id] = function_module
+
+        updated = {**form_data.model_dump(exclude={"id"}), "type": function_type}
+        print(updated)
+
+        function = Functions.update_function_by_id(id, updated)
+
+        if function:
+            return function
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
+            )
+
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+############################
+# DeleteFunctionById
+############################
+
+
+@router.delete("/id/{id}/delete", response_model=bool)
+async def delete_function_by_id(
+    request: Request, id: str, user=Depends(get_admin_user)
+):
+    result = Functions.delete_function_by_id(id)
+
+    if result:
+        FUNCTIONS = request.app.state.FUNCTIONS
+        if id in FUNCTIONS:
+            del FUNCTIONS[id]
+
+        # delete the function file
+        function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
+        os.remove(function_path)
+
+    return result
+
+
 ############################
 ############################
 # GetFunctionValves
 # GetFunctionValves
 ############################
 ############################
@@ -155,7 +249,7 @@ async def get_function_valves_spec_by_id(
         if id in request.app.state.FUNCTIONS:
         if id in request.app.state.FUNCTIONS:
             function_module = request.app.state.FUNCTIONS[id]
             function_module = request.app.state.FUNCTIONS[id]
         else:
         else:
-            function_module, function_type = load_function_module_by_id(id)
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
             request.app.state.FUNCTIONS[id] = function_module
             request.app.state.FUNCTIONS[id] = function_module
 
 
         if hasattr(function_module, "Valves"):
         if hasattr(function_module, "Valves"):
@@ -184,7 +278,7 @@ async def update_function_valves_by_id(
         if id in request.app.state.FUNCTIONS:
         if id in request.app.state.FUNCTIONS:
             function_module = request.app.state.FUNCTIONS[id]
             function_module = request.app.state.FUNCTIONS[id]
         else:
         else:
-            function_module, function_type = load_function_module_by_id(id)
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
             request.app.state.FUNCTIONS[id] = function_module
             request.app.state.FUNCTIONS[id] = function_module
 
 
         if hasattr(function_module, "Valves"):
         if hasattr(function_module, "Valves"):
@@ -247,7 +341,7 @@ async def get_function_user_valves_spec_by_id(
         if id in request.app.state.FUNCTIONS:
         if id in request.app.state.FUNCTIONS:
             function_module = request.app.state.FUNCTIONS[id]
             function_module = request.app.state.FUNCTIONS[id]
         else:
         else:
-            function_module, function_type = load_function_module_by_id(id)
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
             request.app.state.FUNCTIONS[id] = function_module
             request.app.state.FUNCTIONS[id] = function_module
 
 
         if hasattr(function_module, "UserValves"):
         if hasattr(function_module, "UserValves"):
@@ -271,7 +365,7 @@ async def update_function_user_valves_by_id(
         if id in request.app.state.FUNCTIONS:
         if id in request.app.state.FUNCTIONS:
             function_module = request.app.state.FUNCTIONS[id]
             function_module = request.app.state.FUNCTIONS[id]
         else:
         else:
-            function_module, function_type = load_function_module_by_id(id)
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
             request.app.state.FUNCTIONS[id] = function_module
             request.app.state.FUNCTIONS[id] = function_module
 
 
         if hasattr(function_module, "UserValves"):
         if hasattr(function_module, "UserValves"):
@@ -300,93 +394,3 @@ async def update_function_user_valves_by_id(
             status_code=status.HTTP_401_UNAUTHORIZED,
             status_code=status.HTTP_401_UNAUTHORIZED,
             detail=ERROR_MESSAGES.NOT_FOUND,
             detail=ERROR_MESSAGES.NOT_FOUND,
         )
         )
-
-
-############################
-# ToggleFunctionById
-############################
-
-
-@router.post("/id/{id}/toggle", response_model=Optional[FunctionModel])
-async def toggle_function_by_id(id: str, user=Depends(get_admin_user)):
-    function = Functions.get_function_by_id(id)
-    if function:
-        function = Functions.update_function_by_id(
-            id, {"is_active": not function.is_active}
-        )
-
-        if function:
-            return function
-        else:
-            raise HTTPException(
-                status_code=status.HTTP_400_BAD_REQUEST,
-                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
-            )
-    else:
-        raise HTTPException(
-            status_code=status.HTTP_401_UNAUTHORIZED,
-            detail=ERROR_MESSAGES.NOT_FOUND,
-        )
-
-
-############################
-# UpdateFunctionById
-############################
-
-
-@router.post("/id/{id}/update", response_model=Optional[FunctionModel])
-async def update_function_by_id(
-    request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
-):
-    function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
-
-    try:
-        with open(function_path, "w") as function_file:
-            function_file.write(form_data.content)
-
-        function_module, function_type = load_function_module_by_id(id)
-
-        FUNCTIONS = request.app.state.FUNCTIONS
-        FUNCTIONS[id] = function_module
-
-        updated = {**form_data.model_dump(exclude={"id"}), "type": function_type}
-        print(updated)
-
-        function = Functions.update_function_by_id(id, updated)
-
-        if function:
-            return function
-        else:
-            raise HTTPException(
-                status_code=status.HTTP_400_BAD_REQUEST,
-                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
-            )
-
-    except Exception as e:
-        raise HTTPException(
-            status_code=status.HTTP_400_BAD_REQUEST,
-            detail=ERROR_MESSAGES.DEFAULT(e),
-        )
-
-
-############################
-# DeleteFunctionById
-############################
-
-
-@router.delete("/id/{id}/delete", response_model=bool)
-async def delete_function_by_id(
-    request: Request, id: str, user=Depends(get_admin_user)
-):
-    result = Functions.delete_function_by_id(id)
-
-    if result:
-        FUNCTIONS = request.app.state.FUNCTIONS
-        if id in FUNCTIONS:
-            del FUNCTIONS[id]
-
-        # delete the function file
-        function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
-        os.remove(function_path)
-
-    return result

+ 73 - 71
backend/apps/webui/routers/tools.py

@@ -74,7 +74,8 @@ async def create_new_toolkit(
             with open(toolkit_path, "w") as tool_file:
             with open(toolkit_path, "w") as tool_file:
                 tool_file.write(form_data.content)
                 tool_file.write(form_data.content)
 
 
-            toolkit_module = load_toolkit_module_by_id(form_data.id)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(form_data.id)
+            form_data.meta.manifest = frontmatter
 
 
             TOOLS = request.app.state.TOOLS
             TOOLS = request.app.state.TOOLS
             TOOLS[form_data.id] = toolkit_module
             TOOLS[form_data.id] = toolkit_module
@@ -123,6 +124,73 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
         )
         )
 
 
 
 
+############################
+# UpdateToolkitById
+############################
+
+
+@router.post("/id/{id}/update", response_model=Optional[ToolModel])
+async def update_toolkit_by_id(
+    request: Request, id: str, form_data: ToolForm, user=Depends(get_admin_user)
+):
+    toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
+
+    try:
+        with open(toolkit_path, "w") as tool_file:
+            tool_file.write(form_data.content)
+
+        toolkit_module, frontmatter = load_toolkit_module_by_id(id)
+        form_data.meta.manifest = frontmatter
+
+        TOOLS = request.app.state.TOOLS
+        TOOLS[id] = toolkit_module
+
+        specs = get_tools_specs(TOOLS[id])
+
+        updated = {
+            **form_data.model_dump(exclude={"id"}),
+            "specs": specs,
+        }
+
+        print(updated)
+        toolkit = Tools.update_tool_by_id(id, updated)
+
+        if toolkit:
+            return toolkit
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating toolkit"),
+            )
+
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+############################
+# DeleteToolkitById
+############################
+
+
+@router.delete("/id/{id}/delete", response_model=bool)
+async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)):
+    result = Tools.delete_tool_by_id(id)
+
+    if result:
+        TOOLS = request.app.state.TOOLS
+        if id in TOOLS:
+            del TOOLS[id]
+
+        # delete the toolkit file
+        toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
+        os.remove(toolkit_path)
+
+    return result
+
+
 ############################
 ############################
 # GetToolValves
 # GetToolValves
 ############################
 ############################
@@ -161,7 +229,7 @@ async def get_toolkit_valves_spec_by_id(
         if id in request.app.state.TOOLS:
         if id in request.app.state.TOOLS:
             toolkit_module = request.app.state.TOOLS[id]
             toolkit_module = request.app.state.TOOLS[id]
         else:
         else:
-            toolkit_module = load_toolkit_module_by_id(id)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(id)
             request.app.state.TOOLS[id] = toolkit_module
             request.app.state.TOOLS[id] = toolkit_module
 
 
         if hasattr(toolkit_module, "Valves"):
         if hasattr(toolkit_module, "Valves"):
@@ -189,7 +257,7 @@ async def update_toolkit_valves_by_id(
         if id in request.app.state.TOOLS:
         if id in request.app.state.TOOLS:
             toolkit_module = request.app.state.TOOLS[id]
             toolkit_module = request.app.state.TOOLS[id]
         else:
         else:
-            toolkit_module = load_toolkit_module_by_id(id)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(id)
             request.app.state.TOOLS[id] = toolkit_module
             request.app.state.TOOLS[id] = toolkit_module
 
 
         if hasattr(toolkit_module, "Valves"):
         if hasattr(toolkit_module, "Valves"):
@@ -252,7 +320,7 @@ async def get_toolkit_user_valves_spec_by_id(
         if id in request.app.state.TOOLS:
         if id in request.app.state.TOOLS:
             toolkit_module = request.app.state.TOOLS[id]
             toolkit_module = request.app.state.TOOLS[id]
         else:
         else:
-            toolkit_module = load_toolkit_module_by_id(id)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(id)
             request.app.state.TOOLS[id] = toolkit_module
             request.app.state.TOOLS[id] = toolkit_module
 
 
         if hasattr(toolkit_module, "UserValves"):
         if hasattr(toolkit_module, "UserValves"):
@@ -276,7 +344,7 @@ async def update_toolkit_user_valves_by_id(
         if id in request.app.state.TOOLS:
         if id in request.app.state.TOOLS:
             toolkit_module = request.app.state.TOOLS[id]
             toolkit_module = request.app.state.TOOLS[id]
         else:
         else:
-            toolkit_module = load_toolkit_module_by_id(id)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(id)
             request.app.state.TOOLS[id] = toolkit_module
             request.app.state.TOOLS[id] = toolkit_module
 
 
         if hasattr(toolkit_module, "UserValves"):
         if hasattr(toolkit_module, "UserValves"):
@@ -305,69 +373,3 @@ async def update_toolkit_user_valves_by_id(
             status_code=status.HTTP_401_UNAUTHORIZED,
             status_code=status.HTTP_401_UNAUTHORIZED,
             detail=ERROR_MESSAGES.NOT_FOUND,
             detail=ERROR_MESSAGES.NOT_FOUND,
         )
         )
-
-
-############################
-# UpdateToolkitById
-############################
-
-
-@router.post("/id/{id}/update", response_model=Optional[ToolModel])
-async def update_toolkit_by_id(
-    request: Request, id: str, form_data: ToolForm, user=Depends(get_admin_user)
-):
-    toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
-
-    try:
-        with open(toolkit_path, "w") as tool_file:
-            tool_file.write(form_data.content)
-
-        toolkit_module = load_toolkit_module_by_id(id)
-
-        TOOLS = request.app.state.TOOLS
-        TOOLS[id] = toolkit_module
-
-        specs = get_tools_specs(TOOLS[id])
-
-        updated = {
-            **form_data.model_dump(exclude={"id"}),
-            "specs": specs,
-        }
-
-        print(updated)
-        toolkit = Tools.update_tool_by_id(id, updated)
-
-        if toolkit:
-            return toolkit
-        else:
-            raise HTTPException(
-                status_code=status.HTTP_400_BAD_REQUEST,
-                detail=ERROR_MESSAGES.DEFAULT("Error updating toolkit"),
-            )
-
-    except Exception as e:
-        raise HTTPException(
-            status_code=status.HTTP_400_BAD_REQUEST,
-            detail=ERROR_MESSAGES.DEFAULT(e),
-        )
-
-
-############################
-# DeleteToolkitById
-############################
-
-
-@router.delete("/id/{id}/delete", response_model=bool)
-async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)):
-    result = Tools.delete_tool_by_id(id)
-
-    if result:
-        TOOLS = request.app.state.TOOLS
-        if id in TOOLS:
-            del TOOLS[id]
-
-        # delete the toolkit file
-        toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
-        os.remove(toolkit_path)
-
-    return result

+ 26 - 3
backend/apps/webui/utils.py

@@ -1,19 +1,41 @@
 from importlib import util
 from importlib import util
 import os
 import os
+import re
 
 
 from config import TOOLS_DIR, FUNCTIONS_DIR
 from config import TOOLS_DIR, FUNCTIONS_DIR
 
 
 
 
+def extract_frontmatter(file_path):
+    """
+    Extract frontmatter as a dictionary from the specified file path.
+    """
+    frontmatter = {}
+    frontmatter_pattern = re.compile(r"^([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
+
+    with open(file_path, "r", encoding="utf-8") as file:
+        for line in file:
+            if line.strip() == '"""':
+                # End of frontmatter section
+                break
+            match = frontmatter_pattern.match(line)
+            if match:
+                key, value = match.groups()
+                frontmatter[key] = value
+
+    return frontmatter
+
+
 def load_toolkit_module_by_id(toolkit_id):
 def load_toolkit_module_by_id(toolkit_id):
     toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
     toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
     spec = util.spec_from_file_location(toolkit_id, toolkit_path)
     spec = util.spec_from_file_location(toolkit_id, toolkit_path)
     module = util.module_from_spec(spec)
     module = util.module_from_spec(spec)
+    frontmatter = extract_frontmatter(toolkit_path)
 
 
     try:
     try:
         spec.loader.exec_module(module)
         spec.loader.exec_module(module)
         print(f"Loaded module: {module.__name__}")
         print(f"Loaded module: {module.__name__}")
         if hasattr(module, "Tools"):
         if hasattr(module, "Tools"):
-            return module.Tools()
+            return module.Tools(), frontmatter
         else:
         else:
             raise Exception("No Tools class found")
             raise Exception("No Tools class found")
     except Exception as e:
     except Exception as e:
@@ -28,14 +50,15 @@ def load_function_module_by_id(function_id):
 
 
     spec = util.spec_from_file_location(function_id, function_path)
     spec = util.spec_from_file_location(function_id, function_path)
     module = util.module_from_spec(spec)
     module = util.module_from_spec(spec)
+    frontmatter = extract_frontmatter(function_path)
 
 
     try:
     try:
         spec.loader.exec_module(module)
         spec.loader.exec_module(module)
         print(f"Loaded module: {module.__name__}")
         print(f"Loaded module: {module.__name__}")
         if hasattr(module, "Pipe"):
         if hasattr(module, "Pipe"):
-            return module.Pipe(), "pipe"
+            return module.Pipe(), "pipe", frontmatter
         elif hasattr(module, "Filter"):
         elif hasattr(module, "Filter"):
-            return module.Filter(), "filter"
+            return module.Filter(), "filter", frontmatter
         else:
         else:
             raise Exception("No Function class found")
             raise Exception("No Function class found")
     except Exception as e:
     except Exception as e:

+ 9 - 5
backend/main.py

@@ -258,7 +258,7 @@ async def get_function_call_response(
                 if tool_id in webui_app.state.TOOLS:
                 if tool_id in webui_app.state.TOOLS:
                     toolkit_module = webui_app.state.TOOLS[tool_id]
                     toolkit_module = webui_app.state.TOOLS[tool_id]
                 else:
                 else:
-                    toolkit_module = load_toolkit_module_by_id(tool_id)
+                    toolkit_module, frontmatter = load_toolkit_module_by_id(tool_id)
                     webui_app.state.TOOLS[tool_id] = toolkit_module
                     webui_app.state.TOOLS[tool_id] = toolkit_module
 
 
                 file_handler = False
                 file_handler = False
@@ -415,8 +415,8 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
                     if filter_id in webui_app.state.FUNCTIONS:
                     if filter_id in webui_app.state.FUNCTIONS:
                         function_module = webui_app.state.FUNCTIONS[filter_id]
                         function_module = webui_app.state.FUNCTIONS[filter_id]
                     else:
                     else:
-                        function_module, function_type = load_function_module_by_id(
-                            filter_id
+                        function_module, function_type, frontmatter = (
+                            load_function_module_by_id(filter_id)
                         )
                         )
                         webui_app.state.FUNCTIONS[filter_id] = function_module
                         webui_app.state.FUNCTIONS[filter_id] = function_module
 
 
@@ -909,7 +909,9 @@ async def generate_chat_completions(form_data: dict, user=Depends(get_verified_u
 
 
             # Check if function is already loaded
             # Check if function is already loaded
             if pipe_id not in webui_app.state.FUNCTIONS:
             if pipe_id not in webui_app.state.FUNCTIONS:
-                function_module, function_type = load_function_module_by_id(pipe_id)
+                function_module, function_type, frontmatter = (
+                    load_function_module_by_id(pipe_id)
+                )
                 webui_app.state.FUNCTIONS[pipe_id] = function_module
                 webui_app.state.FUNCTIONS[pipe_id] = function_module
             else:
             else:
                 function_module = webui_app.state.FUNCTIONS[pipe_id]
                 function_module = webui_app.state.FUNCTIONS[pipe_id]
@@ -1155,7 +1157,9 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)):
             if filter_id in webui_app.state.FUNCTIONS:
             if filter_id in webui_app.state.FUNCTIONS:
                 function_module = webui_app.state.FUNCTIONS[filter_id]
                 function_module = webui_app.state.FUNCTIONS[filter_id]
             else:
             else:
-                function_module, function_type = load_function_module_by_id(filter_id)
+                function_module, function_type, frontmatter = (
+                    load_function_module_by_id(filter_id)
+                )
                 webui_app.state.FUNCTIONS[filter_id] = function_module
                 webui_app.state.FUNCTIONS[filter_id] = function_module
 
 
             if hasattr(function_module, "valves") and hasattr(
             if hasattr(function_module, "valves") and hasattr(