mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-06 02:48:04 +00:00
fix: expose model/selected skills as a lazy-loaded view_skill manifest
This commit is contained in:
parent
4be25c4fdd
commit
8516c80762
2 changed files with 34 additions and 53 deletions
|
|
@ -2619,19 +2619,28 @@ 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 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', []))
|
||||
# Skills are surfaced two ways:
|
||||
# - $-mention tags (<$skillId|label>) in the message pull a skill's full content
|
||||
# into the chat immediately (extracted here so persisted chats work too).
|
||||
# - skill_ids (the integrations menu, where the frontend also pre-selects the
|
||||
# model's attached skills) plus the model's own attached skills are exposed as a
|
||||
# lightweight <available_skills> manifest, loaded on demand via view_skill.
|
||||
mention_skill_ids = extract_skill_ids_from_messages(form_data.get('messages', []))
|
||||
menu_skill_ids = set(form_data.pop('skill_ids', None) or [])
|
||||
model_skill_ids = set(model.get('info', {}).get('meta', {}).get('skillIds', []))
|
||||
all_skill_ids = mention_skill_ids | menu_skill_ids | model_skill_ids
|
||||
|
||||
# The view_skill tool is only registered for native function calling with builtin
|
||||
# tools enabled (see get_builtin_tools); when it can't be, manifest skills are
|
||||
# injected as full content too so they stay usable.
|
||||
builtin_tools_enabled = (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get('builtin_tools', True)
|
||||
skills_lazy_load = metadata.get('params', {}).get('function_calling') == 'native' and builtin_tools_enabled
|
||||
|
||||
all_skill_ids = user_skill_ids | model_skill_ids
|
||||
available_skills = []
|
||||
if all_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:
|
||||
if sid in accessible_skill_ids:
|
||||
s = await SkillsModel.get_skill_by_id(sid)
|
||||
|
|
@ -2640,15 +2649,15 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||
|
||||
skill_descriptions = ''
|
||||
for skill in available_skills:
|
||||
if skill.id in user_skill_ids:
|
||||
# User-selected: inject full content
|
||||
# $-mentioned skills (and, when view_skill can't be registered, every skill)
|
||||
# get full content injected; the rest are lightweight manifest entries.
|
||||
if skill.id in mention_skill_ids or not skills_lazy_load:
|
||||
form_data['messages'] = add_or_update_system_message(
|
||||
f'<skill name="{skill.name}">\n{skill.content}\n</skill>',
|
||||
form_data['messages'],
|
||||
append=True,
|
||||
)
|
||||
else:
|
||||
# Model-attached: name+description only
|
||||
skill_descriptions += f'<skill>\n<id>{skill.id}</id>\n<name>{skill.name}</name>\n<description>{skill.description or ""}</description>\n</skill>\n'
|
||||
|
||||
if skill_descriptions:
|
||||
|
|
@ -2838,11 +2847,8 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||
if mcp_clients:
|
||||
metadata['mcp_clients'] = mcp_clients
|
||||
|
||||
# Inject builtin tools for native function calling based on enabled features and model capability
|
||||
# Check if builtin_tools capability is enabled for this model (defaults to True if not specified)
|
||||
builtin_tools_enabled = (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get(
|
||||
'builtin_tools', True
|
||||
)
|
||||
# Inject builtin tools for native function calling based on enabled features and model capability.
|
||||
# builtin_tools_enabled (model "builtin_tools" capability) was resolved with the skills above.
|
||||
if metadata.get('params', {}).get('function_calling') == 'native' and builtin_tools_enabled:
|
||||
# Add file context to user messages
|
||||
chat_id = metadata.get('chat_id')
|
||||
|
|
@ -2852,7 +2858,10 @@ 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],
|
||||
# This branch only runs when skills_lazy_load is True; $-mentioned
|
||||
# skills are injected as full content, the rest are manifest entries
|
||||
# the model loads via view_skill.
|
||||
'__skill_ids__': [s.id for s in available_skills if s.id not in mention_skill_ids],
|
||||
},
|
||||
features,
|
||||
model,
|
||||
|
|
|
|||
|
|
@ -374,7 +374,11 @@
|
|||
selectedToolIds = selectedToolIds.filter((id) => !id.startsWith('direct_server:'));
|
||||
}
|
||||
|
||||
// Set Default Skills
|
||||
// Pre-select model-attached skills so they appear enabled in the integrations
|
||||
// menu. These — and any skill the user enables there or via $-mention — are sent
|
||||
// as skill_ids and exposed to the model as an <available_skills> manifest; the
|
||||
// model loads full content on demand via the builtin view_skill tool rather than
|
||||
// having it injected up front.
|
||||
if (model?.info?.meta?.skillIds) {
|
||||
selectedSkillIds = [
|
||||
...new Set(
|
||||
|
|
@ -2386,44 +2390,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Parse skill mentions (<$skillId|label>) from user messages
|
||||
const skillMentionRegex = /<\$([^|>]+)\|?[^>]*>/g;
|
||||
// Skills enabled via the integrations menu (the model's attached skills are
|
||||
// pre-selected here) are sent as skill_ids and exposed to the model as a manifest,
|
||||
// loaded on demand via view_skill. $-mention skills are left inline in the message:
|
||||
// the backend extracts them, injects their full content into the chat, and strips
|
||||
// the markup — so a mention is pulled into the chat rather than lazy-loaded.
|
||||
const skillIds = [...selectedSkillIds];
|
||||
const mentionSkillIds = [];
|
||||
for (const message of messages) {
|
||||
const content =
|
||||
typeof message.content === 'string' ? message.content : (message.content?.[0]?.text ?? '');
|
||||
for (const match of content.matchAll(skillMentionRegex)) {
|
||||
if (!mentionSkillIds.includes(match[1])) {
|
||||
mentionSkillIds.push(match[1]);
|
||||
}
|
||||
if (!skillIds.includes(match[1])) {
|
||||
skillIds.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip skill mentions from message content
|
||||
if (mentionSkillIds.length > 0) {
|
||||
messages = messages.map((message) => {
|
||||
if (typeof message.content === 'string') {
|
||||
return {
|
||||
...message,
|
||||
content: message.content.replace(/<\$[^>]+>/g, '').trim()
|
||||
};
|
||||
} else if (Array.isArray(message.content)) {
|
||||
return {
|
||||
...message,
|
||||
content: message.content.map((part) =>
|
||||
part.type === 'text'
|
||||
? { ...part, text: part.text.replace(/<\$[^>]+>/g, '').trim() }
|
||||
: part
|
||||
)
|
||||
};
|
||||
}
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
// Use the user-selected terminal from the dropdown
|
||||
const activeTerminalId = $selectedTerminalId ?? null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue