fix(providers): keep custom provider when model id has known-provider prefix (#4210) (#4220)

* fix: keep custom provider when model id has known-provider prefix (#4210)

Siblings of #3872 (v0.51.349, custom provider with a configured base_url
preserves intrinsic routing prefixes). The base_url unset variant of
the same bug was missed: with provider=custom or custom:<slug> and no
base_url set, a model id like 'x-ai/grok-2' or 'google/gemma-2' was
hijacked to provider='openrouter', base_url=None. The backend then
failed to initialize with 'RuntimeError: No LLM provider configured'
because the user had no OpenRouter credentials configured.

resolve_model_provider() now applies the same _is_custom exemption the
config_base_url branch already carries to the cross-provider redirect
at the end of the slash branch. The custom provider keeps the request
and the full model id; real first-party providers pointed at an
OpenAI-compatible proxy (e.g. provider=openai + base_url=litellm) are
unaffected because their strip path is in the base_url branch and the
'prefix == config_provider' branch above the change.

Verified:
- test_model_resolver.py 32 passed (30 existing + 2 new RED tests)
- test_custom_provider_bare_model_reasoning.py 34 passed
- Pre-existing F401 (api/config.py:23,24,28) are not introduced by
  this change; same 3 errors exist on upstream/master.

Refs #4210

* chore(release): reposition #4210 CHANGELOG entry under v0.51.426 (OM)

---------

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
This commit is contained in:
authored and GitHub committed 2026-06-14 19:56:12 -07:00
1 parent d0fb2aa22b
commit e7e856cd79
3 files changed
+54 -1

No files matched your search

+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.426] — 2026-06-15 — Release OM (custom-provider model-prefix routing fix, #4210)
### Fixed
- **Custom providers with no `base_url` no longer get hijacked to OpenRouter when the model id has a known-provider prefix.** A bare `custom` or named `custom:<slug>` provider pointed at a vendor-routing proxy (e.g. `custom:llm-proxy` with no configured `base_url`, since the proxy URL is supplied at request time) used to receive an OpenRouter redirect for model ids like `x-ai/grok-2` or `google/gemma-2` whenever the prefix was a member of `_PROVIDER_MODELS`. The backend then failed to initialize with `RuntimeError: No LLM provider configured` because the user had no OpenRouter credentials configured. `resolve_model_provider()` now applies the same custom-provider exemption the `config_base_url` branch already carries (sibling of #3872 / v0.51.349), so the request stays on the user's selected custom provider with the full model id preserved. (#4210)
## [v0.51.425] — 2026-06-15 — Release OL (data-integrity batch: empty-pending guard #4205 + cron-reply fix #3975)
### Fixed
+9 -1
View File
@@ -2187,7 +2187,15 @@ def resolve_model_provider(model_id: str) -> tuple:
# If prefix does NOT match config provider, the user picked a cross-provider model
# from the OpenRouter dropdown (e.g. config=anthropic but picked openai/gpt-5.4-mini).
# In this case always route through openrouter with the full provider/model string.
if prefix in _PROVIDER_MODELS and prefix != config_provider:
# Exception (#4210): a custom provider (bare ``custom`` or named ``custom:<slug>``)
# is a vendor-routing proxy, not a first-party provider — its model ids commonly
# contain a known-provider prefix that the proxy uses for upstream routing, not
# an OpenRouter dropdown selection. Keep the request on the custom provider; the
# base_url-set sibling of this exception lives earlier in the ``config_base_url``
# branch (#3872).
_cp_lower_cross = (config_provider or "").strip().lower()
_is_custom_cross = _cp_lower_cross == "custom" or _cp_lower_cross.startswith("custom:")
if prefix in _PROVIDER_MODELS and prefix != config_provider and not _is_custom_cross:
return model_id, "openrouter", None
return model_id, config_provider, config_base_url
+39
View File
@@ -709,3 +709,42 @@ def test_custom_endpoint_slash_model_routes_to_custom_not_openrouter():
assert model_or == 'google/gemma-4-26b-a4b', (
"Model name should be preserved for openrouter, got '{}'.".format(model_or)
)
# ── #4210: custom provider (no base_url) must not be hijacked to openrouter
# when the model id has a known-provider prefix (sibling of #3872, which
# only covered the base_url-set variant). Bug-report case 1.
def test_custom_provider_no_base_url_with_known_prefix_keeps_custom_and_full_id_4210():
"""#4210: provider=custom:llm-proxy (no base_url) + 'x-ai/grok-2' must NOT
be redirected to openrouter. The prefix is intrinsic to the custom proxy's
routing; the user did not pick anything from the OpenRouter dropdown."""
model, provider, base_url = _resolve_with_config(
'x-ai/grok-2',
provider='custom:llm-proxy',
default='x-ai/grok-2',
)
assert provider == 'custom:llm-proxy', (
"Custom provider must not be hijacked to openrouter when no base_url is "
"set; got provider={!r} model={!r}".format(provider, model)
)
assert model == 'x-ai/grok-2', (
"Custom provider must preserve the full model id; got model={!r}".format(model)
)
assert base_url is None
def test_bare_custom_provider_no_base_url_with_known_prefix_keeps_custom_and_full_id_4210():
"""#4210 sibling: bare 'custom' (no 'custom:<slug>') with no base_url
must also not be hijacked to openrouter for a known-prefix model id."""
model, provider, base_url = _resolve_with_config(
'google/gemma-2-9b',
provider='custom',
default='google/gemma-2-9b',
)
assert provider == 'custom', (
"Bare 'custom' provider must not be hijacked to openrouter when no "
"base_url is set; got provider={!r}".format(provider)
)
assert model == 'google/gemma-2-9b'
assert base_url is None