Refactor JSON handling to use JSONCodec

Updated JSON handling to use JSONCodec instead of json module for better compatibility. Added error handling for loading various configuration parameters.
This commit is contained in:
phone2good10-pixel 2026-08-19 16:40:20 +03:00 committed by GitHub
parent 01f4282f1f
commit bfea4bf881
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,7 +1,6 @@
from __future__ import annotations
import base64
import json
import logging
import os
import shutil
@ -35,11 +34,12 @@ from open_webui.env import (
log,
)
from open_webui.models.config import Config
from open_webui.utils.json_codec import JSONCodec
async def seed_registered_defaults():
await Config.rename_prefix('rag.web', 'web')
await Config.repair_flattened_dict_configs()
await Config.repair_config_rows()
await Config.seed_defaults(DEFAULT_CONFIG)
@ -84,7 +84,7 @@ async def import_legacy_config_json():
if not os.path.exists(f'{DATA_DIR}/config.json'):
return
with open(f'{DATA_DIR}/config.json', 'r') as _f:
await Config.upsert(json.load(_f))
await Config.upsert(JSONCodec.loads(_f.read()))
os.rename(f'{DATA_DIR}/config.json', f'{DATA_DIR}/old_config.json')
@ -114,6 +114,9 @@ for file_path in (FRONTEND_BUILD_DIR / 'static').glob('**/*'):
except Exception as e:
logging.error(f'An error occurred: {e}')
# LICENSE covers copied Open WebUI logo/favicon assets.
# Do not alter, remove, obscure, or replace them except as LICENSE permits:
# https://docs.openwebui.com/license.
frontend_favicon = FRONTEND_BUILD_DIR / 'static' / 'favicon.png'
if frontend_favicon.exists():
@ -181,6 +184,9 @@ CACHE_DIR.mkdir(parents=True, exist_ok=True)
# CUSTOM_NAME (Legacy)
####################################
# LICENSE covers this legacy Open WebUI branding path.
# Do not alter, remove, obscure, or replace it except as LICENSE permits:
# https://docs.openwebui.com/license.
CUSTOM_NAME = os.getenv('CUSTOM_NAME', '')
if CUSTOM_NAME:
@ -270,9 +276,9 @@ def _resolve_ollama_base_url(url: str) -> str:
if not default.result() and fallback.result():
url = url.replace(':11434', ':12434')
log.info(f'Ollama port 11434 unreachable on {host}, falling back to 12434')
log.info('Ollama port 11434 unreachable on %s, falling back to 12434', host)
elif not default.result():
log.info(f'Ollama ports 11434 and 12434 both unreachable on {host}')
log.info('Ollama ports 11434 and 12434 both unreachable on %s', host)
return url
@ -293,12 +299,12 @@ OLLAMA_API_CONFIGS = {}
_ollama_api_configs = os.getenv('OLLAMA_API_CONFIGS', '')
if _ollama_api_configs:
try:
parsed = json.loads(_ollama_api_configs)
parsed = JSONCodec.loads(_ollama_api_configs)
if isinstance(parsed, dict):
OLLAMA_API_CONFIGS = parsed
else:
log.warning('OLLAMA_API_CONFIGS must be a JSON object, ignoring')
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
log.warning('OLLAMA_API_CONFIGS is not valid JSON, ignoring')
####################################
@ -340,12 +346,12 @@ OPENAI_API_CONFIGS = {}
_openai_api_configs = os.getenv('OPENAI_API_CONFIGS', '')
if _openai_api_configs:
try:
parsed = json.loads(_openai_api_configs)
parsed = JSONCodec.loads(_openai_api_configs)
if isinstance(parsed, dict):
OPENAI_API_CONFIGS = parsed
else:
log.warning('OPENAI_API_CONFIGS must be a JSON object, ignoring')
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
log.warning('OPENAI_API_CONFIGS is not valid JSON, ignoring')
# Get the actual OpenAI API key based on the base URL
@ -369,7 +375,7 @@ ENABLE_BASE_MODELS_CACHE = os.getenv('ENABLE_BASE_MODELS_CACHE', 'False').lower(
####################################
try:
tool_server_connections = json.loads(os.getenv('TOOL_SERVER_CONNECTIONS', '[]'))
tool_server_connections = JSONCodec.loads(os.getenv('TOOL_SERVER_CONNECTIONS', '[]'))
except Exception as e:
log.exception(f'Error loading TOOL_SERVER_CONNECTIONS: {e}')
tool_server_connections = []
@ -383,12 +389,12 @@ OAUTH_CLIENT_TIMEOUT = os.getenv('OAUTH_CLIENT_TIMEOUT', '')
# TERMINAL_SERVER
####################################
terminal_server_connections = json.loads(os.getenv('TERMINAL_SERVER_CONNECTIONS', '[]'))
terminal_server_connections = JSONCodec.loads(os.getenv('TERMINAL_SERVER_CONNECTIONS', '[]'))
TERMINAL_SERVER_CONNECTIONS = terminal_server_connections
try:
TERMINAL_PROXY_HEADERS = json.loads(os.getenv('TERMINAL_PROXY_HEADERS', '{}'))
TERMINAL_PROXY_HEADERS = JSONCodec.loads(os.getenv('TERMINAL_PROXY_HEADERS', '{}'))
except Exception:
TERMINAL_PROXY_HEADERS = {}
@ -606,6 +612,7 @@ QDRANT_PREFER_GRPC = os.getenv('QDRANT_PREFER_GRPC', 'false').lower() == 'true'
QDRANT_GRPC_PORT = int(os.getenv('QDRANT_GRPC_PORT', '6334'))
QDRANT_TIMEOUT = int(os.getenv('QDRANT_TIMEOUT', '5'))
QDRANT_HNSW_M = int(os.getenv('QDRANT_HNSW_M', '16'))
QDRANT_EMBEDDING_MODEL = os.getenv('QDRANT_EMBEDDING_MODEL', 'BAAI/bge-m3')
ENABLE_QDRANT_MULTITENANCY_MODE = os.getenv('ENABLE_QDRANT_MULTITENANCY_MODE', 'true').lower() == 'true'
QDRANT_COLLECTION_PREFIX = os.getenv('QDRANT_COLLECTION_PREFIX', 'open-webui')
@ -805,7 +812,7 @@ if VECTOR_DB == 'oracle23ai':
'Oracle23ai requires setting ORACLE_WALLET_DIR and ORACLE_WALLET_PASSWORD when using wallet authentication.'
)
log.info(f'VECTOR_DB: {VECTOR_DB}')
log.info('VECTOR_DB: %s', VECTOR_DB)
# S3 Vector
S3_VECTOR_BUCKET_NAME = os.getenv('S3_VECTOR_BUCKET_NAME', None)
@ -894,8 +901,8 @@ MINERU_API_KEY = os.getenv('MINERU_API_KEY', '')
mineru_params = os.getenv('MINERU_PARAMS', '')
try:
mineru_params = json.loads(mineru_params)
except json.JSONDecodeError:
mineru_params = JSONCodec.loads(mineru_params)
except JSONCodec.JSONDecodeError:
mineru_params = {}
MINERU_PARAMS = mineru_params
@ -908,8 +915,8 @@ EXTERNAL_DOCUMENT_LOADER_API_KEY = os.getenv('EXTERNAL_DOCUMENT_LOADER_API_KEY',
external_document_loader_headers = os.getenv('EXTERNAL_DOCUMENT_LOADER_HEADERS', '')
try:
external_document_loader_headers = json.loads(external_document_loader_headers)
except json.JSONDecodeError:
external_document_loader_headers = JSONCodec.loads(external_document_loader_headers)
except JSONCodec.JSONDecodeError:
external_document_loader_headers = {}
if not isinstance(external_document_loader_headers, dict):
external_document_loader_headers = {}
@ -924,8 +931,8 @@ DOCLING_API_KEY = os.getenv('DOCLING_API_KEY', '')
docling_params = os.getenv('DOCLING_PARAMS', '')
try:
docling_params = json.loads(docling_params)
except json.JSONDecodeError:
docling_params = JSONCodec.loads(docling_params)
except JSONCodec.JSONDecodeError:
docling_params = {}
DOCLING_PARAMS = docling_params
@ -988,7 +995,7 @@ PDF_EXTRACT_IMAGES = os.getenv('PDF_EXTRACT_IMAGES', 'False').lower() == 'true'
PDF_LOADER_MODE = os.getenv('PDF_LOADER_MODE', 'page')
RAG_EMBEDDING_MODEL = os.getenv('RAG_EMBEDDING_MODEL', 'sentence-transformers/all-MiniLM-L6-v2')
log.info(f'Embedding model set: {RAG_EMBEDDING_MODEL}')
log.info('Embedding model set: %s', RAG_EMBEDDING_MODEL)
RAG_TOKENIZER_MODEL = os.getenv('RAG_TOKENIZER_MODEL', '')
@ -1016,7 +1023,7 @@ RAG_RERANKING_ENGINE = os.getenv('RAG_RERANKING_ENGINE', '')
RAG_RERANKING_MODEL = os.getenv('RAG_RERANKING_MODEL', '')
if RAG_RERANKING_MODEL != '':
log.info(f'Reranking model set: {RAG_RERANKING_MODEL}')
log.info('Reranking model set: %s', RAG_RERANKING_MODEL)
RAG_RERANKING_MODEL_AUTO_UPDATE = (
@ -1148,7 +1155,7 @@ WEB_SEARCH_RESULT_COUNT = int(os.getenv('WEB_SEARCH_RESULT_COUNT', '3'))
try:
web_search_domain_filter_list = json.loads(os.getenv('WEB_SEARCH_DOMAIN_FILTER_LIST', '[]'))
web_search_domain_filter_list = JSONCodec.loads(os.getenv('WEB_SEARCH_DOMAIN_FILTER_LIST', '[]'))
except Exception as e:
web_search_domain_filter_list = [
# "wikipedia.com",
@ -1297,8 +1304,8 @@ LINKUP_API_KEY = os.getenv('LINKUP_API_KEY', '')
linkup_search_params = os.getenv('LINKUP_SEARCH_PARAMS', '')
try:
linkup_search_params = json.loads(linkup_search_params)
except json.JSONDecodeError:
linkup_search_params = JSONCodec.loads(linkup_search_params)
except JSONCodec.JSONDecodeError:
linkup_search_params = {}
LINKUP_SEARCH_PARAMS = linkup_search_params
@ -1330,8 +1337,8 @@ AUTOMATIC1111_API_AUTH = os.getenv('AUTOMATIC1111_API_AUTH', '')
automatic1111_params = os.getenv('AUTOMATIC1111_PARAMS', '')
try:
automatic1111_params = json.loads(automatic1111_params)
except json.JSONDecodeError:
automatic1111_params = JSONCodec.loads(automatic1111_params)
except JSONCodec.JSONDecodeError:
automatic1111_params = {}
AUTOMATIC1111_PARAMS = automatic1111_params
@ -1455,8 +1462,8 @@ COMFYUI_WORKFLOW = os.getenv('COMFYUI_WORKFLOW', COMFYUI_DEFAULT_WORKFLOW)
comfyui_workflow_nodes = os.getenv('COMFYUI_WORKFLOW_NODES', '')
try:
comfyui_workflow_nodes = json.loads(comfyui_workflow_nodes)
except json.JSONDecodeError:
comfyui_workflow_nodes = JSONCodec.loads(comfyui_workflow_nodes)
except JSONCodec.JSONDecodeError:
comfyui_workflow_nodes = []
COMFYUI_WORKFLOW_NODES = comfyui_workflow_nodes
@ -1468,8 +1475,8 @@ IMAGES_OPENAI_API_KEY = os.getenv('IMAGES_OPENAI_API_KEY', OPENAI_API_KEY)
images_openai_params = os.getenv('IMAGES_OPENAI_PARAMS', '')
try:
images_openai_params = json.loads(images_openai_params)
except json.JSONDecodeError:
images_openai_params = JSONCodec.loads(images_openai_params)
except JSONCodec.JSONDecodeError:
images_openai_params = {}
@ -1507,8 +1514,8 @@ IMAGES_EDIT_COMFYUI_WORKFLOW = os.getenv('IMAGES_EDIT_COMFYUI_WORKFLOW', '')
images_edit_comfyui_workflow_nodes = os.getenv('IMAGES_EDIT_COMFYUI_WORKFLOW_NODES', '')
try:
images_edit_comfyui_workflow_nodes = json.loads(images_edit_comfyui_workflow_nodes)
except json.JSONDecodeError:
images_edit_comfyui_workflow_nodes = JSONCodec.loads(images_edit_comfyui_workflow_nodes)
except JSONCodec.JSONDecodeError:
images_edit_comfyui_workflow_nodes = []
IMAGES_EDIT_COMFYUI_WORKFLOW_NODES = images_edit_comfyui_workflow_nodes
@ -1582,8 +1589,8 @@ AUDIO_TTS_OPENAI_API_KEY = os.getenv('AUDIO_TTS_OPENAI_API_KEY', OPENAI_API_KEY)
audio_tts_openai_params = os.getenv('AUDIO_TTS_OPENAI_PARAMS', '')
try:
audio_tts_openai_params = json.loads(audio_tts_openai_params)
except json.JSONDecodeError:
audio_tts_openai_params = JSONCodec.loads(audio_tts_openai_params)
except JSONCodec.JSONDecodeError:
audio_tts_openai_params = {}
AUDIO_TTS_OPENAI_PARAMS = audio_tts_openai_params
@ -1635,7 +1642,7 @@ DEFAULT_MODELS = os.getenv('DEFAULT_MODELS', None)
DEFAULT_PINNED_MODELS = os.getenv('DEFAULT_PINNED_MODELS', None)
try:
default_prompt_suggestions = json.loads(os.getenv('DEFAULT_PROMPT_SUGGESTIONS', '[]'))
default_prompt_suggestions = JSONCodec.loads(os.getenv('DEFAULT_PROMPT_SUGGESTIONS', '[]'))
except Exception as e:
log.exception(f'Error loading DEFAULT_PROMPT_SUGGESTIONS: {e}')
default_prompt_suggestions = []
@ -1673,7 +1680,7 @@ if default_prompt_suggestions == []:
DEFAULT_PROMPT_SUGGESTIONS = default_prompt_suggestions
try:
model_order_list = json.loads(os.getenv('MODEL_ORDER_LIST', '[]'))
model_order_list = JSONCodec.loads(os.getenv('MODEL_ORDER_LIST', '[]'))
except Exception as e:
log.exception(f'Error loading MODEL_ORDER_LIST: {e}')
model_order_list = []
@ -1681,7 +1688,7 @@ except Exception as e:
MODEL_ORDER_LIST = model_order_list
try:
default_model_metadata = json.loads(os.getenv('DEFAULT_MODEL_METADATA', '{}'))
default_model_metadata = JSONCodec.loads(os.getenv('DEFAULT_MODEL_METADATA', '{}'))
except Exception as e:
log.exception(f'Error loading DEFAULT_MODEL_METADATA: {e}')
default_model_metadata = {}
@ -1689,13 +1696,22 @@ except Exception as e:
DEFAULT_MODEL_METADATA = default_model_metadata
try:
default_model_params = json.loads(os.getenv('DEFAULT_MODEL_PARAMS', '{}'))
default_model_params = JSONCodec.loads(os.getenv('DEFAULT_MODEL_PARAMS', '{}'))
except Exception as e:
log.exception(f'Error loading DEFAULT_MODEL_PARAMS: {e}')
default_model_params = {}
DEFAULT_MODEL_PARAMS = default_model_params
try:
default_interface_settings = JSONCodec.loads(os.getenv('DEFAULT_INTERFACE_SETTINGS', '{}'))
except Exception as e:
log.exception(f'Error loading DEFAULT_INTERFACE_SETTINGS: {e}')
default_interface_settings = {}
DEFAULT_INTERFACE_SETTINGS = default_interface_settings if isinstance(default_interface_settings, dict) else {}
DEFAULT_USER_ROLE = os.getenv('DEFAULT_USER_ROLE', 'pending')
DEFAULT_GROUP_ID = os.getenv('DEFAULT_GROUP_ID', '')
@ -2032,7 +2048,7 @@ ENABLE_USER_STATUS = os.getenv('ENABLE_USER_STATUS', 'True').lower() == 'true'
ENABLE_EVALUATION_ARENA_MODELS = os.getenv('ENABLE_EVALUATION_ARENA_MODELS', 'True').lower() == 'true'
try:
evaluation_arena_models = json.loads(os.getenv('EVALUATION_ARENA_MODELS', '[]'))
evaluation_arena_models = JSONCodec.loads(os.getenv('EVALUATION_ARENA_MODELS', '[]'))
if not isinstance(evaluation_arena_models, list) or not all(
isinstance(model, dict) for model in evaluation_arena_models
):
@ -2047,6 +2063,9 @@ DEFAULT_ARENA_MODEL = {
'id': 'arena-model',
'name': 'Arena Model',
'meta': {
# LICENSE covers this Open WebUI fallback logo.
# Do not alter, remove, obscure, or replace it except as LICENSE permits:
# https://docs.openwebui.com/license.
'profile_image_url': '/favicon.png',
'description': 'Submit your questions to anonymous AI chatbots and vote on the best response.',
'model_ids': None,
@ -2079,6 +2098,7 @@ ENABLE_USER_WEBHOOKS = os.getenv('ENABLE_USER_WEBHOOKS', 'False').lower() == 'tr
# FastAPI / AnyIO settings
THREAD_POOL_SIZE = os.getenv('THREAD_POOL_SIZE', None)
THREAD_POOL_THREAD_NAME_PREFIX = os.getenv('THREAD_POOL_THREAD_NAME_PREFIX', '')
if THREAD_POOL_SIZE is not None and isinstance(THREAD_POOL_SIZE, str):
try:
@ -2134,7 +2154,7 @@ class BannerModel(BaseModel):
try:
banners = json.loads(os.getenv('WEBUI_BANNERS', '[]'))
banners = JSONCodec.loads(os.getenv('WEBUI_BANNERS', '[]'))
banners = [BannerModel(**banner) for banner in banners]
except Exception as e:
log.exception(f'Error loading WEBUI_BANNERS: {e}')
@ -2157,10 +2177,20 @@ TASK_MODEL = os.getenv('TASK_MODEL', '')
TASK_MODEL_EXTERNAL = os.getenv('TASK_MODEL_EXTERNAL', '')
try:
task_model_params = JSONCodec.loads(os.getenv('TASK_MODEL_PARAMS', '{}'))
except Exception as e:
log.exception(f'Error loading TASK_MODEL_PARAMS: {e}')
task_model_params = {}
TASK_MODEL_PARAMS = task_model_params
CONTEXT_COMPACTION_MODEL = os.getenv('CONTEXT_COMPACTION_MODEL', '')
ENABLE_CONTEXT_COMPACTION = os.getenv('ENABLE_CONTEXT_COMPACTION', 'False').lower() == 'true'
ENABLE_TOOL_PERMISSIONS = os.getenv('ENABLE_TOOL_PERMISSIONS', 'False').lower() == 'true'
CONTEXT_COMPACTION_TOKEN_THRESHOLD = int(os.getenv('CONTEXT_COMPACTION_TOKEN_THRESHOLD', '80000'))
_CONTEXT_COMPACTION_TOKEN_CAP = os.getenv('CONTEXT_COMPACTION_TOKEN_CAP')
@ -2470,12 +2500,12 @@ GOOGLE_OAUTH_AUTHORIZE_PARAMS = {}
_google_oauth_authorize_params = os.getenv('GOOGLE_OAUTH_AUTHORIZE_PARAMS', '')
if _google_oauth_authorize_params:
try:
_parsed = json.loads(_google_oauth_authorize_params)
_parsed = JSONCodec.loads(_google_oauth_authorize_params)
if isinstance(_parsed, dict):
GOOGLE_OAUTH_AUTHORIZE_PARAMS = _parsed
else:
log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring')
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring')
MICROSOFT_CLIENT_ID = os.getenv('MICROSOFT_CLIENT_ID', '')
@ -2590,12 +2620,12 @@ OAUTH_AUTHORIZE_PARAMS = {}
_oauth_authorize_params = os.getenv('OAUTH_AUTHORIZE_PARAMS', '')
if _oauth_authorize_params:
try:
_parsed = json.loads(_oauth_authorize_params)
_parsed = JSONCodec.loads(_oauth_authorize_params)
if isinstance(_parsed, dict):
OAUTH_AUTHORIZE_PARAMS = _parsed
else:
log.warning('OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring')
except (json.JSONDecodeError, TypeError):
except (JSONCodec.JSONDecodeError, TypeError):
log.warning('OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring')
@ -3046,6 +3076,7 @@ DEFAULT_CONFIG = {
'ui.default_locale': DEFAULT_LOCALE,
'ui.default_models': DEFAULT_MODELS,
'ui.default_pinned_models': DEFAULT_PINNED_MODELS,
'ui.default_interface_settings': DEFAULT_INTERFACE_SETTINGS,
'ui.prompt_suggestions': DEFAULT_PROMPT_SUGGESTIONS,
'ui.model_order_list': MODEL_ORDER_LIST,
'models.default_metadata': DEFAULT_MODEL_METADATA,
@ -3085,12 +3116,14 @@ DEFAULT_CONFIG = {
'auth.admin.email': ADMIN_EMAIL,
'task.model.default': TASK_MODEL,
'task.model.external': TASK_MODEL_EXTERNAL,
'task.model.params': TASK_MODEL_PARAMS,
'chat.context_compaction.model': CONTEXT_COMPACTION_MODEL,
'chat.context_compaction.enable': ENABLE_CONTEXT_COMPACTION,
'chat.context_compaction.token_threshold': CONTEXT_COMPACTION_TOKEN_THRESHOLD,
'chat.context_compaction.token_cap': CONTEXT_COMPACTION_TOKEN_CAP,
'chat.context_compaction.retention_percentage': CONTEXT_COMPACTION_RETENTION_PERCENTAGE,
'chat.context_compaction.prompt_template': CONTEXT_COMPACTION_PROMPT_TEMPLATE,
'chat.tool_permissions.enable': ENABLE_TOOL_PERMISSIONS,
'task.title.prompt_template': TITLE_GENERATION_PROMPT_TEMPLATE,
'task.tags.prompt_template': TAGS_GENERATION_PROMPT_TEMPLATE,
'task.image.prompt_template': IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE,