diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 97bed90b51..4dfa6dea99 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1263,6 +1263,18 @@ MODEL_ORDER_LIST = PersistentConfig( [], ) +DEFAULT_MODEL_METADATA = PersistentConfig( + "DEFAULT_MODEL_METADATA", + "models.default_metadata", + {}, +) + +DEFAULT_MODEL_PARAMS = PersistentConfig( + "DEFAULT_MODEL_PARAMS", + "models.default_params", + {}, +) + DEFAULT_USER_ROLE = PersistentConfig( "DEFAULT_USER_ROLE", "ui.default_user_role", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 6b2df1b457..30ce10f291 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -394,6 +394,8 @@ from open_webui.config import ( DEFAULT_PINNED_MODELS, DEFAULT_ARENA_MODEL, MODEL_ORDER_LIST, + DEFAULT_MODEL_METADATA, + DEFAULT_MODEL_PARAMS, EVALUATION_ARENA_MODELS, # WebUI (OAuth) ENABLE_OAUTH_ROLE_MANAGEMENT, @@ -822,6 +824,8 @@ app.state.config.ADMIN_EMAIL = ADMIN_EMAIL app.state.config.DEFAULT_MODELS = DEFAULT_MODELS app.state.config.DEFAULT_PINNED_MODELS = DEFAULT_PINNED_MODELS app.state.config.MODEL_ORDER_LIST = MODEL_ORDER_LIST +app.state.config.DEFAULT_MODEL_METADATA = DEFAULT_MODEL_METADATA +app.state.config.DEFAULT_MODEL_PARAMS = DEFAULT_MODEL_PARAMS app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS @@ -1688,9 +1692,14 @@ async def chat_completion( request.state.direct = True request.state.model = model - model_info_params = ( - model_info.params.model_dump() if model_info and model_info.params else {} + # Model params: global defaults as base, per-model overrides win + default_model_params = ( + getattr(request.app.state.config, "DEFAULT_MODEL_PARAMS", None) or {} ) + model_info_params = { + **default_model_params, + **(model_info.params.model_dump() if model_info and model_info.params else {}), + } # Check base model existence for custom models if model_info_params.get("base_model_id"): diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 4dee4488cf..d1703d7020 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -467,6 +467,8 @@ class ModelsConfigForm(BaseModel): DEFAULT_MODELS: Optional[str] DEFAULT_PINNED_MODELS: Optional[str] MODEL_ORDER_LIST: Optional[list[str]] + DEFAULT_MODEL_METADATA: Optional[dict] = None + DEFAULT_MODEL_PARAMS: Optional[dict] = None @router.get("/models", response_model=ModelsConfigForm) @@ -475,6 +477,8 @@ async def get_models_config(request: Request, user=Depends(get_admin_user)): "DEFAULT_MODELS": request.app.state.config.DEFAULT_MODELS, "DEFAULT_PINNED_MODELS": request.app.state.config.DEFAULT_PINNED_MODELS, "MODEL_ORDER_LIST": request.app.state.config.MODEL_ORDER_LIST, + "DEFAULT_MODEL_METADATA": request.app.state.config.DEFAULT_MODEL_METADATA, + "DEFAULT_MODEL_PARAMS": request.app.state.config.DEFAULT_MODEL_PARAMS, } @@ -485,10 +489,14 @@ async def set_models_config( request.app.state.config.DEFAULT_MODELS = form_data.DEFAULT_MODELS request.app.state.config.DEFAULT_PINNED_MODELS = form_data.DEFAULT_PINNED_MODELS request.app.state.config.MODEL_ORDER_LIST = form_data.MODEL_ORDER_LIST + request.app.state.config.DEFAULT_MODEL_METADATA = form_data.DEFAULT_MODEL_METADATA + request.app.state.config.DEFAULT_MODEL_PARAMS = form_data.DEFAULT_MODEL_PARAMS return { "DEFAULT_MODELS": request.app.state.config.DEFAULT_MODELS, "DEFAULT_PINNED_MODELS": request.app.state.config.DEFAULT_PINNED_MODELS, "MODEL_ORDER_LIST": request.app.state.config.MODEL_ORDER_LIST, + "DEFAULT_MODEL_METADATA": request.app.state.config.DEFAULT_MODEL_METADATA, + "DEFAULT_MODEL_PARAMS": request.app.state.config.DEFAULT_MODEL_PARAMS, } diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index e2f9e5bcad..285de54fc9 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -1,3 +1,4 @@ +import copy import time import logging import asyncio @@ -307,6 +308,29 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) except Exception as e: log.info(f"Failed to load function module for {function_id}: {e}") + # Apply global model defaults to all models + # Per-model overrides take precedence over global defaults + default_metadata = ( + getattr(request.app.state.config, "DEFAULT_MODEL_METADATA", None) or {} + ) + + if default_metadata: + for model in models: + info = model.get("info") + + if info is None: + model["info"] = {"meta": copy.deepcopy(default_metadata)} + continue + + meta = info.setdefault("meta", {}) + for key, value in default_metadata.items(): + if key == "capabilities": + # Merge capabilities: defaults as base, per-model overrides win + existing = meta.get("capabilities") or {} + meta["capabilities"] = {**value, **existing} + elif meta.get(key) is None: + meta[key] = copy.deepcopy(value) + for model in models: action_ids = [ action_id diff --git a/src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte b/src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte index a48335a8de..3c34c26490 100644 --- a/src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte +++ b/src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte @@ -6,6 +6,7 @@ const dispatch = createEventDispatcher(); import { models } from '$lib/stores'; + import { DEFAULT_CAPABILITIES } from '$lib/constants'; import { deleteAllModels } from '$lib/apis/models'; import { getModelsConfig, setModelsConfig } from '$lib/apis/configs'; @@ -21,12 +22,22 @@ import XMark from '$lib/components/icons/XMark.svelte'; import ModelSelector from './ModelSelector.svelte'; import Model from '../Evaluations/Model.svelte'; + import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte'; + + import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte'; + import DefaultFeatures from '$lib/components/workspace/Models/DefaultFeatures.svelte'; + import BuiltinTools from '$lib/components/workspace/Models/BuiltinTools.svelte'; + + import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.svelte'; + import Eye from '$lib/components/icons/Eye.svelte'; export let show = false; export let initHandler = () => {}; let config = null; + let selectedTab = 'defaults'; + let selectedModelId = ''; let defaultModelIds = []; @@ -40,6 +51,13 @@ let loading = false; let showResetModal = false; + let showDefaultCapabilities = false; + let showDefaultParams = false; + + let defaultCapabilities = {}; + let defaultFeatureIds = []; + let defaultParams = {}; + let builtinTools = {}; $: if (show) { init(); @@ -74,14 +92,36 @@ sortKey = ''; sortOrder = ''; + + const savedMeta = config?.DEFAULT_MODEL_METADATA; + if (savedMeta && Object.keys(savedMeta).length > 0) { + defaultCapabilities = savedMeta.capabilities ?? { ...DEFAULT_CAPABILITIES }; + defaultFeatureIds = savedMeta.defaultFeatureIds ?? []; + builtinTools = savedMeta.builtinTools ?? {}; + } else { + defaultCapabilities = { ...DEFAULT_CAPABILITIES }; + defaultFeatureIds = []; + builtinTools = {}; + } + defaultParams = config?.DEFAULT_MODEL_PARAMS ?? {}; }; const submitHandler = async () => { loading = true; + const metadata = { + capabilities: defaultCapabilities, + ...(defaultFeatureIds.length > 0 ? { defaultFeatureIds } : {}), + ...(Object.keys(builtinTools).length > 0 ? { builtinTools } : {}) + }; + const res = await setModelsConfig(localStorage.token, { DEFAULT_MODELS: defaultModelIds.join(','), DEFAULT_PINNED_MODELS: defaultPinnedModelIds.join(','), - MODEL_ORDER_LIST: modelIds + MODEL_ORDER_LIST: modelIds, + DEFAULT_MODEL_METADATA: metadata, + DEFAULT_MODEL_PARAMS: Object.fromEntries( + Object.entries(defaultParams).filter(([_, v]) => v !== null && v !== '' && v !== undefined) + ) }); if (res) { @@ -113,7 +153,7 @@ }} /> - +
@@ -129,7 +169,7 @@
-
+
{#if config}
-
-
- - - -
-
- -
- - - -
- - - -
- - - - - +
{$i18n.t('Defaults')}
+ + + +
+ +
+
+ {#if selectedTab === 'defaults'} + + +
+ + + +
+ +
+ + + {#if showDefaultCapabilities} +
+ + + {#if Object.keys(defaultCapabilities).filter((key) => defaultCapabilities[key]).length > 0} + {@const availableFeatures = Object.entries(defaultCapabilities) + .filter( + ([key, value]) => + value && + ['web_search', 'code_interpreter', 'image_generation'].includes( + key + ) + ) + .map(([key, value]) => key)} + + {#if availableFeatures.length > 0} +
+ +
+ {/if} + {/if} + + {#if defaultCapabilities.builtin_tools} +
+ +
+ {/if} +
+ {/if} +
+ +
+ +
+ + + {#if showDefaultParams} +
+ +
+ {/if} +
+ {:else if selectedTab === 'display'} +
+
+ + + +
+
+ +
+ +
+ + + +
+ {/if} +
+ +
+ +
+
{:else} diff --git a/src/lib/components/admin/Settings/Models/ModelSelector.svelte b/src/lib/components/admin/Settings/Models/ModelSelector.svelte index 6af182453d..d76326aa05 100644 --- a/src/lib/components/admin/Settings/Models/ModelSelector.svelte +++ b/src/lib/components/admin/Settings/Models/ModelSelector.svelte @@ -3,8 +3,10 @@ const i18n = getContext('i18n'); import Minus from '$lib/components/icons/Minus.svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; export let title = ''; + export let tooltip = ''; export let models = []; export let modelIds = []; @@ -14,7 +16,27 @@
-
{title}
+
+ {title} + {#if tooltip} + + + + + + {/if} +
diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte index 760d128db5..df9ab80292 100644 --- a/src/lib/components/workspace/Models/ModelEditor.svelte +++ b/src/lib/components/workspace/Models/ModelEditor.svelte @@ -3,7 +3,7 @@ import { onMount, getContext, tick } from 'svelte'; import { models, tools, functions, user } from '$lib/stores'; - import { WEBUI_BASE_URL } from '$lib/constants'; + import { WEBUI_BASE_URL, DEFAULT_CAPABILITIES } from '$lib/constants'; import { getTools } from '$lib/apis/tools'; import { getFunctions } from '$lib/apis/functions'; @@ -95,18 +95,7 @@ let filterIds = []; let defaultFilterIds = []; - let capabilities = { - file_context: true, - vision: true, - file_upload: true, - web_search: true, - image_generation: true, - code_interpreter: true, - citations: true, - status_updates: true, - usage: undefined, - builtin_tools: true - }; + let capabilities = { ...DEFAULT_CAPABILITIES }; let defaultFeatureIds = []; let builtinTools = {}; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index d92f336718..3cc7743db8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -95,6 +95,19 @@ export const SUPPORTED_FILE_EXTENSIONS = [ 'msg' ]; +export const DEFAULT_CAPABILITIES = { + file_context: true, + vision: true, + file_upload: true, + web_search: true, + image_generation: true, + code_interpreter: true, + citations: true, + status_updates: true, + usage: undefined, + builtin_tools: true +}; + export const PASTED_TEXT_CHARACTER_LIMIT = 1000; // Source: https://kit.svelte.dev/docs/modules#$env-static-public