fix: prefer active provider for default model overlap
This commit is contained in:
1 parent
e4a9c5b7f5
commit
ed183784d4
2 files changed
+77
-7
No files matched your search
+25
-7
@@ -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.
|
||||
|
||||
@@ -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 ──────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user