mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 09:28:34 +00:00
fix(providers): require api_base before local provider wins on keyword match
Ollama's spec keeps "nemotron" as a keyword so bare `nemotron-3-nano` auto-routes to a configured Ollama install (PR #1863). NVIDIA NIM was later registered with the same "nemotron" keyword (commit 046d0831), creating the only keyword collision in the registry. In `_match_provider`, the keyword loop accepted any local provider on `spec.is_local` alone — no api_base check. Models like `nvidia/nemotron-3-super-120b-a12b` (intended for OpenRouter or NVIDIA NIM) were therefore hijacked to http://localhost:11434/v1 even when the user had never configured Ollama, causing silent connection errors at runtime. Add the same api_base gate the local-fallback loop already uses: a local provider only wins by keyword when the user has actually set its api_base. Preserves PR #1863's intent for users who configured Ollama; fixes the silent hijack for everyone else. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4c387f6633
commit
5eb818e800
@ -533,6 +533,15 @@ class Config(BaseSettings):
|
|||||||
continue
|
continue
|
||||||
p = getattr(self.providers, spec.name, None)
|
p = getattr(self.providers, spec.name, None)
|
||||||
if p and any(_kw_matches(kw) for kw in spec.keywords):
|
if p and any(_kw_matches(kw) for kw in spec.keywords):
|
||||||
|
# Local providers (Ollama, vLLM, …) keep model-family keywords
|
||||||
|
# like "nemotron" or "llama" to enable bare-model auto-routing,
|
||||||
|
# but those keywords collide with cloud-hosted variants of the
|
||||||
|
# same family (e.g. `nvidia/nemotron-...` via OpenRouter). Only
|
||||||
|
# honor a local keyword match when the user has actually
|
||||||
|
# configured that local endpoint via `api_base` — mirrors the
|
||||||
|
# gate already used by the local-fallback loop below.
|
||||||
|
if spec.is_local and not p.api_base:
|
||||||
|
continue
|
||||||
if spec.is_oauth or spec.is_local or spec.is_direct or p.api_key:
|
if spec.is_oauth or spec.is_local or spec.is_direct or p.api_key:
|
||||||
return p, spec.name
|
return p, spec.name
|
||||||
|
|
||||||
|
|||||||
@ -1160,6 +1160,42 @@ def test_config_falls_back_to_vllm_when_ollama_not_configured():
|
|||||||
assert config.get_api_base() == "http://localhost:8000"
|
assert config.get_api_base() == "http://localhost:8000"
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_cloud_nemotron_is_not_hijacked_by_unconfigured_ollama():
|
||||||
|
"""`nvidia/nemotron-*` via a gateway must not route to Ollama when no
|
||||||
|
Ollama endpoint is configured. Ollama keeps "nemotron" in its keywords
|
||||||
|
for bare-model auto-routing (PR #1863), which previously hijacked
|
||||||
|
cloud-hosted nemotron variants and silently sent traffic to
|
||||||
|
http://localhost:11434/v1."""
|
||||||
|
config = Config.model_validate(
|
||||||
|
{
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"provider": "auto",
|
||||||
|
"model": "nvidia/nemotron-3-super-120b-a12b",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"providers": {"openrouter": {"apiKey": "sk-or-test"}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.get_provider_name() == "openrouter"
|
||||||
|
assert config.get_api_base() == "https://openrouter.ai/api/v1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_bare_nemotron_still_auto_routes_to_configured_ollama():
|
||||||
|
"""Preserves PR #1863 intent: when the user has actually configured an
|
||||||
|
Ollama endpoint, a bare nemotron model still auto-routes there."""
|
||||||
|
config = Config.model_validate(
|
||||||
|
{
|
||||||
|
"agents": {"defaults": {"provider": "auto", "model": "nemotron-3-nano"}},
|
||||||
|
"providers": {"ollama": {"apiBase": "http://localhost:11434/v1"}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.get_provider_name() == "ollama"
|
||||||
|
assert config.get_api_base() == "http://localhost:11434/v1"
|
||||||
|
|
||||||
|
|
||||||
def test_openai_compat_provider_passes_model_through():
|
def test_openai_compat_provider_passes_model_through():
|
||||||
from nanobot.providers.openai_compat_provider import OpenAICompatProvider
|
from nanobot.providers.openai_compat_provider import OpenAICompatProvider
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user