fix(models): keep curated Anthropic aliases in /model picker (#43103)
The Anthropic picker returned the live /v1/models dump verbatim whenever credentials were configured. Anthropic's API lags newly-routed curated aliases (e.g. claude-fable-5, reachable on Anthropic before the models endpoint enumerates it), so the curated entry vanished from the picker. Merge curated _PROVIDER_MODELS["anthropic"] with the live catalog — curated first, live-only appended, deduped — mirroring the OpenAI curated-merge path. Live failure / no creds falls back to curated verbatim.
This commit is contained in:
parent
a5d05cf30e
commit
57c6714995
2 changed files with 72 additions and 1 deletions
|
|
@ -2220,7 +2220,20 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)
|
|||
if normalized == "anthropic":
|
||||
live = _fetch_anthropic_models()
|
||||
if live:
|
||||
return live
|
||||
# The live /v1/models dump lags newly-routed curated aliases
|
||||
# (e.g. claude-fable-5, which is reachable on Anthropic before it
|
||||
# is enumerated by the models endpoint). Surface curated entries
|
||||
# first, then append any live-only models, so a fresh curated
|
||||
# model never disappears just because the API hasn't listed it yet.
|
||||
curated = list(_PROVIDER_MODELS.get("anthropic", []))
|
||||
merged = list(curated)
|
||||
merged_lower = {m.lower() for m in curated}
|
||||
for m in live:
|
||||
if m.lower() not in merged_lower:
|
||||
merged.append(m)
|
||||
merged_lower.add(m.lower())
|
||||
return merged
|
||||
return list(_PROVIDER_MODELS.get("anthropic", []))
|
||||
if normalized == "ollama-cloud":
|
||||
live = fetch_ollama_cloud_models(force_refresh=force_refresh)
|
||||
if live:
|
||||
|
|
|
|||
58
tests/hermes_cli/test_anthropic_picker_curated.py
Normal file
58
tests/hermes_cli/test_anthropic_picker_curated.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""Regression tests for the Anthropic model-picker dropping curated aliases.
|
||||
|
||||
Bug — newly-routed curated aliases vanished on a native Anthropic setup
|
||||
``provider_model_ids("anthropic")`` returned the live ``/v1/models`` dump
|
||||
verbatim whenever Anthropic credentials were configured. Anthropic's API
|
||||
lags behind freshly-routed aliases (e.g. ``claude-fable-5``, which is
|
||||
reachable on Anthropic before the models endpoint enumerates it), so the
|
||||
curated entry disappeared from the picker. The picker now merges the
|
||||
curated ``_PROVIDER_MODELS["anthropic"]`` list with the live catalog —
|
||||
curated entries first, live-only models appended, deduped — mirroring the
|
||||
OpenAI curated-merge philosophy.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from hermes_cli import models as M
|
||||
|
||||
|
||||
def test_anthropic_curated_alias_survives_when_live_omits_it():
|
||||
"""A curated alias missing from /v1/models still surfaces (first)."""
|
||||
curated = M._PROVIDER_MODELS["anthropic"]
|
||||
assert "claude-fable-5" in curated # sanity: the alias is curated
|
||||
|
||||
# Live catalog the API would actually return — no fable-5.
|
||||
live = ["claude-opus-4-8", "claude-sonnet-4-6", "claude-haiku-4-5-20251001"]
|
||||
with patch.object(M, "_fetch_anthropic_models", return_value=live):
|
||||
result = M.provider_model_ids("anthropic")
|
||||
|
||||
assert "claude-fable-5" in result
|
||||
# Curated order is preserved at the front.
|
||||
assert result[:len(curated)] == list(curated)
|
||||
|
||||
|
||||
def test_anthropic_merge_dedupes_overlap_and_appends_live_only():
|
||||
"""Models in both lists appear once; live-only models are appended."""
|
||||
live = [
|
||||
"claude-opus-4-8", # overlaps curated
|
||||
"claude-sonnet-4-6", # overlaps curated
|
||||
"claude-future-9-99", # live-only, not curated
|
||||
]
|
||||
with patch.object(M, "_fetch_anthropic_models", return_value=live):
|
||||
result = M.provider_model_ids("anthropic")
|
||||
|
||||
# No duplicates introduced by the merge.
|
||||
assert result.count("claude-opus-4-8") == 1
|
||||
# Live-only entry is preserved (discovery still works for unknown models).
|
||||
assert "claude-future-9-99" in result
|
||||
# Curated entries lead, live-only trails.
|
||||
assert result.index("claude-fable-5") < result.index("claude-future-9-99")
|
||||
|
||||
|
||||
def test_anthropic_falls_back_to_curated_when_live_unavailable():
|
||||
"""No creds / live failure -> curated list verbatim (alias still present)."""
|
||||
with patch.object(M, "_fetch_anthropic_models", return_value=None):
|
||||
result = M.provider_model_ids("anthropic")
|
||||
|
||||
assert result == list(M._PROVIDER_MODELS["anthropic"])
|
||||
assert "claude-fable-5" in result
|
||||
Loading…
Add table
Add a link
Reference in a new issue