diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 55a70b1291..f3e2a7a704 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2693,41 +2693,49 @@ 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) - # Skills — extract IDs from message content (<$skillId|label> tags) so - # persisted chats work without relying on the frontend to send skill_ids. - user_skill_ids = set(form_data.pop('skill_ids', None) or []) - user_skill_ids |= extract_skill_ids_from_messages(form_data.get('messages', [])) - model_skill_ids = set(model.get('info', {}).get('meta', {}).get('skillIds', [])) - - all_skill_ids = user_skill_ids | model_skill_ids + # Mentioned skills get full content; selected/default skills can be loaded through view_skill. + mentioned_skill_ids = extract_skill_ids_from_messages(form_data.get('messages', [])) + skill_ids = ( + set(form_data.pop('skill_ids', None) or []) + | set(model.get('info', {}).get('meta', {}).get('skillIds', [])) + | mentioned_skill_ids + ) available_skills = [] - if all_skill_ids: + view_skill_ids = [] + use_builtin_tools = ( + bool(metadata.get('session_id')) + and metadata.get('params', {}).get('function_calling') != 'legacy' + and (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get('builtin_tools', True) + ) + + if skill_ids: from open_webui.models.skills import Skills as SkillsModel accessible_skill_ids = {s.id for s in await SkillsModel.get_skills_by_user_id(user.id, 'read')} - available_skills = [] - for sid in all_skill_ids: + for sid in skill_ids: if sid in accessible_skill_ids: s = await SkillsModel.get_skill_by_id(sid) if s and s.is_active: available_skills.append(s) - skill_descriptions = '' + skill_manifest = '' for skill in available_skills: - if skill.id in user_skill_ids: - # User-selected: inject full content + if skill.id in mentioned_skill_ids or not use_builtin_tools: form_data['messages'] = add_or_update_system_message( f'\n{skill.content}\n', form_data['messages'], append=True, ) else: - # Model-attached: name+description only - skill_descriptions += f'\n{skill.id}\n{skill.name}\n{skill.description or ""}\n\n' + view_skill_ids.append(skill.id) + skill_manifest += ( + f'\n{skill.id}\n{skill.name}\n' + f'{skill.description or ""}\n\n' + ) - if skill_descriptions: + if skill_manifest: form_data['messages'] = add_or_update_system_message( - f'\n{skill_descriptions}', + f'\n{skill_manifest}', form_data['messages'], append=True, ) @@ -2916,11 +2924,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Inject builtin tools for native function calling based on enabled features and model capability. # Only inject when the request originates from the UI (identified by session_id). # API callers don't expect hidden tools; they can explicitly request tools via tool_ids. - builtin_tools_enabled = (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get( - 'builtin_tools', True - ) - has_session = bool(metadata.get('session_id')) - if has_session and metadata.get('params', {}).get('function_calling') != 'legacy' and builtin_tools_enabled: + if use_builtin_tools: # Add file context to user messages chat_id = metadata.get('chat_id') form_data['messages'] = await add_file_context(form_data.get('messages', []), chat_id, user) @@ -2929,7 +2933,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 if s.id not in user_skill_ids], + '__skill_ids__': view_skill_ids, }, features, model,