Move the `_PORTAL_PROVIDERS` guard in `resolve_model_provider()` to run BEFORE the `prefix == config_provider` strip branch. The guard was added for NVIDIA (along with the Nous portal cases in #854 / #894) but was placed after the strip, so it never fired when `config_provider == "nvidia"` and the model id started with `nvidia/`. For `model_id="nvidia/nemotron-3-super-120b-a12b"`, `config_provider="nvidia"`: - prefix = "nvidia", bare = "nemotron-3-super-120b-a12b" - prefix == config_provider → True → strip branch returned bare name - `_PORTAL_PROVIDERS` guard never reached - bare "nemotron-3-super-120b-a12b" sent to NVIDIA NIM → HTTP 404 NIM requires the full namespaced path. The fix moves the portal guard to run first, so all portal providers (Nous, OpenCode-Zen, OpenCode-Go, NVIDIA NIM) always preserve the full `provider/model` id regardless of whether the prefix happens to equal the provider name. This also closes a latent symmetric bug for the Nous case if a `nous/<model>` id ever existed in the catalog. Test plan: - New `tests/test_issue2177_nvidia_prefix_preservation.py` covers: - nvidia/nemotron-... under nvidia (the reported case) - cross-namespace qwen/ and meta/ under nvidia (regression pin) - every static nvidia model in `_PROVIDER_MODELS` resolves to itself - latent nous/<model> under nous (structural ordering pin) - non-portal providers (anthropic) still strip — fix doesn't over-correct - Existing portal-routing suites (test_nous_portal_routing.py, test_issue895_894_nous_prefix.py) continue to pass. - Full test suite: 5320 passed, 4 skipped, 3 xpassed. Reported on Discord by @vishnu (Nathan forwarded as #2177).
119 lines
5.3 KiB
Python
119 lines
5.3 KiB
Python
"""Regression tests for #2177 — resolve_model_provider() strips nvidia/ prefix on NVIDIA NIM.
|
|
|
|
The bug: when ``config_provider == "nvidia"`` and the model id is
|
|
``nvidia/nemotron-3-super-120b-a12b``, the resolver hit the
|
|
``prefix == config_provider`` strip BEFORE the ``_PORTAL_PROVIDERS`` guard and
|
|
returned the bare name ``nemotron-3-super-120b-a12b``. NVIDIA NIM's inference
|
|
endpoint then 404'd because it requires the full namespaced model id.
|
|
|
|
Same bug class as #854 / #894 for the Nous portal. The fix moves the
|
|
``_PORTAL_PROVIDERS`` guard to run BEFORE the prefix-strip so portal providers
|
|
always preserve the full ``provider/model`` path regardless of whether the
|
|
prefix happens to equal the config_provider name.
|
|
"""
|
|
import api.config as config
|
|
|
|
|
|
def _resolve(model_id: str, provider: str):
|
|
"""Resolve a model under a synthesized config_provider, isolated from disk state."""
|
|
old = dict(config.cfg)
|
|
old_mtime = config._cfg_mtime
|
|
config.cfg.clear()
|
|
config.cfg["model"] = {"provider": provider}
|
|
try:
|
|
config._cfg_mtime = config.Path(config._get_config_path()).stat().st_mtime
|
|
except Exception:
|
|
config._cfg_mtime = 0.0
|
|
try:
|
|
return config.resolve_model_provider(model_id)
|
|
finally:
|
|
config.cfg.clear()
|
|
config.cfg.update(old)
|
|
config._cfg_mtime = old_mtime
|
|
|
|
|
|
class TestNvidiaPortalPrefixPreservation:
|
|
"""NVIDIA NIM requires the full ``namespace/model`` id — prefix must never be stripped."""
|
|
|
|
def test_nvidia_self_namespace_prefix_preserved(self):
|
|
"""``nvidia/nemotron-...`` under nvidia provider must keep the nvidia/ prefix.
|
|
|
|
This is the exact case in #2177 — prefix == config_provider would otherwise
|
|
trigger the strip branch and emit a bare ``nemotron-...`` id that NIM 404s.
|
|
"""
|
|
model, provider, _ = _resolve("nvidia/nemotron-3-super-120b-a12b", "nvidia")
|
|
assert model == "nvidia/nemotron-3-super-120b-a12b", (
|
|
f"prefix was stripped: {model!r} — NVIDIA NIM requires the full namespaced id"
|
|
)
|
|
assert provider == "nvidia"
|
|
|
|
def test_nvidia_cross_namespace_qwen_prefix_preserved(self):
|
|
"""``qwen/...`` under nvidia provider keeps the qwen/ prefix (regression pin).
|
|
|
|
Cross-namespace already worked because ``prefix != config_provider`` — pinning
|
|
the test so a later refactor doesn't regress it.
|
|
"""
|
|
model, provider, _ = _resolve("qwen/qwen2.5-coder-32b-instruct", "nvidia")
|
|
assert model == "qwen/qwen2.5-coder-32b-instruct"
|
|
assert provider == "nvidia"
|
|
|
|
def test_nvidia_cross_namespace_meta_prefix_preserved(self):
|
|
"""``meta/llama-...`` under nvidia provider keeps the meta/ prefix."""
|
|
model, provider, _ = _resolve("meta/llama-3.1-70b-instruct", "nvidia")
|
|
assert model == "meta/llama-3.1-70b-instruct"
|
|
assert provider == "nvidia"
|
|
|
|
def test_nvidia_static_models_all_resolve_with_prefix_intact(self):
|
|
"""Every static nvidia model in ``_PROVIDER_MODELS`` must resolve to itself.
|
|
|
|
Pins the contract that the static dropdown list and the resolver agree on
|
|
the wire format — same invariant ``test_nous_portal_routing.py`` enforces
|
|
for the Nous portal.
|
|
"""
|
|
nvidia_models = config._PROVIDER_MODELS.get("nvidia", [])
|
|
assert nvidia_models, "nvidia must have at least one static model"
|
|
for entry in nvidia_models:
|
|
mid = entry["id"]
|
|
resolved, provider, _ = _resolve(mid, "nvidia")
|
|
assert resolved == mid, (
|
|
f"static model {mid!r} resolved to {resolved!r} — "
|
|
f"portal must preserve full namespaced id"
|
|
)
|
|
assert provider == "nvidia"
|
|
|
|
|
|
class TestPortalGuardOrdering:
|
|
"""The ``_PORTAL_PROVIDERS`` guard must run BEFORE the prefix-strip branch.
|
|
|
|
Otherwise, any portal user whose model id starts with the literal provider
|
|
name (nvidia/nvidia-..., nous/nous-..., etc.) hits the strip branch and
|
|
breaks at the upstream API. This is structural — pinning the ordering so a
|
|
future refactor cannot quietly reintroduce the bug.
|
|
"""
|
|
|
|
def test_hypothetical_nous_self_prefix_preserved(self):
|
|
"""``nous/<model>`` under nous provider must keep the prefix.
|
|
|
|
Latent bug shape — there's no shipped ``nous/<model>`` id today, but if
|
|
Nous ever serves a self-prefixed model the resolver must handle it the
|
|
same way it handles nvidia/.
|
|
"""
|
|
model, provider, _ = _resolve("nous/hermes-3-something", "nous")
|
|
assert model == "nous/hermes-3-something", (
|
|
f"prefix stripped: {model!r} — portal guard must preserve full id"
|
|
)
|
|
assert provider == "nous"
|
|
|
|
def test_anthropic_self_prefix_still_strips_for_anthropic(self):
|
|
"""Non-portal providers (anthropic) keep the existing strip behavior.
|
|
|
|
``anthropic/claude-...`` under the anthropic provider should still
|
|
resolve to bare ``claude-...`` — anthropic's API doesn't want the
|
|
``anthropic/`` prefix. Pinning so the fix doesn't over-correct.
|
|
"""
|
|
model, provider, _ = _resolve("anthropic/claude-opus-4.6", "anthropic")
|
|
assert model == "claude-opus-4.6", (
|
|
f"anthropic strip-prefix path broken: got {model!r}, expected 'claude-opus-4.6'"
|
|
)
|
|
assert provider == "anthropic"
|