Merge pull request #18208 from Chesars/fix/case-insensitive-model-cost-lookup

fix: case-insensitive model cost map lookup
This commit is contained in:
Sameer Kankute 2026-01-13 11:53:43 +05:30 committed by GitHub
commit ecb3959c3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 144 additions and 35 deletions

View File

@ -4993,6 +4993,25 @@ def _strip_model_name(model: str, custom_llm_provider: Optional[str]) -> str:
return model
def _get_model_cost_key(potential_key: str) -> Optional[str]:
"""
Get the actual key from model_cost, with case-insensitive fallback.
Returns the key if found (exact match preferred, then case-insensitive), or None if not found.
"""
# Try exact match first (most common case, O(1))
if potential_key in litellm.model_cost:
return potential_key
# Fallback to case-insensitive match
potential_key_lower = potential_key.lower()
for key in litellm.model_cost:
if key.lower() == potential_key_lower:
return key
return None
def _get_model_info_from_model_cost(key: str) -> dict:
return litellm.model_cost[key]
@ -5142,10 +5161,10 @@ def _is_potential_model_name_in_model_cost(
potential_model_names: PotentialModelNamesAndCustomLLMProvider,
) -> bool:
"""
Check if the potential model name is in the model cost.
Check if the potential model name is in the model cost (case-insensitive).
"""
return any(
potential_model_name in litellm.model_cost
_get_model_cost_key(str(potential_model_name)) is not None
for potential_model_name in potential_model_names.values()
)
@ -5223,44 +5242,51 @@ def _get_model_info_helper( # noqa: PLR0915
_model_info: Optional[Dict[str, Any]] = None
key: Optional[str] = None
if combined_model_name in litellm.model_cost:
key = combined_model_name
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None and model in litellm.model_cost:
key = model
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if (
_model_info is None
and combined_stripped_model_name in litellm.model_cost
):
key = combined_stripped_model_name
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None and stripped_model_name in litellm.model_cost:
key = stripped_model_name
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None and split_model in litellm.model_cost:
key = split_model
# Use case-insensitive lookup for all model name checks
_matched_key = _get_model_cost_key(combined_model_name)
if _matched_key is not None:
key = _matched_key
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None:
_matched_key = _get_model_cost_key(model)
if _matched_key is not None:
key = _matched_key
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None:
_matched_key = _get_model_cost_key(combined_stripped_model_name)
if _matched_key is not None:
key = _matched_key
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None:
_matched_key = _get_model_cost_key(stripped_model_name)
if _matched_key is not None:
key = _matched_key
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None:
_matched_key = _get_model_cost_key(split_model)
if _matched_key is not None:
key = _matched_key
_model_info = _get_model_info_from_model_cost(key=cast(str, key))
if not _check_provider_match(
model_info=_model_info, custom_llm_provider=custom_llm_provider
):
_model_info = None
if _model_info is None or key is None:
raise ValueError(

View File

@ -372,3 +372,86 @@ def test_get_model_info_cost_calculator_bedrock_region_cris_stripped(model, prov
print("info", info)
assert info["key"] == "us.anthropic.claude-3-haiku-20240307-v1:0"
assert info["litellm_provider"] == "bedrock"
def test_get_model_info_case_insensitive_lookup(monkeypatch):
"""
Test that model info lookup is case-insensitive.
This ensures that users can use lowercase model names even when the model cost
map has mixed-case keys (e.g., "Qwen/Qwen3-Next-80B-A3B-Thinking").
Related Slack discussion: Users were getting "does not support parameters: ['tools']"
errors when using lowercase model names like "qwen/qwen3-next-80b-a3b-thinking"
because the lookup was case-sensitive.
"""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
# Register a test model with mixed-case name
litellm.register_model(
{
"together_ai/Qwen/Qwen3-Next-80B-A3B-Thinking": {
"input_cost_per_token": 0.0001,
"output_cost_per_token": 0.0002,
"litellm_provider": "together_ai",
"supports_function_calling": True,
}
}
)
# Test 1: Exact case should work
info = litellm.get_model_info(
model="Qwen/Qwen3-Next-80B-A3B-Thinking", custom_llm_provider="together_ai"
)
assert info is not None
assert info["supports_function_calling"] is True
# Test 2: Lowercase should also work (case-insensitive lookup)
info_lower = litellm.get_model_info(
model="qwen/qwen3-next-80b-a3b-thinking", custom_llm_provider="together_ai"
)
assert info_lower is not None
assert info_lower["supports_function_calling"] is True
# Test 3: Mixed case should also work
info_mixed = litellm.get_model_info(
model="QWEN/qwen3-NEXT-80b-a3b-thinking", custom_llm_provider="together_ai"
)
assert info_mixed is not None
assert info_mixed["supports_function_calling"] is True
def test_get_model_info_case_insensitive_supports_function_calling(monkeypatch):
"""
Test that supports_function_calling check works with case-insensitive model lookup.
"""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
# Register a model with mixed-case name that supports function calling
litellm.register_model(
{
"test_provider/TestModel-ABC": {
"input_cost_per_token": 0.0001,
"output_cost_per_token": 0.0002,
"litellm_provider": "test_provider",
"supports_function_calling": True,
}
}
)
# Test that supports_function_calling works with lowercase model name
from litellm.utils import supports_function_calling
# Exact case
assert (
supports_function_calling("TestModel-ABC", custom_llm_provider="test_provider")
is True
)
# Lowercase (should now work with case-insensitive lookup)
assert (
supports_function_calling("testmodel-abc", custom_llm_provider="test_provider")
is True
)