agharsallah commited on
Commit
66f0e23
·
1 Parent(s): eed2172

feat: update HF model catalogue to prioritize chat-capable model and adjust tests for new routing logic

Browse files
src/models/hf_catalogue.py CHANGED
@@ -69,21 +69,19 @@ class HFModel:
69
  # router shifts over time; because this is plain data, retuning is a one-line edit.
70
 
71
  HF_MODELS: tuple[HFModel, ...] = (
72
- # tiny ≤4B (Tiny-Titan band)
73
- HFModel("meta-llama/Llama-3.2-3B-Instruct", profile="tiny", params_b=3, source="Meta Llama"),
74
- HFModel("Qwen/Qwen2.5-3B-Instruct", params_b=3, source="Qwen"),
75
- HFModel("microsoft/Phi-3.5-mini-instruct", params_b=3.8, source="Microsoft Phi"),
76
- HFModel("HuggingFaceTB/SmolLM2-1.7B-Instruct", params_b=1.7, source="SmolLM"),
77
- # fast ≤8B
78
- HFModel("Qwen/Qwen2.5-7B-Instruct", profile="fast", params_b=7, source="Qwen"),
79
- HFModel("mistralai/Mistral-7B-Instruct-v0.3", params_b=7, source="Mistral"),
80
- HFModel("meta-llama/Llama-3.1-8B-Instruct", params_b=8, source="Meta Llama"),
81
- # balanced ≤13B
82
- HFModel("google/gemma-2-9b-it", profile="balanced", params_b=9, source="Google Gemma"),
83
- # strong ≤32B
84
- HFModel("Qwen/Qwen2.5-32B-Instruct", profile="strong", params_b=32, source="Qwen"),
85
- HFModel("mistralai/Mistral-Small-24B-Instruct-2501", params_b=24, source="Mistral"),
86
- HFModel("Qwen/Qwen2.5-14B-Instruct", params_b=14, source="Qwen"),
87
  )
88
 
89
 
 
69
  # router shifts over time; because this is plain data, retuning is a one-line edit.
70
 
71
  HF_MODELS: tuple[HFModel, ...] = (
72
+ # Only chat-capable model currently live on the enabled HF providers (free
73
+ # `hf-inference`), verified by a real /v1/chat/completions call. Pinned to its
74
+ # provider so the router does not depend on paid-provider auto-routing. It is
75
+ # tagged `tiny` (1.5B, ≤4B band) but serves every tier: a tier with no dedicated
76
+ # HF model falls back to the first catalogue entry (see lab._default_model_key),
77
+ # so the whole cast routes here while only `hf-inference` is enabled.
78
+ HFModel("katanemo/Arch-Router-1.5B", profile="tiny", params_b=1.5, source="Katanemo", hf_provider="hf-inference"),
79
+ # NOTE: to use larger small models (e.g. openai/gpt-oss-20b — 20B, ≤32B, OpenAI
80
+ # track) enable a provider that serves them (together / nscale / fireworks /
81
+ # novita / groq) at https://huggingface.co/settings/inference-providers, then add
82
+ # the model here. `HuggingFaceBio/Carbon-3B` is intentionally NOT listed: the HF
83
+ # router rejects it as "not a chat model" (it is text-generation only), so it
84
+ # cannot drive the chat-completions path the engine uses.
 
 
85
  )
86
 
87
 
tests/test_hf_catalogue.py CHANGED
@@ -31,15 +31,18 @@ def test_every_model_is_within_its_tier_param_cap():
31
  assert params <= _TIER_CAP[tier], f"{e['key']} ({params}B) exceeds {tier} cap"
32
 
33
 
34
- def test_each_logical_tier_has_a_default():
35
- for tier in ("tiny", "fast", "balanced", "strong"):
36
- assert hf_catalogue.default_key_for_profile(tier) is not None, f"no default for {tier}"
 
 
37
 
38
 
39
  def test_binding_uses_router_url_and_token():
40
- key = hf_catalogue.default_key_for_profile("fast")
41
  binding = hf_catalogue.binding_for(key, env={"HF_TOKEN": "hf_xyz"})
42
- assert binding["model"] == f"openai/{key}"
 
43
  assert binding["base_url"] == hf_catalogue.DEFAULT_BASE_URL
44
  assert binding["api_key"] == "hf_xyz"
45
 
 
31
  assert params <= _TIER_CAP[tier], f"{e['key']} ({params}B) exceeds {tier} cap"
32
 
33
 
