This commit is contained in:
Timothy Jaeryang Baek
2026-06-23 00:25:21 +02:00
parent a0c2ec3d2c
commit 91762ed807
8 changed files with 77 additions and 78 deletions
+1 -1
View File
@@ -839,7 +839,7 @@ async def get_models(request: Request, refresh: bool = False, user=Depends(get_v
models.append(model)
model_order_list = await Config.get('models.order_list')
model_order_list = await Config.get('ui.model_order_list')
if model_order_list:
model_order_dict = {model_id: i for i, model_id in enumerate(model_order_list)}
# Sort models by order list priority, with fallback for those not in the list
+5 -5
View File
@@ -22,6 +22,7 @@ from open_webui.models.functions import (
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.plugin import (
get_functions_cache,
get_function_module_from_cache,
load_function_module_by_id,
replace_imports,
@@ -205,7 +206,7 @@ async def create_new_function(
)
form_data.meta.manifest = frontmatter
FUNCTIONS = request.app.state.FUNCTIONS
FUNCTIONS = get_functions_cache(request)
FUNCTIONS[form_data.id] = function_module
function = await Functions.insert_new_function(user.id, function_type, form_data, db=db)
@@ -322,7 +323,7 @@ async def update_function_by_id(
function_module, function_type, frontmatter = await load_function_module_by_id(id, content=form_data.content)
form_data.meta.manifest = frontmatter
FUNCTIONS = request.app.state.FUNCTIONS
FUNCTIONS = get_functions_cache(request)
FUNCTIONS[id] = function_module
updated = {**form_data.model_dump(exclude={'id'}), 'type': function_type}
@@ -363,9 +364,8 @@ async def delete_function_by_id(
result = await Functions.delete_function_by_id(id, db=db)
if result:
FUNCTIONS = request.app.state.FUNCTIONS
if id in FUNCTIONS:
del FUNCTIONS[id]
FUNCTIONS = get_functions_cache(request)
FUNCTIONS.pop(id, None)
return result
+3 -2
View File
@@ -773,14 +773,15 @@ async def edit_images(request: Request, form_data: EditImageForm, user=Depends(g
# global image-edit switch and the per-user image-generation permission. The internal
# callers (edit_image tool, chat middleware) gate themselves and call image_edits()
# directly, so they are unaffected by this wrapper.
if not request.app.state.config.ENABLE_IMAGE_EDIT:
image_config = await get_image_config()
if not image_config.ENABLE_IMAGE_EDIT:
raise HTTPException(
status_code=403,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != 'admin' and not await has_permission(
user.id, 'features.image_generation', request.app.state.config.USER_PERMISSIONS
user.id, 'features.image_generation', image_config.USER_PERMISSIONS
):
raise HTTPException(
status_code=403,
+1 -5
View File
@@ -532,11 +532,7 @@ async def get_model_profile_image(
# Fallback: check arena models stored in config (not in the DB)
if not profile_image_url:
arena_models = getattr(
getattr(request.app.state, 'config', None),
'EVALUATION_ARENA_MODELS',
[],
)
arena_models = await Config.get('evaluation.arena.models', []) or []
for arena_model in arena_models:
if arena_model.get('id') == id:
profile_image_url = arena_model.get('meta', {}).get('profile_image_url')
+11 -26
View File
@@ -31,6 +31,7 @@ from open_webui.utils.access_control import (
)
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.plugin import (
get_tools_cache,
get_tool_module_from_cache,
load_tool_module_by_id,
replace_imports,
@@ -70,8 +71,9 @@ async def get_tools(
tools = []
# Local Tools
tools_cache = get_tools_cache(request)
for tool in await Tools.get_tools(defer_content=True, db=db):
tool_module = request.app.state.TOOLS.get(tool.id) if hasattr(request.app.state, 'TOOLS') else None
tool_module = tools_cache.get(tool.id)
tools.append(
ToolUserResponse(
**{
@@ -368,7 +370,7 @@ async def create_new_tools(
tool_module, frontmatter = await load_tool_module_by_id(form_data.id, content=form_data.content)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
TOOLS = get_tools_cache(request)
TOOLS[form_data.id] = tool_module
specs = get_tool_specs(TOOLS[form_data.id])
@@ -498,7 +500,7 @@ async def update_tools_by_id(
tool_module, frontmatter = await load_tool_module_by_id(id, content=form_data.content)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
TOOLS = get_tools_cache(request)
TOOLS[id] = tool_module
specs = get_tool_specs(TOOLS[id])
@@ -624,9 +626,8 @@ async def delete_tools_by_id(
result = await Tools.delete_tool_by_id(id, db=db)
if result:
TOOLS = request.app.state.TOOLS
if id in TOOLS:
del TOOLS[id]
TOOLS = get_tools_cache(request)
TOOLS.pop(id, None)
return result
@@ -708,11 +709,7 @@ async def get_tools_valves_spec_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
tools_module, _ = await get_tool_module_from_cache(request, id)
if hasattr(tools_module, 'Valves'):
Valves = tools_module.Valves
@@ -759,11 +756,7 @@ async def update_tools_valves_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
tools_module, _ = await get_tool_module_from_cache(request, id)
if not hasattr(tools_module, 'Valves'):
raise HTTPException(
@@ -858,11 +851,7 @@ async def get_tools_user_valves_spec_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
tools_module, _ = await get_tool_module_from_cache(request, id)
if hasattr(tools_module, 'UserValves'):
UserValves = tools_module.UserValves
@@ -904,11 +893,7 @@ async def update_tools_user_valves_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
tools_module, _ = await get_tool_module_from_cache(request, id)
if hasattr(tools_module, 'UserValves'):
UserValves = tools_module.UserValves
+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__'],