Merge remote-tracking branch 'origin/main' into litellm_team_member_temp_budget_increase

This commit is contained in:
yassin 2026-09-17 19:59:44 +00:00
commit 42429500be
5 changed files with 68 additions and 9 deletions

View file

@ -1937,6 +1937,14 @@ class ProxyBaseLLMRequestProcessing:
) -> tuple[dict, LiteLLMLoggingObj]:
start_time: Final = datetime.now() # start before calling guardrail hooks
requested_model: Final = self.data.get("model")
if requested_model is not None and not isinstance(requested_model, str):
raise ProxyException(
message="'model' must be a string.",
type=ProxyErrorTypes.bad_request_error,
param="model",
code=status.HTTP_400_BAD_REQUEST,
)
self.data = await add_litellm_data_to_request(
data=self.data,
request=request,

View file

@ -485,10 +485,13 @@ def get_logging_payload(
or None
)
custom_llm_provider: Final = logged_provider or _model_group_provider(_model_group, llm_router)
raw_model: Final = cast(str, kwargs.get("model") or "")
resolved_model: Final = (
standard_logging_payload.get("model") if standard_logging_payload is not None else None
) or reconstruct_model_name(raw_model, logged_provider, metadata or {})
requested_model: Final = cast(object, kwargs.get("model"))
raw_model: Final = requested_model if isinstance(requested_model, str) else ""
model_is_malformed: Final = requested_model is not None and not isinstance(requested_model, str)
logged_model: Final = standard_logging_payload.get("model") if standard_logging_payload is not None else None
resolved_model: Final = (logged_model if isinstance(logged_model, str) else None) or reconstruct_model_name(
raw_model, logged_provider, metadata or {}
)
failed_with_prompt_shaped_model: Final = (
_get_status_for_spend_log(metadata=metadata) == "failure"
and not _model_group
@ -496,7 +499,7 @@ def get_logging_payload(
)
model_name: Final = (
UNKNOWN_MODEL_SPEND_LOG_MODEL
if rejected_as_unknown_model or failed_with_prompt_shaped_model
if rejected_as_unknown_model or failed_with_prompt_shaped_model or model_is_malformed
else resolved_model
)
litellm_call_id: Final = cast(

View file

@ -1049,6 +1049,27 @@ def test_get_logging_payload_replaces_rejected_or_prompt_shaped_models_with_the_
assert payload["model"] == expected_model
@pytest.mark.parametrize("requested_model", [{"bad": "value"}, ["gpt-5.2"], 1])
def test_get_logging_payload_replaces_a_non_string_model_with_the_placeholder(
requested_model: dict[str, str] | list[str] | int,
):
kwargs: Final = {
"model": requested_model,
"messages": [{"role": "user", "content": "hi"}],
"call_type": "acompletion",
"litellm_params": {"metadata": {"user_api_key": "sk-test", "status": "failure"}},
}
payload: Final = get_logging_payload(
kwargs=kwargs,
response_obj=ValueError("model must be a string"),
start_time=datetime.datetime.now(timezone.utc),
end_time=datetime.datetime.now(timezone.utc),
)
assert payload["model"] == UNKNOWN_MODEL_SPEND_LOG_MODEL
@pytest.mark.parametrize(
("metadata", "response_obj"),
[

View file

@ -327,6 +327,35 @@ class TestProxyBaseLLMRequestProcessing:
pytest.fail("litellm_call_id is not a valid UUID")
assert data_passed["litellm_call_id"] == returned_data["litellm_call_id"]
@pytest.mark.asyncio
@pytest.mark.parametrize("requested_model", [{"bad": "value"}, ["gpt-5.2"], 1])
async def test_common_processing_pre_call_logic_rejects_a_non_string_model_with_400(
self, monkeypatch, requested_model: dict[str, str] | list[str] | int
):
processing_obj = ProxyBaseLLMRequestProcessing(
data={"model": requested_model, "messages": [{"role": "user", "content": "hi"}]}
)
mock_request = MagicMock(spec=Request)
mock_request.headers = {}
add_litellm_data_to_request = AsyncMock()
monkeypatch.setattr(
litellm.proxy.common_request_processing, "add_litellm_data_to_request", add_litellm_data_to_request
)
with pytest.raises(ProxyException) as exc_info:
await processing_obj.common_processing_pre_call_logic(
request=mock_request,
general_settings={},
user_api_key_dict=MagicMock(spec=UserAPIKeyAuth),
proxy_logging_obj=MagicMock(spec=ProxyLogging),
proxy_config=MagicMock(spec=ProxyConfig),
route_type="acompletion",
)
assert exc_info.value.code == str(status.HTTP_400_BAD_REQUEST)
assert exc_info.value.param == "model"
add_litellm_data_to_request.assert_not_awaited()
@pytest.mark.asyncio
async def test_common_processing_pre_call_logic_refreshes_proxy_server_request_body_after_guardrails(
self, monkeypatch

View file

@ -95,7 +95,7 @@ def _successor(info: dict[str, object]) -> str | None:
return successor if isinstance(successor, str) else None
def test_together_successor_metadata_points_at_live_models(cost_map: CostMap):
def test_together_successor_metadata_points_at_known_models(cost_map: CostMap):
successors = {
model: successor
for model, info in cost_map.items()
@ -103,9 +103,7 @@ def test_together_successor_metadata_points_at_live_models(cost_map: CostMap):
}
assert len(successors) >= 10
for model, successor in successors.items():
target = cost_map.get(successor)
assert target is not None, f"{model} names successor {successor} that is not in the map"
assert "deprecation_date" not in target, f"{model} names deprecated successor {successor}"
assert successor in cost_map, f"{model} names successor {successor} that is not in the map"
def test_together_backup_cost_map_in_sync(cost_map: CostMap):