34
+ def test_catalogue_has_a_tiny_default():
35
+ # The catalogue is currently scoped to the one chat-capable model live on the
36
+ # enabled providers (tagged tiny). Tiers without a dedicated model fall back to
37
+ # it at the UI layer (see lab._default_model_key), so they may return None here.
38
+ assert hf_catalogue.default_key_for_profile("tiny") == "katanemo/Arch-Router-1.5B"
39
 
40
 
41
  def test_binding_uses_router_url_and_token():
42
+ key = hf_catalogue.default_key_for_profile("tiny")
43
  binding = hf_catalogue.binding_for(key, env={"HF_TOKEN": "hf_xyz"})
44
+ # The model pins its provider (hf-inference) so routing needs no paid auto-select.
45
+ assert binding["model"] == f"openai/{key}:hf-inference"
46
  assert binding["base_url"] == hf_catalogue.DEFAULT_BASE_URL
47
  assert binding["api_key"] == "hf_xyz"
48
 
tests/test_inference_backends.py CHANGED
@@ -64,7 +64,9 @@ def test_binding_dispatches_to_the_right_backend():
64
 
65
 
66
  def test_default_key_for_profile_is_backend_scoped():
67
- hf_default = inference.default_key_for_profile("strong", "hf")
 
 
68
  assert hf_default is not None and hf_default.startswith("hf:")
69
  modal_default = inference.default_key_for_profile("strong", "modal")
70
  assert modal_default is not None and not modal_default.startswith("hf:")
 
64
 
65
 
66
  def test_default_key_for_profile_is_backend_scoped():
67
+ # HF currently tags only the tiny tier (its single live chat model); Modal tags
68
+ # every tier. The point here is that keys are namespaced per backend.
69
+ hf_default = inference.default_key_for_profile("tiny", "hf")
70
  assert hf_default is not None and hf_default.startswith("hf:")
71
  modal_default = inference.default_key_for_profile("strong", "modal")
72
  assert modal_default is not None and not modal_default.startswith("hf:")
tests/test_router.py CHANGED
@@ -111,17 +111,18 @@ class TestModelRouterCatalogueEndpoint:
111
 
112
  def test_online_hf_endpoint_key_resolves_to_hf_router(self, monkeypatch):
113
  # A backend-qualified HF key resolves to the HF Inference router binding —
114
- # the OpenAI-compatible model string + HF token, no Modal env needed.
 
115
  monkeypatch.setenv("HF_TOKEN", "hf_secret")
116
  monkeypatch.delenv("HF_INFERENCE_BASE_URL", raising=False)
117
- monkeypatch.delenv("MODEL_FAST", raising=False)
118
  router = ModelRouter(offline=False)
119
- provider = router.for_profile("hf:Qwen/Qwen2.5-7B-Instruct")
120
  assert isinstance(provider, LiteLLMProvider)
121
- assert provider.model == "openai/Qwen/Qwen2.5-7B-Instruct"
122
  assert provider.api_base == "https://router.huggingface.co/v1"
123
  assert provider.api_key == "hf_secret"
124
- assert provider.max_tokens == 320 # fast tier decoding (the model's tier)
125
 
126
  def test_offline_hf_endpoint_key_serves_distinct_stub(self):
127
  # Offline, an HF key routes like any profile: the deterministic stub with the
 
111
 
112
  def test_online_hf_endpoint_key_resolves_to_hf_router(self, monkeypatch):
113
  # A backend-qualified HF key resolves to the HF Inference router binding —
114
+ # the OpenAI-compatible model string + HF token, no Modal env needed. The
115
+ # model pins its provider (hf-inference) so routing needs no paid auto-select.
116
  monkeypatch.setenv("HF_TOKEN", "hf_secret")
117
  monkeypatch.delenv("HF_INFERENCE_BASE_URL", raising=False)
118
+ monkeypatch.delenv("MODEL_TINY", raising=False)
119
  router = ModelRouter(offline=False)
120
+ provider = router.for_profile("hf:katanemo/Arch-Router-1.5B")
121
  assert isinstance(provider, LiteLLMProvider)
122
+ assert provider.model == "openai/katanemo/Arch-Router-1.5B:hf-inference"
123
  assert provider.api_base == "https://router.huggingface.co/v1"
124
  assert provider.api_key == "hf_secret"
125
+ assert provider.max_tokens == 192 # tiny tier decoding (the model's tier)
126
 
127
  def test_offline_hf_endpoint_key_serves_distinct_stub(self):
128
  # Offline, an HF key routes like any profile: the deterministic stub with the