From c1b227cf81f009c33ffc4aec0f1a73134f6c4d5f Mon Sep 17 00:00:00 2001 From: knowhycodata Date: Mon, 10 Aug 2026 08:19:07 +0300 Subject: [PATCH] fix: exclude non-chat models from OpenAI-compatible gateway catalogs Gateways that front many providers (LiteLLM, OpenRouter-style aggregators, and others) return their entire catalog from `/models`, including embedding, image, video, rerank and audio models. Open WebUI listed all of them as chat models, so the model picker filled up with entries that fail as soon as they are selected. These gateways already mark each entry with the OpenAI-style `supported_operations` / `supported_endpoints` hints. Honour them when building the model list, and pick the required operation based on the connection's `api_type` so Responses-API connections list Responses models. Providers that do not advertise these fields are treated as chat capable, so plain OpenAI, Ollama and vLLM connections are unaffected. Connections with a manually configured `model_ids` list are left untouched. Co-Authored-By: Claude Opus 5 --- backend/open_webui/routers/openai.py | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index e5c9f2e7e8..502179fea7 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -86,6 +86,34 @@ def _clean_proxy_headers(raw_headers) -> dict: return {k: v for k, v in raw_headers.items() if k not in _STRIP_PROXY_HEADERS} +def supports_text_generation(model: dict, api_type: str | None = None) -> bool: + """Whether *model* can serve the text generation endpoint this connection uses. + + Gateways that front many providers return their whole catalog from + ``/models`` - embedding, image, video, rerank and audio models included - + and mark each entry with the OpenAI-style ``supported_operations`` / + ``supported_endpoints`` hints. Without this check every one of those + non-chat models shows up in the chat model picker. Providers that + advertise nothing are assumed to be chat capable, which keeps plain + OpenAI / Ollama / vLLM behaviour unchanged. + """ + if not isinstance(model, dict): + return True + + operation = 'RESPONSES' if api_type == 'responses' else 'CHAT_COMPLETIONS' + + operations = model.get('supported_operations') + if isinstance(operations, list) and operations: + return operation in operations + + endpoints = model.get('supported_endpoints') + if isinstance(endpoints, list) and endpoints: + endpoint = '/responses' if api_type == 'responses' else '/chat/completions' + return any(isinstance(e, str) and e.endswith(endpoint) for e in endpoints) + + return True + + async def send_get_request( request: Request = None, url=None, @@ -596,6 +624,20 @@ async def get_all_models_responses(request: Request, user: UserModel) -> list: # Catch non-list responses model_list = [] + if not api_config.get('model_ids'): + # Manually listed model ids are taken at face value; a fetched + # catalog gets narrowed down to the models this connection can + # actually chat with. + api_type = api_config.get('api_type') + filtered = [model for model in model_list if supports_text_generation(model, api_type)] + + if len(filtered) != len(model_list): + model_list = filtered + if isinstance(response, list): + responses[idx] = model_list + else: + response['data'] = model_list + for model in model_list: # Remove name key if its value is None #16689 if 'name' in model and model['name'] is None: @@ -773,6 +815,15 @@ async def get_models(request: Request, url_idx: int | None = None, user=Depends( for model in response_data.get('data', []) if not any(name in model['id'] for name in _UNSUPPORTED_OPENAI_MODEL_KEYWORDS) ] + elif isinstance(response_data, dict) and isinstance(response_data.get('data'), list): + # Keep the model id picker in sync with what this + # connection will actually serve as chat models. + api_type = api_config.get('api_type') + response_data['data'] = [ + model + for model in response_data['data'] + if isinstance(model, dict) and supports_text_generation(model, api_type) + ] models = response_data except aiohttp.ClientError as e: