Merge pull request #13358 from feddersen-group/fix/load_ext_dependencies_on_startup

fix: install external requirements of tools and functions to prevent deactivation
This commit is contained in:
Tim Jaeryang Baek 2025-04-30 04:59:56 -07:00 committed by GitHub
commit e822984162
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 2 deletions

View File

@ -383,6 +383,7 @@ from open_webui.utils.auth import (
get_admin_user, get_admin_user,
get_verified_user, get_verified_user,
) )
from open_webui.utils.plugin import install_admin_tool_and_function_dependencies
from open_webui.utils.oauth import OAuthManager from open_webui.utils.oauth import OAuthManager
from open_webui.utils.security_headers import SecurityHeadersMiddleware from open_webui.utils.security_headers import SecurityHeadersMiddleware
@ -451,6 +452,12 @@ async def lifespan(app: FastAPI):
limiter.total_tokens = pool_size limiter.total_tokens = pool_size
asyncio.create_task(periodic_usage_pool_cleanup()) asyncio.create_task(periodic_usage_pool_cleanup())
# This should be blocking (sync) so functions are not deactivated on first /get_models calls
# when the first user lands on the / route.
log.info("Installing external dependencies of functions and tools...")
install_admin_tool_and_function_dependencies()
yield yield
@ -895,7 +902,8 @@ class RedirectMiddleware(BaseHTTPMiddleware):
# Check for the specific watch path and the presence of 'v' parameter # Check for the specific watch path and the presence of 'v' parameter
if path.endswith("/watch") and "v" in query_params: if path.endswith("/watch") and "v" in query_params:
video_id = query_params["v"][0] # Extract the first 'v' parameter # Extract the first 'v' parameter
video_id = query_params["v"][0]
encoded_video_id = urlencode({"youtube": video_id}) encoded_video_id = urlencode({"youtube": video_id})
redirect_url = f"/?{encoded_video_id}" redirect_url = f"/?{encoded_video_id}"
return RedirectResponse(url=redirect_url) return RedirectResponse(url=redirect_url)

View File

@ -157,7 +157,8 @@ def load_function_module_by_id(function_id, content=None):
raise Exception("No Function class found in the module") raise Exception("No Function class found in the module")
except Exception as e: except Exception as e:
log.error(f"Error loading module: {function_id}: {e}") log.error(f"Error loading module: {function_id}: {e}")
del sys.modules[module_name] # Cleanup by removing the module in case of error # Cleanup by removing the module in case of error
del sys.modules[module_name]
Functions.update_function_by_id(function_id, {"is_active": False}) Functions.update_function_by_id(function_id, {"is_active": False})
raise e raise e
@ -182,3 +183,32 @@ def install_frontmatter_requirements(requirements: str):
else: else:
log.info("No requirements found in frontmatter.") log.info("No requirements found in frontmatter.")
def install_admin_tool_and_function_dependencies():
"""
Install all dependencies for all admin tools and active functions.
By first collecting all dependencies from the frontmatter of each tool and function,
and then installing them using pip. Duplicates or similar version specifications are
handled by pip as much as possible.
"""
function_list = Functions.get_functions(active_only=True)
tool_list = Tools.get_tools()
all_dependencies = ""
try:
for function in function_list:
frontmatter = extract_frontmatter(replace_imports(function.content))
if dependencies := frontmatter.get("requirements"):
all_dependencies += f"{dependencies}, "
for tool in tool_list:
# Only install requirements for admin tools
if tool.user.role == "admin":
frontmatter = extract_frontmatter(replace_imports(tool.content))
if dependencies := frontmatter.get("requirements"):
all_dependencies += f"{dependencies}, "
install_frontmatter_requirements(all_dependencies.strip(", "))
except Exception as e:
log.error(f"Error installing requirements: {e}")