diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 832f1b1ce7..8a67f84539 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2058,6 +2058,8 @@ ENABLE_FOLDERS = os.getenv('ENABLE_FOLDERS', 'True').lower() == 'true' FOLDER_MAX_FILE_COUNT = os.getenv('FOLDER_MAX_FILE_COUNT', '') +ENABLE_SKILL_LAZY_LOADING = os.getenv('ENABLE_SKILL_LAZY_LOADING', 'False').lower() == 'true' + SKILL_CONTENT_MAX_CHARS = int(os.environ['SKILL_CONTENT_MAX_CHARS']) if os.getenv('SKILL_CONTENT_MAX_CHARS') else None if SKILL_CONTENT_MAX_CHARS is not None and SKILL_CONTENT_MAX_CHARS <= 0: raise ValueError('SKILL_CONTENT_MAX_CHARS must be a positive integer or unset') @@ -2900,6 +2902,7 @@ ENABLE_LDAP_GROUP_CREATION = os.getenv('ENABLE_LDAP_GROUP_CREATION', 'False').lo LDAP_ATTRIBUTE_FOR_GROUPS = os.getenv('LDAP_ATTRIBUTE_FOR_GROUPS', 'memberOf') DEFAULT_CONFIG = { + 'skills.lazy_loading.enable': ENABLE_SKILL_LAZY_LOADING, 'skills.content_max_chars': SKILL_CONTENT_MAX_CHARS, 'skills.preview_max_chars': SKILL_PREVIEW_MAX_CHARS, 'direct.enable': ENABLE_DIRECT_CONNECTIONS, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 701acfb360..5c9ea63455 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -134,6 +134,7 @@ ADMIN_CONFIG_KEYS = { 'ENABLE_CALENDAR': 'calendar.enable', 'ENABLE_MEMORIES': 'memories.enable', 'ENABLE_MEMORY_SYSTEM_CONTEXT': 'memories.system_context.enable', + 'ENABLE_SKILL_LAZY_LOADING': 'skills.lazy_loading.enable', 'SKILL_CONTENT_MAX_CHARS': 'skills.content_max_chars', 'SKILL_PREVIEW_MAX_CHARS': 'skills.preview_max_chars', 'ENABLE_NOTES': 'notes.enable', @@ -1187,6 +1188,7 @@ class AdminConfig(MfaConfigForm): ENABLE_CALENDAR: bool ENABLE_MEMORIES: bool ENABLE_MEMORY_SYSTEM_CONTEXT: bool + ENABLE_SKILL_LAZY_LOADING: bool = False SKILL_CONTENT_MAX_CHARS: int | None = Field(default=None, gt=0, strict=True) SKILL_PREVIEW_MAX_CHARS: int | None = Field(default=None, gt=0, strict=True) ENABLE_NOTES: bool @@ -1237,6 +1239,8 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep updates.pop('apps.enable', None) if 'I18N' not in form_data.model_fields_set: updates.pop('ui.i18n', None) + if 'ENABLE_SKILL_LAZY_LOADING' not in form_data.model_fields_set: + updates.pop('skills.lazy_loading.enable', None) if 'SKILL_CONTENT_MAX_CHARS' not in form_data.model_fields_set: updates.pop('skills.content_max_chars', None) if 'SKILL_PREVIEW_MAX_CHARS' not in form_data.model_fields_set: diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 9d88eb9a92..7f8436e1cf 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2453,7 +2453,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Otherwise, save any tools that filter inlets added for merging later. inlet_filter_tools = None if payload_tools is not None else form_data.get('tools', None) - # Selected and mentioned skills get bounded content; other skills remain available through view_skill. + # Mentions always load instructions; selected skills can optionally load through view_skill. chat_context = metadata.get('chat_context') or {} metadata['chat_context'] = chat_context skill_versions = chat_context.setdefault('skill_versions', {}) @@ -2462,6 +2462,8 @@ async def process_chat_payload(request, form_data, user, metadata, model): ) activated_skill_ids = set(form_data.pop('skill_ids', None) or []) | mentioned_skill_ids skill_ids = sorted(activated_skill_ids | set(model.get('info', {}).get('meta', {}).get('skillIds', []))) + if payload_tools is None and await Config.get('skills.lazy_loading.enable', False): + activated_skill_ids = mentioned_skill_ids available_skills = [] terminal_skills = [] if is_note_chat: diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index db9c9ea478..6207fd5b4b 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -339,6 +339,13 @@ /> {/if} + + + mentions both load instructions. + // The backend applies the skill loading setting to menu-selected skills. // Keep mentions in the message so the backend can resolve them. const skillIds = [...selectedSkillIds]; diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 2c4d70a9ac..7fb24dd957 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -2828,6 +2828,8 @@ "settings.admin.general.seeWhatSNew.label": "See what's new", "settings.admin.general.skillContentMaxChars.description": "Maximum characters per skill read. Longer instructions open as a preview. Leave empty to use the default.", "settings.admin.general.skillContentMaxChars.label": "Skill Read Limit", + "settings.admin.general.skillLazyLoading.description": "Load selected and model-attached skills when needed instead of including instructions upfront. Explicit skill mentions still load immediately. Requires built-in skill tools; otherwise instructions load upfront.", + "settings.admin.general.skillLazyLoading.label": "Load selected skills on demand", "settings.admin.general.skillPreviewMaxChars.description": "Characters shown when instructions exceed the read limit. Cannot exceed that limit. Leave empty to use the default.", "settings.admin.general.skillPreviewMaxChars.label": "Skill Preview Length", "settings.admin.general.title": "General",