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 <noreply@anthropic.com>
This commit is contained in:
knowhycodata 2026-08-10 08:19:07 +03:00
parent 2dadc5435a
commit c1b227cf81

View file

@ -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: