fix: resolve base_model in /cost/estimate for Azure custom deployments (#22724)

The _resolve_model_for_cost_lookup function was only checking
litellm_params.model when resolving model names from the router.
For Azure custom deployment names (e.g. azure/openai/gpt-5.3-codex),
this deployment name doesn't exist in the model cost map, so cost
returned /bin/zsh.

Now checks model_info.base_model and litellm_params.base_model first,
falling back to litellm_params.model only if no base_model is set.
This matches how the router resolves base_model everywhere else.
This commit is contained in:
milan-berri
2026-03-03 15:43:02 -08:00
committed by GitHub
parent 661f1e16cf
commit 98b9bc8b72
2 changed files with 133 additions and 2 deletions
@@ -54,16 +54,27 @@ def _resolve_model_for_cost_lookup(model: str) -> Tuple[str, Optional[str]]:
deployments = llm_router.get_model_list(model_name=model)
if deployments and len(deployments) > 0:
# Get the first deployment's litellm model
first_deployment = deployments[0]
litellm_params = first_deployment.get("litellm_params", {})
model_info = first_deployment.get("model_info", {})
# Check base_model first (needed for Azure custom deployment names)
base_model = model_info.get("base_model") or litellm_params.get(
"base_model"
)
if base_model:
verbose_proxy_logger.debug(
f"Resolved model '{model}' to base_model '{base_model}' from router"
)
custom_llm_provider = litellm_params.get("custom_llm_provider")
return base_model, custom_llm_provider
resolved_model = litellm_params.get("model")
if resolved_model:
verbose_proxy_logger.debug(
f"Resolved model '{model}' to '{resolved_model}' from router"
)
# Extract custom_llm_provider if present
custom_llm_provider = litellm_params.get("custom_llm_provider")
return resolved_model, custom_llm_provider
except Exception as e:
@@ -270,3 +270,123 @@ class TestCostTrackingSettings:
assert "error" in response_data["detail"]
assert "STORE_MODEL_IN_DB" in response_data["detail"]["error"]
class TestResolveModelForCostLookup:
"""Tests for _resolve_model_for_cost_lookup base_model resolution."""
def test_resolves_base_model_for_azure_deployment(self):
"""
When a model group has base_model set in model_info,
_resolve_model_for_cost_lookup should return the base_model
instead of the raw litellm_params.model (Azure deployment name).
"""
from litellm.proxy.management_endpoints.cost_tracking_settings import (
_resolve_model_for_cost_lookup,
)
mock_router = MagicMock()
mock_router.get_model_list.return_value = [
{
"model_name": "gpt-5.3-codex",
"litellm_params": {
"model": "azure/openai/gpt-5.3-codex",
"api_base": "https://fake.openai.azure.com/",
"api_key": "fake-key",
},
"model_info": {
"id": "test-id",
"base_model": "azure/gpt-4o",
},
}
]
with patch(
"litellm.proxy.proxy_server.llm_router",
mock_router,
):
resolved_model, provider = _resolve_model_for_cost_lookup("gpt-5.3-codex")
assert resolved_model == "azure/gpt-4o"
mock_router.get_model_list.assert_called_once_with(model_name="gpt-5.3-codex")
def test_falls_back_to_litellm_params_model_when_no_base_model(self):
"""
When no base_model is set, should fall back to litellm_params.model.
"""
from litellm.proxy.management_endpoints.cost_tracking_settings import (
_resolve_model_for_cost_lookup,
)
mock_router = MagicMock()
mock_router.get_model_list.return_value = [
{
"model_name": "gpt-4",
"litellm_params": {
"model": "openai/gpt-4",
},
"model_info": {
"id": "test-id",
},
}
]
with patch(
"litellm.proxy.proxy_server.llm_router",
mock_router,
):
resolved_model, provider = _resolve_model_for_cost_lookup("gpt-4")
assert resolved_model == "openai/gpt-4"
def test_resolves_base_model_from_litellm_params(self):
"""
When base_model is in litellm_params (not model_info),
it should still be resolved.
"""
from litellm.proxy.management_endpoints.cost_tracking_settings import (
_resolve_model_for_cost_lookup,
)
mock_router = MagicMock()
mock_router.get_model_list.return_value = [
{
"model_name": "my-azure-model",
"litellm_params": {
"model": "azure/my-custom-deployment",
"base_model": "azure/gpt-4o-mini",
},
"model_info": {
"id": "test-id",
},
}
]
with patch(
"litellm.proxy.proxy_server.llm_router",
mock_router,
):
resolved_model, provider = _resolve_model_for_cost_lookup(
"my-azure-model"
)
assert resolved_model == "azure/gpt-4o-mini"
def test_returns_original_model_when_no_router(self):
"""
When no router is available, should return the original model name.
"""
from litellm.proxy.management_endpoints.cost_tracking_settings import (
_resolve_model_for_cost_lookup,
)
with patch(
"litellm.proxy.proxy_server.llm_router",
None,
):
resolved_model, provider = _resolve_model_for_cost_lookup(
"azure/openai/gpt-5.3-codex"
)
assert resolved_model == "azure/openai/gpt-5.3-codex"
assert provider is None