diff --git a/backend/open_webui/events.py b/backend/open_webui/events.py index b627c69a18..2df315e04d 100644 --- a/backend/open_webui/events.py +++ b/backend/open_webui/events.py @@ -5,6 +5,7 @@ import inspect import logging import time import uuid +import weakref from types import SimpleNamespace from typing import Any @@ -1067,6 +1068,23 @@ class NotificationEventSink: schedule_notification_dispatch(app, event) +# Keyed on the underlying function: reflection is expensive and runs per +# dispatched event per handler; reloads create new functions and evict. +_signature_cache = weakref.WeakKeyDictionary() + + +def _handler_signature(handler): + func = getattr(handler, '__func__', handler) + try: + signature = _signature_cache.get(func) + if signature is None: + signature = inspect.signature(handler) + _signature_cache[func] = signature + return signature + except TypeError: + return inspect.signature(handler) + + async def dispatch_event_functions(app: Any, event: Event, request: Any | None = None) -> None: if not ENABLE_PLUGINS: return @@ -1094,7 +1112,7 @@ async def dispatch_event_functions(app: Any, event: Event, request: Any | None = valves = await Functions.get_function_valves_by_id(function.id) function_module.valves = function_module.Valves(**(valves if valves else {})) - sig = inspect.signature(handler) + sig = _handler_signature(handler) accepts_kwargs = any(param.kind == inspect.Parameter.VAR_KEYWORD for param in sig.parameters.values()) extra_params = { 'event': event_payload, diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index bbd2e589e5..6f2939d214 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -728,8 +728,8 @@ class UsersTable: async def get_valid_user_ids(self, user_ids: list[str], db: AsyncSession | None = None) -> list[str]: async with get_async_db_context(db) as session: - result = await session.execute(select(User).where(User.id.in_(user_ids))) - return [u.id for u in result.scalars().all()] + result = await session.execute(select(User.id).where(User.id.in_(user_ids))) + return list(result.scalars().all()) async def get_super_admin_user(self, db: AsyncSession | None = None) -> UserModel | None: async with get_async_db_context(db) as session: diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index e3ebbfd96f..6020c10450 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -76,11 +76,13 @@ async def get_folders( await check_folders_permission(request, user, db=db) folders = await Folders.get_folders_by_user_id(user.id, db=db) + # The full folder set is already loaded; parents can be validated against it + folder_ids = {folder.id for folder in folders} # Verify folder data integrity folder_list = [] for folder in folders: - if folder.parent_id and not await Folders.get_folder_by_id_and_user_id(folder.parent_id, user.id, db=db): + if folder.parent_id and folder.parent_id not in folder_ids: folder = await Folders.update_folder_parent_id_by_id_and_user_id(folder.id, user.id, None, db=db) if folder.data and 'files' in folder.data: diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index c4a042d9e1..1b5c683aa6 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2157,9 +2157,12 @@ def extract_skill_ids_from_messages(messages: list[dict]) -> set[str]: return ids +SKILL_MENTION_STRIP_RE = re.compile(r'<(?:\$[^|>]+(?:\|([^>]*))?|/[^|>]+\|([^>]*))>') + + def strip_skill_mentions(messages: list[dict]) -> None: """Replace <$skillId|label> and mention tags with the label in-place.""" - strip_re = re.compile(r'<(?:\$[^|>]+(?:\|([^>]*))?|/[^|>]+\|([^>]*))>') + strip_re = SKILL_MENTION_STRIP_RE def label(match): return match.group(1) or match.group(2) or '' @@ -4788,13 +4791,15 @@ async def streaming_chat_response_handler(response, ctx): tool_args = tool_call.get('function', {}).get('arguments', '{}') params = {} if tool_args and tool_args.strip(): + # Arguments are almost always JSON; literal_eval is + # kept as fallback for Python-repr-style payloads try: - params = ast.literal_eval(tool_args) - except Exception as e: - log.debug(e) + params = json.loads(tool_args) + except Exception: try: - params = json.loads(tool_args) - except Exception: + params = ast.literal_eval(tool_args) + except Exception as e: + log.debug(e) return None tool_call.setdefault('function', {})['arguments'] = json.dumps(params) return params diff --git a/backend/open_webui/utils/plugin.py b/backend/open_webui/utils/plugin.py index 4e3c6e5423..d6d5ff3388 100644 --- a/backend/open_webui/utils/plugin.py +++ b/backend/open_webui/utils/plugin.py @@ -207,6 +207,7 @@ async def load_tool_module_by_id(tool_id, content=None): if not ENABLE_PLUGINS: raise RuntimeError('Plugins are disabled by ENABLE_PLUGINS=false') + frontmatter = None if content is None: tool = await Tools.get_tool_by_id(tool_id) if not tool: @@ -238,7 +239,8 @@ async def load_tool_module_by_id(tool_id, content=None): # Executing the modified content in the created module's namespace exec(content, module.__dict__) - frontmatter = extract_frontmatter(content) + if frontmatter is None: + frontmatter = extract_frontmatter(content) log.info(f'Loaded module: {module.__name__}') # Create and return the object if the class 'Tools' is found in the module @@ -258,6 +260,7 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non if not ENABLE_PLUGINS: raise RuntimeError('Plugins are disabled by ENABLE_PLUGINS=false') + frontmatter = None if content is None: function = await Functions.get_function_by_id(function_id) if not function: @@ -286,7 +289,8 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non # Execute the modified content in the created module's namespace exec(content, module.__dict__) - frontmatter = extract_frontmatter(content) + if frontmatter is None: + frontmatter = extract_frontmatter(content) log.info(f'Loaded module: {module.__name__}') # Create appropriate object based on available class type in the module