From 99f25492897eab9dc1bb9971c54abe23098cd591 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Thu, 27 Aug 2026 23:28:57 +0200 Subject: [PATCH] fix: drop cached base models on every instance when a connection changes With ENABLE_BASE_MODELS_CACHE on and more than one instance behind the load balancer, editing an OpenAI or Ollama connection only refreshes the instance that served the admin request. Every other instance keeps handing out the model list it cached before the edit, so a newly added model appears for some users and not others, and a removed one lingers. With MODELS_CACHE_TTL left empty the per-provider cache never expires either, so those instances stay wrong until they are restarted. A provider config change now bumps an epoch stored in the config table, and each instance drops its cached base models when the epoch it last stamped no longer matches. The database is the only thing every deployment shares, and the key rides along in the config query get_all_models already makes, so this costs no extra round trip. The direct /openai/models and /ollama/api/tags endpoints still answer from their own provider cache without checking the epoch, a window of MODELS_CACHE_TTL and one second by default. The model list users actually see, /api/models, is covered. --- backend/open_webui/config.py | 1 + backend/open_webui/main.py | 1 + backend/open_webui/routers/ollama.py | 2 ++ backend/open_webui/routers/openai.py | 11 +++-------- backend/open_webui/utils/models.py | 10 ++++++++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index acd3d83bf8..de956aed9b 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2841,6 +2841,7 @@ DEFAULT_CONFIG = { 'openai.api_base_urls': OPENAI_API_BASE_URLS, 'openai.api_configs': OPENAI_API_CONFIGS, 'models.base_models_cache': ENABLE_BASE_MODELS_CACHE, + 'models.base_models_cache_epoch': '', 'tool_server.connections': TOOL_SERVER_CONNECTIONS, 'oauth.client.timeout': OAUTH_CLIENT_TIMEOUT, 'terminal_server.connections': TERMINAL_SERVER_CONNECTIONS, diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index c84a394b87..914c0620b8 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -587,6 +587,7 @@ app.state.SCIM_TOKEN = SCIM_TOKEN ######################################## app.state.BASE_MODELS = [] +app.state.BASE_MODELS_EPOCH = None ######################################## # diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 00f41c01b3..4f719c55e5 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -9,6 +9,7 @@ import time from datetime import datetime from typing import Optional, Union from urllib.parse import urlparse +from uuid import uuid4 import aiofiles import aiohttp @@ -315,6 +316,7 @@ async def update_config( 'ollama.enable': form_data.ENABLE_OLLAMA_API, 'ollama.base_urls': form_data.OLLAMA_BASE_URLS, 'ollama.api_configs': api_configs, + 'models.base_models_cache_epoch': str(uuid4()), } ) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 0f21defdd5..7ea9302d47 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -6,6 +6,7 @@ import logging import re from typing import Optional from urllib.parse import quote, urlparse +from uuid import uuid4 import aiofiles import aiohttp @@ -344,6 +345,7 @@ async def get_openai_connection(idx: int) -> tuple[str, str, dict]: async def clear_openai_model_cache(request: Request): + await Config.upsert({'models.base_models_cache_epoch': str(uuid4())}) await get_all_models.cache.clear() request.app.state.BASE_MODELS = [] request.app.state.OPENAI_MODELS = {} @@ -571,14 +573,7 @@ async def update_config(request: Request, form_data: OpenAIConfigForm, user=Depe } ) - await get_all_models.cache.clear() - request.app.state.BASE_MODELS = [] - request.app.state.OPENAI_MODELS = {} - models = getattr(request.app.state, 'MODELS', None) - if hasattr(models, 'clear'): - models.clear() - else: - request.app.state.MODELS = {} + await clear_openai_model_cache(request) await publish_event( request, diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 1c648b7f38..d55c5b52bc 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -67,22 +67,28 @@ async def get_all_base_models(request: Request, user: UserModel = None): async def get_all_models(request, refresh: bool = False, user: UserModel = None): config = await Config.get_many( 'models.base_models_cache', + 'models.base_models_cache_epoch', 'evaluation.arena.enable', 'evaluation.arena.models', 'models.default_metadata', ) - if refresh: + # A changed epoch means another instance edited a provider, so our cached models are stale. + cache_epoch = config.get('models.base_models_cache_epoch') + stale = request.app.state.BASE_MODELS_EPOCH != cache_epoch + + if refresh or stale: await openai.get_all_models.cache.clear() await ollama.get_all_models.cache.clear() if ( request.app.state.MODELS and request.app.state.BASE_MODELS - and (config.get('models.base_models_cache') and not refresh) + and (config.get('models.base_models_cache') and not (refresh or stale)) ): base_models = request.app.state.BASE_MODELS else: base_models = await get_all_base_models(request, user=user) + request.app.state.BASE_MODELS_EPOCH = cache_epoch if base_models: request.app.state.BASE_MODELS = base_models else: