fix(router): don't log 'Could not identify azure model' when the deployment name resolves from the cost map

get_router_model_info already falls back to resolving the azure
deployment's model name against the model cost map when base_model is
unset — and for deployments named after real azure models (e.g.
azure/gpt-4o) that resolution returns correct max tokens and costs. The
unconditional ERROR was therefore spurious for exactly the deployments
that need no operator action, and on busy proxies it logs thousands of
times per day per multi-deployment group.

Log at debug when the fallback entry carries usable limits/costs
(membership alone is not enough: Router init auto-registers every
deployment name as a zeroed stub), keep the ERROR otherwise.

Fixes #33172
This commit is contained in:
Mihidum Hettiyahandi 2026-07-15 08:37:25 +10:00 • committed by Devin AI
parent 229159c790
commit 60a4dccd85
2 changed files with 111 additions and 2 deletions

View file

@ -8524,9 +8524,29 @@ class Router:
## SET MODEL TO 'model=' - if base_model is None + not azure
if custom_llm_provider == "azure" and base_model is None:
verbose_router_logger.error(
f"Could not identify azure model '{_model}'. Set azure 'base_model' for accurate max tokens, cost tracking, etc.- https://docs.litellm.ai/docs/proxy/cost_tracking#spend-tracking-for-azure-openai-models"
# the `if model is None` fallback below resolves the deployment's
# model name against the model cost map — when the name is a known
# azure key (e.g. deployment model "azure/gpt-4o"), that resolution
# gives correct max tokens / costs and there is nothing for the
# operator to fix, so don't spam an ERROR on every request.
# membership alone isn't enough: Router init auto-registers every
# deployment name into litellm.model_cost as a zeroed stub, so
# require the entry to carry usable limits/costs.
_azure_fallback_key = _model if _model.startswith("azure/") else "azure/{}".format(_model)
_fallback_entry = litellm.model_cost.get(_azure_fallback_key) or {}
_fallback_resolves = (
_fallback_entry.get("max_input_tokens") is not None
or _fallback_entry.get("max_tokens") is not None
or (_fallback_entry.get("input_cost_per_token") or 0) > 0
)
if _fallback_resolves:
verbose_router_logger.debug(
f"Azure deployment '{_model}' has no base_model set; using '{_azure_fallback_key}' from the model cost map for max tokens, cost tracking, etc."
)
else:
verbose_router_logger.error(
f"Could not identify azure model '{_model}'. Set azure 'base_model' for accurate max tokens, cost tracking, etc.- https://docs.litellm.ai/docs/proxy/cost_tracking#spend-tracking-for-azure-openai-models"
)
elif custom_llm_provider != "azure":
model = _model

View file

@ -5304,3 +5304,92 @@ class TestRouterRequestTimeoutPropagation:
)
== 60
)
class TestAzureBaseModelFallbackLogging:
"""When an azure deployment has no base_model but its model name is a known
azure key in the cost map, get_router_model_info resolves it via the
fallback — so it must not log the per-request 'Could not identify azure
model' ERROR. The ERROR must remain for genuinely unmappable deployment
names. Issue #33172."""
@pytest.fixture(autouse=True)
def _use_local_model_cost_map(self, monkeypatch):
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
original_model_cost = litellm.model_cost
litellm.model_cost = litellm.get_model_cost_map(url="")
yield
litellm.model_cost = original_model_cost
def _router_with_azure_deployment(self, deployment_model: str):
return litellm.Router(
model_list=[
{
"model_name": "my-group",
"litellm_params": {
"model": deployment_model,
"api_key": "fake-key",
"api_base": "https://fake.openai.azure.com",
},
"model_info": {"id": "azure-base-model-test-id"},
}
]
)
def test_map_known_deployment_name_resolves_without_error_log(self):
router = self._router_with_azure_deployment("azure/gpt-4o")
with patch(
"litellm.router.verbose_router_logger.error"
) as mock_error:
model_info = router.get_router_model_info(
deployment=None, received_model_name="my-group", id="azure-base-model-test-id"
)
assert not any(
"Could not identify azure model" in str(call)
for call in mock_error.call_args_list
), f"unexpected error log: {mock_error.call_args_list}"
# the fallback resolution must actually surface the map values
assert model_info["max_input_tokens"] == litellm.model_cost["azure/gpt-4o"]["max_input_tokens"]
assert model_info["input_cost_per_token"] == litellm.model_cost["azure/gpt-4o"]["input_cost_per_token"]
def test_unmappable_deployment_name_still_logs_error(self):
router = self._router_with_azure_deployment("azure/my-custom-deployment-name")
with patch(
"litellm.router.verbose_router_logger.error"
) as mock_error:
model_info = router.get_router_model_info(
deployment=None, received_model_name="my-group", id="azure-base-model-test-id"
)
assert any(
"Could not identify azure model" in str(call)
for call in mock_error.call_args_list
), "expected the error log for an unmappable azure deployment name"
# unmappable names resolve to a zeroed stub — unchanged behavior
assert model_info.get("max_input_tokens") is None
def test_explicit_base_model_still_wins(self):
router = litellm.Router(
model_list=[
{
"model_name": "my-group",
"litellm_params": {
"model": "azure/some-deployment",
"api_key": "fake-key",
"api_base": "https://fake.openai.azure.com",
},
"model_info": {
"id": "azure-base-model-test-id",
"base_model": "azure/gpt-4o-mini",
},
}
]
)
model_info = router.get_router_model_info(
deployment=None, received_model_name="my-group", id="azure-base-model-test-id"
)
assert model_info["max_input_tokens"] == litellm.model_cost["azure/gpt-4o-mini"]["max_input_tokens"]