fix(models): preserve case-sensitive catalog lookups

This commit is contained in:
Muzzy5150 2026-10-29 23:45:30 -07:00
parent 6b7ae2ebe4
commit 3f79c1098d
3 changed files with 166 additions and 8 deletions

View file

@ -1016,22 +1016,30 @@ def model_supports_images(model_name: str) -> bool:
def _bare_openai_name(model_name: str) -> str:
name = model_name.strip().lower()
name = model_name.strip()
normalized = name.lower()
for prefix in ("litellm/", "any-llm/", "openai/"):
if name.startswith(prefix):
if normalized.startswith(prefix):
return name[len(prefix) :]
return name
def _catalog_entry(model_name: str) -> dict[str, Any] | None:
"""LiteLLM's cost-map entry for the model, looked up as it would route it."""
"""LiteLLM's case-sensitive cost-map entry for the model and its aliases."""
import litellm
name = _bare_openai_name(model_name)
entry = litellm.model_cost.get(name)
if entry is None and "/" in name:
entry = litellm.model_cost.get(name.rsplit("/", 1)[1])
return entry if isinstance(entry, dict) else None
supplied_name = model_name.strip()
routed_name = _bare_openai_name(supplied_name)
candidates = [supplied_name, routed_name, routed_name.lower()]
if "/" in routed_name:
bare_name = routed_name.rsplit("/", 1)[1]
candidates.extend((bare_name, bare_name.lower()))
for candidate in dict.fromkeys(candidates):
entry = litellm.model_cost.get(candidate)
if entry is not None:
return entry if isinstance(entry, dict) else None
return None
def is_known_openai_bare_model(model_name: str) -> bool:

View file

@ -156,6 +156,22 @@ def test_max_reasoning_effort_sent_as_raw_body_field() -> None:
assert settings.extra_body == {"reasoning_effort": "max"}
def test_mixed_case_reasoning_capability_reaches_model_settings(
monkeypatch: pytest.MonkeyPatch,
) -> None:
model = "deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731"
monkeypatch.setattr(
litellm,
"model_cost",
{model: {"supports_reasoning": True}},
)
settings = make_model_settings("high", model_name=model)
assert settings.reasoning is not None
assert settings.reasoning.effort == "high"
def test_conversation_tail_breakpoint_moves_with_appended_transcript() -> None:
# LiteLLM must place the index=-1 cache_control on the last message however
# long the transcript grows.

View file

@ -17,6 +17,7 @@ from strix.config.models import (
_TurnGuardModel,
configure_sdk_model_defaults,
model_supports_images,
model_supports_reasoning,
request_timeout_extra_args,
resolve_api_type,
routes_through_litellm,
@ -211,3 +212,136 @@ def test_image_support_follows_the_catalog(monkeypatch: pytest.MonkeyPatch) -> N
assert model_supports_images("acme-text") is False
assert model_supports_images("litellm/openai/acme-vision") is True
assert model_supports_images("acme-unknown-model") is True
_MIXED_CASE_MODEL = "deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731"
def test_mixed_case_model_capabilities_follow_the_pinned_litellm_catalog() -> None:
"""Integration check against the real catalog pinned by uv.lock."""
entry = litellm.model_cost[_MIXED_CASE_MODEL]
assert entry["supports_reasoning"] is True
assert entry["supports_vision"] is False
assert model_supports_reasoning(_MIXED_CASE_MODEL) is True
assert model_supports_images(_MIXED_CASE_MODEL) is False
@pytest.mark.parametrize("route_prefix", ["litellm/", "any-llm/", "openai/"])
def test_catalog_lookup_strips_supported_route_prefix_without_losing_case(
monkeypatch: pytest.MonkeyPatch, route_prefix: str
) -> None:
monkeypatch.setattr(
litellm,
"model_cost",
{"vendor/MixedCase": {"supports_reasoning": True, "supports_vision": False}},
)
model = f"{route_prefix}vendor/MixedCase"
assert model_supports_reasoning(model) is True
assert model_supports_images(model) is False
def test_catalog_lookup_prefers_exact_key_over_normalized_and_bare_fallbacks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
model = "openai/vendor/MixedCase"
monkeypatch.setattr(
litellm,
"model_cost",
{
model: {
"supports_reasoning": False,
"supports_vision": False,
"supported_endpoints": ["/v1/chat/completions"],
},
model.lower(): {
"supports_reasoning": True,
"supports_vision": True,
"supported_endpoints": ["/v1/responses"],
},
"MixedCase": {
"supports_reasoning": True,
"supports_vision": True,
"supported_endpoints": ["/v1/responses"],
},
},
)
settings = _settings(monkeypatch, model, None)
assert model_supports_reasoning(model) is False
assert model_supports_images(model) is False
assert resolve_api_type(model, settings) == "chat_completions"
assert uses_chat_completions_tool_schema(model, settings) is True
def test_catalog_lookup_prefers_case_preserving_stripped_key_over_normalized_fallback(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
litellm,
"model_cost",
{
"vendor/MixedCase": {"supports_reasoning": False, "supports_vision": False},
"vendor/mixedcase": {"supports_reasoning": True, "supports_vision": True},
},
)
model = "litellm/vendor/MixedCase"
assert model_supports_reasoning(model) is False
assert model_supports_images(model) is False
@pytest.mark.parametrize(
("model", "catalog_key"),
[
("vendor/lowercase", "vendor/lowercase"),
("vendor/MixedCase", "vendor/mixedcase"),
("vendor/MixedCase", "MixedCase"),
],
ids=["lowercase-exact", "normalized-fallback", "bare-alias"],
)
def test_catalog_lookup_compatibility_fallbacks(
monkeypatch: pytest.MonkeyPatch, model: str, catalog_key: str
) -> None:
monkeypatch.setattr(
litellm,
"model_cost",
{catalog_key: {"supports_reasoning": True, "supports_vision": False}},
)
assert model_supports_reasoning(model) is True
assert model_supports_images(model) is False
def test_unknown_model_capability_defaults_are_unchanged(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(litellm, "model_cost", {})
settings = _settings(monkeypatch, "openai/UnknownMixedCase", None)
assert model_supports_reasoning("openai/UnknownMixedCase") is False
assert model_supports_images("openai/UnknownMixedCase") is True
assert resolve_api_type("openai/UnknownMixedCase", settings) == "chat_completions"
@pytest.mark.parametrize(
("model", "supported_endpoints", "expected"),
[
("openai/AcmeResponses", ["/v1/responses"], "responses"),
("openai/AcmeChat", ["/v1/chat/completions"], "chat_completions"),
],
)
def test_api_type_follows_exact_mixed_case_catalog_capability(
monkeypatch: pytest.MonkeyPatch,
model: str,
supported_endpoints: list[str],
expected: str,
) -> None:
monkeypatch.setattr(
litellm,
"model_cost",
{model: {"supported_endpoints": supported_endpoints}},
)
settings = _settings(monkeypatch, model, None)
assert resolve_api_type(model, settings) == expected
assert uses_chat_completions_tool_schema(model, settings) is (expected == "chat_completions")