* 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:
3 files changed
+54
-1
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user