fix: prefer active provider for default model overlap

This commit is contained in:
Michael Lam committed 2026-05-10 10:49:12 -07:00
1 parent e4a9c5b7f5
commit ed183784d4
2 files changed
+77 -7

No files matched your search

+25 -7
View File
@@ -1517,18 +1517,36 @@ def resolve_model_provider(model_id: str) -> tuple:
# Custom providers declared in config.yaml should win over slash-based
# OpenRouter heuristics. Their model IDs commonly contain '/' too.
custom_providers = cfg.get("custom_providers", [])
if isinstance(custom_providers, list):
# However, when the active provider is an explicit non-custom provider and
# the requested model_id is the configured default model, that active
# provider takes precedence over overlapping custom_providers[] entries.
# Otherwise WebUI routes to custom:<name> instead of the intended endpoint
# and can surface a 401 from the wrong provider (#1922).
# For all other cases, preserve custom_providers[] routing for explicitly
# selected custom provider models.
_is_explicit_non_custom_provider = (
config_provider is not None
and config_provider != 'custom'
and not config_provider.startswith('custom:')
)
_default_model = model_cfg.get('default') if isinstance(model_cfg, dict) else None
_skip_custom_providers = (
_is_explicit_non_custom_provider
and _default_model is not None
and model_id == _default_model
)
custom_providers = cfg.get('custom_providers', [])
if isinstance(custom_providers, list) and not _skip_custom_providers:
for entry in custom_providers:
if not isinstance(entry, dict):
continue
entry_model = (entry.get("model") or "").strip()
entry_name = (entry.get("name") or "").strip()
entry_base_url = (entry.get("base_url") or "").strip()
entry_model = (entry.get('model') or '').strip()
entry_name = (entry.get('name') or '').strip()
entry_base_url = (entry.get('base_url') or '').strip()
entry_model_ids = set()
if entry_model:
entry_model_ids.add(entry_model)
entry_models = entry.get("models")
entry_models = entry.get('models')
if isinstance(entry_models, dict):
entry_model_ids.update(
key.strip()
@@ -1536,7 +1554,7 @@ def resolve_model_provider(model_id: str) -> tuple:
if isinstance(key, str) and key.strip()
)
if entry_name and model_id in entry_model_ids:
provider_hint = "custom:" + entry_name.lower().replace(" ", "-")
provider_hint = 'custom:' + entry_name.lower().replace(' ', '-')
return model_id, provider_hint, entry_base_url or None
# @provider:model format — explicit provider hint from the dropdown.
+52
View File
@@ -179,6 +179,58 @@ def test_custom_provider_models_dict_routes_to_named_custom_provider():
assert base_url == 'http://127.0.0.1:8080/v1'
# ── Issue #1922: default model shadowed by overlapping custom_providers[] ──
def test_default_model_not_shadowed_by_overlapping_custom_provider():
r'''Regression test for #1922.
When the active provider is an explicit non-custom provider (e.g. ai-gateway,
openrouter, xiaomi) AND the requested model_id matches the configured default
model, the active provider's base_url must take precedence over an overlapping
custom_providers[] entry. Otherwise the WebUI routes to 'custom:<name>' with
the wrong endpoint, causing 401 errors.
This test mirrors the reported scenario:
- provider: ai-gateway
- base_url: https://api.ai-gateway.example/v1
- default: gpt-5.4
- An overlapping custom_providers[] entry with the same default model
Expected: active provider (ai-gateway) wins over custom provider.
'''
model, provider, base_url = _resolve_with_config(
'gpt-5.4',
provider='ai-gateway',
base_url='https://api.ai-gateway.example/v1',
default='gpt-5.4',
custom_providers=[{
'name': 'My Custom Endpoint',
'base_url': 'http://localhost:8080/v1',
'model': 'gpt-5.4',
}],
)
assert model == 'gpt-5.4', f'Expected model=gpt-5.4, got {model!r}'
assert provider == 'ai-gateway', f'Expected provider=ai-gateway, got {provider!r}'
assert base_url == 'https://api.ai-gateway.example/v1', f'Expected base_url from active provider, got {base_url!r}'
def test_default_model_shadowed_with_xiaomi_provider():
r'''Same regression test with provider=xiaomi instead of ai-gateway.'''
model, provider, base_url = _resolve_with_config(
'deepseek-v4-flash',
provider='xiaomi',
default='deepseek-v4-flash',
custom_providers=[{
'name': 'LiteLLM Proxy',
'base_url': 'http://127.0.0.1:8080/v1',
'model': 'deepseek-v4-flash',
}],
)
assert model == 'deepseek-v4-flash'
assert provider == 'xiaomi'
assert base_url is None # xiaomi has no config base_url in this test
# ── get_available_models() @provider: hint behaviour ──────────────────────