mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
feat: add configurable subagent model
This commit is contained in:
parent
d02b6a21fc
commit
ff3da6a5ee
4 changed files with 98 additions and 10 deletions
|
|
@ -2028,6 +2028,7 @@ ENABLE_CALENDAR = os.getenv('ENABLE_CALENDAR', 'True').lower() == 'true'
|
|||
ENABLE_AUTOMATIONS = os.getenv('ENABLE_AUTOMATIONS', 'True').lower() == 'true'
|
||||
|
||||
ENABLE_SUBAGENTS = os.getenv('ENABLE_SUBAGENTS', 'False').lower() == 'true'
|
||||
SUBAGENTS_MODEL_ID = os.getenv('SUBAGENTS_MODEL_ID', '')
|
||||
SUBAGENTS_BACKGROUND_ENABLED = os.getenv('SUBAGENTS_BACKGROUND_ENABLED', 'False').lower() == 'true'
|
||||
SUBAGENTS_MAX_CONCURRENT = int(os.getenv('SUBAGENTS_MAX_CONCURRENT', '20'))
|
||||
SUBAGENTS_MAX_ASYNC = int(os.getenv('SUBAGENTS_MAX_ASYNC', '20'))
|
||||
|
|
@ -3092,6 +3093,7 @@ DEFAULT_CONFIG = {
|
|||
'calendar.enable': ENABLE_CALENDAR,
|
||||
'automations.enable': ENABLE_AUTOMATIONS,
|
||||
'subagents.enable': ENABLE_SUBAGENTS,
|
||||
'subagents.model_id': SUBAGENTS_MODEL_ID,
|
||||
'subagents.background_enabled': SUBAGENTS_BACKGROUND_ENABLED,
|
||||
'subagents.max_concurrent': SUBAGENTS_MAX_CONCURRENT,
|
||||
'subagents.max_async': SUBAGENTS_MAX_ASYNC,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ MODELS_CONFIG_KEYS = {
|
|||
}
|
||||
SUBAGENTS_CONFIG_KEYS = {
|
||||
'ENABLE_SUBAGENTS': 'subagents.enable',
|
||||
'SUBAGENTS_MODEL_ID': 'subagents.model_id',
|
||||
'SUBAGENTS_BACKGROUND_ENABLED': 'subagents.background_enabled',
|
||||
'SUBAGENTS_MAX_CONCURRENT': 'subagents.max_concurrent',
|
||||
'SUBAGENTS_MAX_ASYNC': 'subagents.max_async',
|
||||
|
|
@ -766,6 +767,7 @@ async def set_models_config(request: Request, form_data: ModelsConfigForm, user=
|
|||
|
||||
class SubagentsConfigForm(BaseModel):
|
||||
ENABLE_SUBAGENTS: bool
|
||||
SUBAGENTS_MODEL_ID: str = ''
|
||||
SUBAGENTS_BACKGROUND_ENABLED: bool
|
||||
SUBAGENTS_MAX_CONCURRENT: int
|
||||
SUBAGENTS_MAX_ASYNC: int
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ _foreground_semaphore: asyncio.Semaphore | None = None
|
|||
_parent_locks: dict[str, asyncio.Lock] = {}
|
||||
|
||||
|
||||
def get_subagent_model_ids(
|
||||
parent_model_id: str | None, configured_model_id: str | None
|
||||
) -> tuple[str | None, str | None]:
|
||||
subagent_model_id = configured_model_id or parent_model_id
|
||||
return parent_model_id, subagent_model_id
|
||||
|
||||
|
||||
def _build_request(source: Request, user_id: str, *, internal: bool) -> Request:
|
||||
scope = {
|
||||
'type': 'http',
|
||||
|
|
@ -123,7 +130,17 @@ async def process_pending_internal_messages(
|
|||
if kind == 'timer' and first_meta.get('timer_id'):
|
||||
timer = await Chats.get_chat_by_id(first_meta['timer_id'])
|
||||
run = {**run, **(((timer.meta or {}).get('run') if timer else None) or {})}
|
||||
model_id = first.get('model') or run['model_id']
|
||||
parent_model_id = first_meta.get('parent_model_id') or run.get('parent_model_id')
|
||||
subagent_model_id = (
|
||||
first.get('model')
|
||||
or first_meta.get('subagent_model_id')
|
||||
or run.get('subagent_model_id')
|
||||
or run.get('model_id')
|
||||
)
|
||||
if kind == 'subagent':
|
||||
model_id = parent_model_id or subagent_model_id
|
||||
else:
|
||||
model_id = subagent_model_id
|
||||
if kind == 'timer':
|
||||
batch = [first]
|
||||
else:
|
||||
|
|
@ -132,7 +149,7 @@ async def process_pending_internal_messages(
|
|||
for message in pending
|
||||
for meta in [message.get('meta') or {}]
|
||||
if message.get('parentId') == parent_id
|
||||
and (message.get('model') or model_id) == model_id
|
||||
and (message.get('model') or subagent_model_id) == subagent_model_id
|
||||
and (
|
||||
meta.get('internal') is True
|
||||
and meta.get('type') == 'subagent'
|
||||
|
|
@ -294,6 +311,7 @@ async def delegate(
|
|||
'subagents.max_iterations',
|
||||
'subagents.max_output',
|
||||
'subagents.system_prompt',
|
||||
'subagents.model_id',
|
||||
)
|
||||
max_concurrent = int(config.get('subagents.max_concurrent') or 20)
|
||||
max_async = int(config.get('subagents.max_async') or 20)
|
||||
|
|
@ -314,8 +332,13 @@ async def delegate(
|
|||
and await Config.get('code_interpreter.engine', 'pyodide') != 'jupyter'
|
||||
):
|
||||
features.pop('code_interpreter')
|
||||
parent_model_id, subagent_model_id = get_subagent_model_ids(
|
||||
metadata.get('model_id') or (metadata.get('model') or {}).get('id'),
|
||||
config.get('subagents.model_id'),
|
||||
)
|
||||
run = {
|
||||
'model_id': metadata.get('model_id') or (metadata.get('model') or {}).get('id'),
|
||||
'parent_model_id': parent_model_id,
|
||||
'subagent_model_id': subagent_model_id,
|
||||
'session_id': metadata.get('session_id'),
|
||||
'tool_ids': copy.deepcopy(metadata.get('tool_ids') or []),
|
||||
'skill_ids': copy.deepcopy(metadata.get('skill_ids') or []),
|
||||
|
|
@ -328,7 +351,7 @@ async def delegate(
|
|||
'variables': copy.deepcopy(metadata.get('variables') or {}),
|
||||
'direct': bool(metadata.get('direct')),
|
||||
}
|
||||
if not run.get('model_id'):
|
||||
if not run.get('subagent_model_id'):
|
||||
return 'Error: model context is required.'
|
||||
if run.get('direct'):
|
||||
return 'Error: sub-agents are unavailable for direct connections.'
|
||||
|
|
@ -391,7 +414,7 @@ async def delegate(
|
|||
'role': 'user',
|
||||
'content': prompt,
|
||||
'timestamp': int(time.time()),
|
||||
'models': [run['model_id']],
|
||||
'models': [run['subagent_model_id']],
|
||||
**({'files': prompt_files} if prompt_files else {}),
|
||||
}
|
||||
chat = await Chats.insert_new_chat(
|
||||
|
|
@ -401,7 +424,7 @@ async def delegate(
|
|||
chat={
|
||||
'id': chat_id,
|
||||
'title': f'Sub-agent: {task[:60]}',
|
||||
'models': [run['model_id']],
|
||||
'models': [run['subagent_model_id']],
|
||||
'history': {
|
||||
'currentId': assistant_message_id,
|
||||
'messages': {
|
||||
|
|
@ -413,7 +436,7 @@ async def delegate(
|
|||
'role': 'assistant',
|
||||
'content': '',
|
||||
'done': False,
|
||||
'model': run['model_id'],
|
||||
'model': run['subagent_model_id'],
|
||||
'timestamp': int(time.time()),
|
||||
},
|
||||
},
|
||||
|
|
@ -457,7 +480,7 @@ async def delegate(
|
|||
str(config.get('subagents.system_prompt') or '').strip() or DEFAULT_SUBAGENT_SYSTEM_PROMPT
|
||||
)
|
||||
form_data = {
|
||||
'model': run['model_id'],
|
||||
'model': run['subagent_model_id'],
|
||||
'messages': [
|
||||
{
|
||||
'role': 'system',
|
||||
|
|
@ -589,6 +612,8 @@ async def delegate(
|
|||
'type': 'subagent',
|
||||
'delegation_id': delegation_id,
|
||||
'subagent_chat_id': chat_id,
|
||||
'parent_model_id': run['parent_model_id'],
|
||||
'subagent_model_id': run['subagent_model_id'],
|
||||
}
|
||||
pending_message = {
|
||||
'id': pending_message_id,
|
||||
|
|
@ -596,7 +621,7 @@ async def delegate(
|
|||
'childrenIds': [],
|
||||
'role': 'user',
|
||||
'content': '\n'.join(lines),
|
||||
'model': run['model_id'],
|
||||
'model': run['subagent_model_id'],
|
||||
'meta': pending_meta,
|
||||
'timestamp': int(time.time()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
import { getContext, onMount } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { getModels } from '$lib/apis';
|
||||
import { getSubagentsConfig, setSubagentsConfig } from '$lib/apis/configs';
|
||||
import { getBaseModels } from '$lib/apis/models';
|
||||
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
|
||||
|
|
@ -11,23 +14,50 @@
|
|||
let loading = true;
|
||||
let saving = false;
|
||||
let enabled = false;
|
||||
let subagentModelId = '';
|
||||
let backgroundEnabled = false;
|
||||
let maxConcurrent = 20;
|
||||
let maxAsync = 20;
|
||||
let maxIterations = 30;
|
||||
let maxOutput = 30000;
|
||||
let systemPrompt = '';
|
||||
let models: any[] = [];
|
||||
|
||||
const normalizeModelSelection = (modelId: string | null | undefined) => {
|
||||
if (!modelId) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const model = models.find((item: any) => item.id === modelId);
|
||||
if (!model) {
|
||||
return modelId;
|
||||
}
|
||||
|
||||
return model.id;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const config = await getSubagentsConfig(localStorage.token);
|
||||
const [config, workspaceModels, baseModels] = await Promise.all([
|
||||
getSubagentsConfig(localStorage.token),
|
||||
getBaseModels(localStorage.token),
|
||||
getModels(localStorage.token, null, false)
|
||||
]);
|
||||
enabled = config?.ENABLE_SUBAGENTS ?? false;
|
||||
subagentModelId = config?.SUBAGENTS_MODEL_ID ?? '';
|
||||
backgroundEnabled = config?.SUBAGENTS_BACKGROUND_ENABLED ?? false;
|
||||
maxConcurrent = Number(config?.SUBAGENTS_MAX_CONCURRENT) || 20;
|
||||
maxAsync = Number(config?.SUBAGENTS_MAX_ASYNC) || 20;
|
||||
maxIterations = Number(config?.SUBAGENTS_MAX_ITERATIONS) || 30;
|
||||
maxOutput = Number(config?.SUBAGENTS_MAX_OUTPUT) || 30000;
|
||||
systemPrompt = config?.SUBAGENTS_SYSTEM_PROMPT ?? '';
|
||||
models = baseModels.map((model: any) => {
|
||||
const workspaceModel = workspaceModels.find((item: any) => item.id === model.id);
|
||||
|
||||
return workspaceModel
|
||||
? { ...model, ...workspaceModel }
|
||||
: { ...model, id: model.id, name: model.name, is_active: true };
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error(`${error}`);
|
||||
} finally {
|
||||
|
|
@ -40,6 +70,7 @@
|
|||
try {
|
||||
await setSubagentsConfig(localStorage.token, {
|
||||
ENABLE_SUBAGENTS: enabled,
|
||||
SUBAGENTS_MODEL_ID: subagentModelId,
|
||||
SUBAGENTS_BACKGROUND_ENABLED: backgroundEnabled,
|
||||
SUBAGENTS_MAX_CONCURRENT: maxConcurrent,
|
||||
SUBAGENTS_MAX_ASYNC: maxAsync,
|
||||
|
|
@ -77,6 +108,34 @@
|
|||
</p>
|
||||
|
||||
{#if enabled}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-model">
|
||||
{$i18n.t('Sub-agent Model')}
|
||||
</label>
|
||||
<SettingsSelect
|
||||
id="sa-model"
|
||||
bind:value={subagentModelId}
|
||||
className="mt-1 w-full"
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
subagentModelId = normalizeModelSelection(subagentModelId);
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each models as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
{model?.connection_type === 'local' ? `(${$i18n.t('Local')})` : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
<p class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t(
|
||||
'Choose a dedicated model for sub-agents. Current Model follows the active chat model.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-concurrent">
|
||||
{$i18n.t('Max concurrent')}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue