diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py index e8546b9efd..db2bf5e238 100644 --- a/backend/open_webui/routers/memories.py +++ b/backend/open_webui/routers/memories.py @@ -18,11 +18,6 @@ log = logging.getLogger(__name__) router = APIRouter() -@router.get("/ef") -async def get_embeddings(request: Request): - return {"result": await request.app.state.EMBEDDING_FUNCTION("hello world")} - - ############################ # GetMemories ############################ @@ -165,7 +160,7 @@ async def reset_memory_from_vector_db( user=Depends(get_verified_user), ): """Reset user's memory vector embeddings. - + CRITICAL: We intentionally do NOT use Depends(get_session) here. This endpoint generates embeddings for ALL user memories in parallel using asyncio.gather(). A user with 100 memories would trigger 100 embedding API diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 586a261103..d65e231302 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2099,9 +2099,10 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Skills: inject manifest only — model uses view_skill tool to load full content on-demand user_skill_ids = form_data.pop("skill_ids", None) or [] - model_skill_ids = model.get("info", {}).get("meta", {}).get("skills", []) + model_skill_ids = model.get("info", {}).get("meta", {}).get("skillIds", []) all_skill_ids = list(set(user_skill_ids + model_skill_ids)) + available_skills = [] if all_skill_ids: from open_webui.models.skills import Skills as SkillsModel @@ -2348,6 +2349,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): { **extra_params, "__event_emitter__": event_emitter, + "__skill_ids__": [s.id for s in available_skills], }, features, model, diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index bce57dc266..7e8520a1ac 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -508,7 +508,7 @@ def get_builtin_tools( ) # Skills tools - view_skill allows model to load full skill instructions on demand - if is_builtin_tool_enabled("skills"): + if extra_params.get("__skill_ids__"): builtin_functions.append(view_skill) for func in builtin_functions: diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte index a47d738f79..05d17ddaf6 100644 --- a/src/lib/components/workspace/Models/ModelEditor.svelte +++ b/src/lib/components/workspace/Models/ModelEditor.svelte @@ -12,6 +12,7 @@ import Tags from '$lib/components/common/Tags.svelte'; import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte'; import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte'; + import SkillsSelector from '$lib/components/workspace/Models/SkillsSelector.svelte'; import FiltersSelector from '$lib/components/workspace/Models/FiltersSelector.svelte'; import ActionsSelector from '$lib/components/workspace/Models/ActionsSelector.svelte'; import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte'; @@ -89,6 +90,7 @@ let knowledge = []; let toolIds = []; + let skillIds = []; let filterIds = []; let defaultFilterIds = []; @@ -166,6 +168,14 @@ } } + if (skillIds.length > 0) { + info.meta.skillIds = skillIds; + } else { + if (info.meta.skillIds) { + delete info.meta.skillIds; + } + } + if (filterIds.length > 0) { info.meta.filterIds = filterIds; } else { @@ -293,6 +303,7 @@ }); toolIds = model?.meta?.toolIds ?? []; + skillIds = model?.meta?.skillIds ?? []; filterIds = model?.meta?.filterIds ?? []; defaultFilterIds = model?.meta?.defaultFilterIds ?? []; actionIds = model?.meta?.actionIds ?? []; @@ -736,6 +747,10 @@ +
+ +
+ {#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0}
diff --git a/src/lib/components/workspace/Models/SkillsSelector.svelte b/src/lib/components/workspace/Models/SkillsSelector.svelte new file mode 100644 index 0000000000..6a7a481064 --- /dev/null +++ b/src/lib/components/workspace/Models/SkillsSelector.svelte @@ -0,0 +1,62 @@ + + +
+
+
{$i18n.t('Skills')}
+
+ +
+ {#if Object.keys(_skills).length > 0} +
+ {#each Object.keys(_skills) as skill, skillIdx} +
+
+ { + _skills[skill].selected = e.detail === 'checked'; + selectedSkillIds = Object.keys(_skills).filter((s) => _skills[s].selected); + }} + /> +
+ + +
+ {_skills[skill].name} +
+
+
+ {/each} +
+ {/if} +
+ +
+ {$i18n.t('To select skills here, add them to the "Skills" workspace first.')} +
+
diff --git a/src/lib/components/workspace/Skills.svelte b/src/lib/components/workspace/Skills.svelte index 86d4352c1e..d3595d48f7 100644 --- a/src/lib/components/workspace/Skills.svelte +++ b/src/lib/components/workspace/Skills.svelte @@ -54,22 +54,7 @@ let viewOption = ''; let page = 1; - // Debounce only query changes - $: if (query !== undefined) { - loading = true; - clearTimeout(searchDebounceTimer); - searchDebounceTimer = setTimeout(() => { - page = 1; - getSkillItems(); - }, 300); - } - - // Immediate response to page/filter changes - $: if (page && viewOption !== undefined) { - getSkillItems(); - } - - const getSkillItems = async () => { + const loadSkillItems = async () => { if (!loaded) return; loading = true; @@ -95,6 +80,21 @@ } }; + // Debounce only query changes + $: if (query !== undefined) { + loading = true; + clearTimeout(searchDebounceTimer); + searchDebounceTimer = setTimeout(() => { + page = 1; + loadSkillItems(); + }, 300); + } + + // Immediate response to page/filter changes + $: if (page && viewOption !== undefined) { + loadSkillItems(); + } + const cloneHandler = async (skill) => { const _skill = await getSkillById(localStorage.token, skill.id).catch((error) => { toast.error(`${error}`); @@ -136,7 +136,7 @@ } page = 1; - getSkillItems(); + loadSkillItems(); await _skills.set(await getSkills(localStorage.token)); };