diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index 8f0f7e0d1b..c9f9a3c55f 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -7,7 +7,7 @@ import time # local imports from open_webui.internal.db import Base, JSONField, get_async_db_context -from open_webui.models.users import UserResponse, Users +from open_webui.models.users import User, UserResponse, Users, UserSettings from open_webui.utils.valves import decrypt_valves, encrypt_valves from pydantic import BaseModel, ConfigDict from sqlalchemy import BigInteger, Boolean, Column, Index, String, Text, delete, select, update @@ -275,6 +275,12 @@ class FunctionsTable: result = await db.execute(select(Function).filter_by(type='filter', is_active=True, is_global=True)) return [FunctionModel.model_validate(function) for function in result.scalars().all()] + async def get_active_filter_ids(self, db: AsyncSession | None = None) -> list[tuple[str, bool]]: + """Return (id, is_global) for active filters without fetching plugin source.""" + async with get_async_db_context(db) as db: + result = await db.execute(select(Function.id, Function.is_global).filter_by(type='filter', is_active=True)) + return [(id, bool(is_global)) for id, is_global in result.all()] + async def get_global_action_functions(self, db: AsyncSession | None = None) -> list[FunctionModel]: async with get_async_db_context(db) as db: result = await db.execute(select(Function).filter_by(type='action', is_active=True, is_global=True)) @@ -283,13 +289,15 @@ class FunctionsTable: async def get_function_valves_by_id(self, id: str, db: AsyncSession | None = None) -> dict | None: async with get_async_db_context(db) as db: try: - function = await db.get(Function, id) - return decrypt_valves(function.valves if function else None) + result = await db.execute(select(Function.valves).filter_by(id=id)) + return decrypt_valves(result.scalar_one_or_none()) except Exception as e: log.exception(f'Error getting function valves by id {id}: {e}') return None - async def get_function_valves_by_ids(self, ids: list[str], db: AsyncSession | None = None) -> dict[str, dict]: + async def get_function_valves_by_ids( + self, ids: list[str], db: AsyncSession | None = None + ) -> dict[str, dict]: """ Batch fetch valves for multiple functions in a single query. Returns a dict mapping function_id -> valves dict. @@ -300,8 +308,7 @@ class FunctionsTable: try: async with get_async_db_context(db) as db: result = await db.execute(select(Function.id, Function.valves).filter(Function.id.in_(ids))) - functions = result.all() - return {f.id: decrypt_valves(f.valves) for f in functions} + return {id: decrypt_valves(valves) for id, valves in result.all()} except Exception as e: log.exception(f'Error batch-fetching function valves: {e}') return {} @@ -347,8 +354,11 @@ class FunctionsTable: self, id: str, user_id: str, db: AsyncSession | None = None ) -> dict | None: try: - user = await Users.get_user_by_id(user_id, db=db) - user_settings = user.settings.model_dump() if user.settings else {} + async with get_async_db_context(db) as db: + result = await db.execute(select(User.settings).filter_by(id=user_id)) + settings = result.scalar_one_or_none() + + user_settings = UserSettings(**settings).model_dump() if settings else {} # Check if user has "functions" and "valves" settings if 'functions' not in user_settings: diff --git a/backend/open_webui/utils/filter.py b/backend/open_webui/utils/filter.py index 98eb24dd02..f73278e7e2 100644 --- a/backend/open_webui/utils/filter.py +++ b/backend/open_webui/utils/filter.py @@ -3,10 +3,7 @@ import logging from open_webui.env import ENABLE_PLUGINS from open_webui.models.functions import Functions -from open_webui.utils.plugin import ( - get_function_module_from_cache, - load_function_module_by_id, -) +from open_webui.utils.plugin import get_function_module_from_cache log = logging.getLogger(__name__) @@ -23,22 +20,12 @@ async def get_sorted_filter_ids(request, model: dict, enabled_filter_ids: list = if not ENABLE_PLUGINS: return [] - async def get_priority(function_id): - try: - function_module = await get_function_module(request, function_id) - if function_module and hasattr(function_module, 'Valves'): - valves_db = await Functions.get_function_valves_by_id(function_id) - valves = function_module.Valves(**(valves_db if valves_db else {})) - return getattr(valves, 'priority', 0) - except Exception: - pass - return 0 - - filter_ids = [function.id for function in await Functions.get_global_filter_functions()] + active_filters = await Functions.get_active_filter_ids() + filter_ids = [fid for fid, is_global in active_filters if is_global] if 'info' in model and 'meta' in model['info']: filter_ids.extend(model['info']['meta'].get('filterIds', [])) filter_ids = list(set(filter_ids)) - active_filter_ids = {function.id for function in await Functions.get_functions_by_type('filter', active_only=True)} + active_filter_ids = {fid for fid, _ in active_filters} async def get_active_status(filter_id): function_module = await get_function_module(request, filter_id) @@ -55,6 +42,18 @@ async def get_sorted_filter_ids(request, model: dict, enabled_filter_ids: list = active_filter_ids = {fid for fid, is_active in resolved_active.items() if is_active} filter_ids = [fid for fid in filter_ids if fid in active_filter_ids] + valves_by_id = await Functions.get_function_valves_by_ids(filter_ids) + + async def get_priority(function_id): + try: + function_module = await get_function_module(request, function_id) + if function_module and hasattr(function_module, 'Valves'): + valves_db = valves_by_id.get(function_id) + valves = function_module.Valves(**(valves_db if valves_db else {})) + return getattr(valves, 'priority', 0) + except Exception: + pass + return 0 # Pre-compute priorities (async functions can't be used in sort keys) priorities = {} @@ -72,12 +71,13 @@ async def process_filter_functions(request, filter_functions, filter_type, form_ return form_data, {} skip_files = None + valves_by_id = None + filter_ids = [function.id for function in filter_functions if function] for function in filter_functions: - filter = function - filter_id = function.id - if not filter: + if not function: continue + filter_id = function.id function_module = await get_function_module(request, filter_id, load_from_db=(filter_type != 'stream')) # Prepare handler function @@ -91,7 +91,9 @@ async def process_filter_functions(request, filter_functions, filter_type, form_ # Apply valves to the function if hasattr(function_module, 'valves') and hasattr(function_module, 'Valves'): - valves = await Functions.get_function_valves_by_id(filter_id) + if valves_by_id is None: + valves_by_id = await Functions.get_function_valves_by_ids(filter_ids) + valves = valves_by_id.get(filter_id) function_module.valves = function_module.Valves(**(valves if valves else {})) try: diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index db86b0c8be..c2b9c46fcc 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -3709,10 +3709,9 @@ async def streaming_chat_response_handler(response, ctx): } filter_functions = ( - [ - await Functions.get_function_by_id(filter_id) - for filter_id in await get_sorted_filter_ids(request, model, metadata.get('filter_ids', [])) - ] + await Functions.get_functions_by_ids( + await get_sorted_filter_ids(request, model, metadata.get('filter_ids', [])) + ) if ENABLE_PLUGINS else [] )