diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index f5c182eb3f..de48336799 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import copy import logging import mimetypes import os @@ -239,6 +240,7 @@ from open_webui.utils.middleware import ( process_chat_payload, process_chat_response, ) +from open_webui.utils.misc import merge_model_params from open_webui.utils.model_ids import strip_provider_model_prefix from open_webui.utils.models import ( check_model_access, @@ -1096,17 +1098,15 @@ async def chat_completion( await _set_direct_model(request, model, user) # Model params: global defaults as base, per-model overrides win - default_model_params = await Config.get('models.default_params', {}) or {} - model_info_params = { - **default_model_params, - **(model_info.params.model_dump() if model_info and model_info.params else {}), - } + # Deep copy: downstream mutates params in place and the config default is shared + default_model_params = copy.deepcopy(await Config.get('models.default_params', {}) or {}) + model_info_params = merge_model_params( + default_model_params, + model_info.params.model_dump() if model_info and model_info.params else {}, + ) request_params = {key: value for key, value in (form_data.get('params') or {}).items() if value is not None} if model_info_params or request_params: - form_data['params'] = { - **model_info_params, - **request_params, - } + form_data['params'] = merge_model_params(model_info_params, request_params) # Check base model existence for custom models if model_info and model_info.base_model_id: diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index 4e0f756cb8..39608ccf5c 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -29,6 +29,16 @@ def deep_update(d, u): return d +def merge_model_params(base: dict, override: dict) -> dict: + # Params override wholesale, except custom_params: a user-defined bag merged per key + params = {**base, **override} + base_custom = base.get('custom_params') + override_custom = override.get('custom_params') + if isinstance(base_custom, dict) and (override_custom is None or isinstance(override_custom, dict)): + params['custom_params'] = {**base_custom, **(override_custom or {})} + return params + + def _strip_filter_entry(entry): # Compose list-form env syntax passes surrounding quotes through verbatim return (entry or '').strip().strip('"\'').strip()