From 1a3e5ef4c175eb0848c9b2eced685d036d2eaff7 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 8 May 2026 22:20:13 +0200 Subject: [PATCH] perf(prompts): make /tags fetch only the tags column with SQL access filter (#24287) Non-admin GET /api/v1/prompts/tags went through get_prompts_by_user_id, which loaded every active prompt with its full content/data/meta plus owner records and all access grants, then ran one has_access query per prompt that wasn't owned by the caller - all so the endpoint could collapse the result to a sorted tag list. With 600 prompts this took several seconds while the admin path (a single SELECT) returned in <1s. Add Prompts.get_tags_by_user_id which selects only the tags column and applies the same EXISTS-based access filter used by /list. Also tighten the admin get_tags to project just the tags column instead of full rows. The endpoint is now one DB query (plus one for groups), no row hydration, no N+1. Co-authored-by: Claude --- backend/open_webui/models/prompts.py | 36 +++++++++++++++++++++++---- backend/open_webui/routers/prompts.py | 8 +----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 47f74295db..70c05094b8 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -663,12 +663,38 @@ class PromptsTable: async def get_tags(self, db: Optional[AsyncSession] = None) -> list[str]: try: async with get_async_db_context(db) as db: - result = await db.execute(select(Prompt).filter_by(is_active=True)) - prompts = result.scalars().all() + result = await db.execute(select(Prompt.tags).filter(Prompt.is_active == True)) tags = set() - for prompt in prompts: - if prompt.tags: - for tag in prompt.tags: + for (tag_list,) in result.all(): + if tag_list: + for tag in tag_list: + if tag: + tags.add(tag) + return sorted(list(tags)) + except Exception: + return [] + + async def get_tags_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[str]: + try: + async with get_async_db_context(db) as db: + user_groups = await Groups.get_groups_by_member_id(user_id, db=db) + user_group_ids = [group.id for group in user_groups] + + query = select(Prompt.tags).filter(Prompt.is_active == True) + query = AccessGrants.has_permission_filter( + db=db, + query=query, + DocumentModel=Prompt, + filter={'user_id': user_id, 'group_ids': user_group_ids}, + resource_type='prompt', + permission='read', + ) + + result = await db.execute(query) + tags = set() + for (tag_list,) in result.all(): + if tag_list: + for tag in tag_list: if tag: tags.add(tag) return sorted(list(tags)) diff --git a/backend/open_webui/routers/prompts.py b/backend/open_webui/routers/prompts.py index 11901fc5a7..755034f880 100644 --- a/backend/open_webui/routers/prompts.py +++ b/backend/open_webui/routers/prompts.py @@ -61,13 +61,7 @@ async def get_prompts(user=Depends(get_verified_user), db: AsyncSession = Depend async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL: return await Prompts.get_tags(db=db) - else: - prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db) - tags = set() - for prompt in prompts: - if prompt.tags: - tags.update(prompt.tags) - return sorted(list(tags)) + return await Prompts.get_tags_by_user_id(user.id, db=db) @router.get('/list', response_model=PromptAccessListResponse)