This commit is contained in:
Timothy Jaeryang Baek committed 2026-06-23 00:25:21 +02:00
1 parent a0c2ec3d2c
commit 91762ed807
8 files changed
+77 -78

No files matched your search

+5 -4
View File
@@ -21,8 +21,8 @@ from open_webui.routers import ollama, openai
from open_webui.socket.utils import RedisDict
from open_webui.utils.access_control import has_access, has_base_model_access
from open_webui.utils.plugin import (
get_functions_cache,
get_function_module_from_cache,
load_function_module_by_id,
)
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
@@ -322,10 +322,11 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
# Batch-fetch all function valves in one query to avoid N+1 DB hits
# inside get_action_priority (previously called per action × per model).
all_function_valves = await Functions.get_function_valves_by_ids(list(all_function_ids))
functions_cache = get_functions_cache(request)
def get_action_priority(action_id):
try:
function_module = request.app.state.FUNCTIONS.get(action_id)
function_module = functions_cache.get(action_id)
if function_module and hasattr(function_module, 'Valves'):
valves_db = all_function_valves.get(action_id)
valves = function_module.Valves(**(valves_db if valves_db else {}))
@@ -355,7 +356,7 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
log.info(f'Action not found: {action_id}')
continue
function_module = request.app.state.FUNCTIONS.get(action_id)
function_module = functions_cache.get(action_id)
if function_module is None:
log.info(f'Failed to load action module: {action_id}')
continue
@@ -368,7 +369,7 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
log.info(f'Filter not found: {filter_id}')
continue
function_module = request.app.state.FUNCTIONS.get(filter_id)
function_module = functions_cache.get(filter_id)
if function_module is None:
log.info(f'Failed to load filter module: {filter_id}')
continue
+44 -30
View File
@@ -298,7 +298,33 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non
os.unlink(temp_file.name)
def _state_cache(request, name: str) -> dict:
if not hasattr(request.app.state, name):
setattr(request.app.state, name, {})
return getattr(request.app.state, name)
def get_tools_cache(request) -> dict:
return _state_cache(request, 'TOOLS')
def get_tool_contents_cache(request) -> dict:
return _state_cache(request, 'TOOL_CONTENTS')
def get_functions_cache(request) -> dict:
return _state_cache(request, 'FUNCTIONS')
def get_function_contents_cache(request) -> dict:
return _state_cache(request, 'FUNCTION_CONTENTS')
async def get_tool_module_from_cache(request, tool_id, load_from_db=True):
tools_cache = get_tools_cache(request)
tool_contents_cache = get_tool_contents_cache(request)
content = None
if load_from_db:
# Always load from the database by default
tool = await Tools.get_tool_by_id(tool_id)
@@ -312,27 +338,19 @@ async def get_tool_module_from_cache(request, tool_id, load_from_db=True):
# Update the tool content in the database
await Tools.update_tool_by_id(tool_id, {'content': content})
if (hasattr(request.app.state, 'TOOL_CONTENTS') and tool_id in request.app.state.TOOL_CONTENTS) and (
hasattr(request.app.state, 'TOOLS') and tool_id in request.app.state.TOOLS
):
if request.app.state.TOOL_CONTENTS[tool_id] == content:
return request.app.state.TOOLS[tool_id], None
if tool_id in tool_contents_cache and tool_id in tools_cache:
if tool_contents_cache[tool_id] == content:
return tools_cache[tool_id], None
tool_module, frontmatter = await load_tool_module_by_id(tool_id, content)
else:
if hasattr(request.app.state, 'TOOLS') and tool_id in request.app.state.TOOLS:
return request.app.state.TOOLS[tool_id], None
if tool_id in tools_cache:
return tools_cache[tool_id], None
tool_module, frontmatter = await load_tool_module_by_id(tool_id)
if not hasattr(request.app.state, 'TOOLS'):
request.app.state.TOOLS = {}
if not hasattr(request.app.state, 'TOOL_CONTENTS'):
request.app.state.TOOL_CONTENTS = {}
request.app.state.TOOLS[tool_id] = tool_module
request.app.state.TOOL_CONTENTS[tool_id] = content
tools_cache[tool_id] = tool_module
tool_contents_cache[tool_id] = content
return tool_module, frontmatter
@@ -340,6 +358,10 @@ async def get_tool_module_from_cache(request, tool_id, load_from_db=True):
async def get_function_module_from_cache(
request, function_id, function: FunctionModel | None = None, load_from_db=True
):
functions_cache = get_functions_cache(request)
function_contents_cache = get_function_contents_cache(request)
content = None
if load_from_db:
# Always load from the database by default
# This is useful for hooks like "inlet" or "outlet" where the content might change
@@ -357,30 +379,22 @@ async def get_function_module_from_cache(
# Update the function content in the database
await Functions.update_function_by_id(function_id, {'content': content})
if (
hasattr(request.app.state, 'FUNCTION_CONTENTS') and function_id in request.app.state.FUNCTION_CONTENTS
) and (hasattr(request.app.state, 'FUNCTIONS') and function_id in request.app.state.FUNCTIONS):
if request.app.state.FUNCTION_CONTENTS[function_id] == content:
return request.app.state.FUNCTIONS[function_id], None, None
if function_id in function_contents_cache and function_id in functions_cache:
if function_contents_cache[function_id] == content:
return functions_cache[function_id], None, None
function_module, function_type, frontmatter = await load_function_module_by_id(function_id, content)
else:
# Load from cache (e.g. "stream" hook)
# This is useful for performance reasons
if hasattr(request.app.state, 'FUNCTIONS') and function_id in request.app.state.FUNCTIONS:
return request.app.state.FUNCTIONS[function_id], None, None
if function_id in functions_cache:
return functions_cache[function_id], None, None
function_module, function_type, frontmatter = await load_function_module_by_id(function_id)
if not hasattr(request.app.state, 'FUNCTIONS'):
request.app.state.FUNCTIONS = {}
if not hasattr(request.app.state, 'FUNCTION_CONTENTS'):
request.app.state.FUNCTION_CONTENTS = {}
request.app.state.FUNCTIONS[function_id] = function_module
request.app.state.FUNCTION_CONTENTS[function_id] = content
functions_cache[function_id] = function_module
function_contents_cache[function_id] = content
return function_module, function_type, frontmatter
+7 -5
View File
@@ -95,7 +95,7 @@ from open_webui.tools.builtin import (
from open_webui.utils.access_control import has_access, has_connection_access, has_permission
from open_webui.utils.headers import get_custom_headers, include_user_info_headers
from open_webui.utils.misc import is_string_allowed
from open_webui.utils.plugin import load_tool_module_by_id
from open_webui.utils.plugin import get_tool_contents_cache, get_tools_cache, load_tool_module_by_id
from pydantic import BaseModel, Field, create_model
from pydantic.fields import FieldInfo
@@ -250,11 +250,13 @@ async def get_tools(request: Request, tool_ids: list[str], user: UserModel, extr
log.warning(f'Access denied to tool {tool_id} for user {user.id}')
continue
module = request.app.state.TOOLS.get(tool_id)
if module is None or request.app.state.TOOL_CONTENTS.get(tool_id) != tool.content:
tools_cache = get_tools_cache(request)
tool_contents_cache = get_tool_contents_cache(request)
module = tools_cache.get(tool_id)
if module is None or tool_contents_cache.get(tool_id) != tool.content:
module, _ = await load_tool_module_by_id(tool_id, content=tool.content)
request.app.state.TOOLS[tool_id] = module
request.app.state.TOOL_CONTENTS[tool_id] = tool.content
tools_cache[tool_id] = module
tool_contents_cache[tool_id] = tool.content
__user__ = {
**extra_params['__user__'],