This commit is contained in:
Mrunmayee Daware 2026-10-21 02:40:35 +00:00 • committed by GitHub
commit 8fd034014f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 140 additions and 0 deletions

View file

@ -7319,6 +7319,18 @@ def get_standard_logging_object_payload(
# failure payloads stay None like every response type that carries its own _hidden_params
timing_metrics: Final = getattr(logging_obj, "response_timing_metrics", None) or {}
clean_hidden_params["litellm_overhead_time_ms"] = timing_metrics.get("litellm_overhead_time_ms")
if status == "success" and clean_hidden_params["litellm_model_name"] is None and "model" in kwargs:
# /v1/messages dict results and the bridge stream wrappers cannot carry
# _hidden_params; backfill the deployment model from the call details (the
# same value update_response_metadata writes on the object path) so
# deployment TPM/RPM accounting keys match the pre-call check (#45702)
backfill_model: Final[str | None] = (
cast( # cast-ok: kwargs model is the deployment model string for this call
str | None, kwargs["model"]
)
)
if isinstance(backfill_model, str) and backfill_model:
clean_hidden_params["litellm_model_name"] = backfill_model
model_cost_information: Final = StandardLoggingPayloadSetup.get_model_cost_information(
base_model=base_model,

View file

@ -0,0 +1,128 @@
"""
Regression tests for BerriAI/litellm#45702: a native /v1/messages result is a
plain dict, so it carries no _hidden_params and the standard logging payload left
hidden_params["litellm_model_name"] as None. Deployment-level TPM/RPM accounting
(model_rate_limit_check, lowest_tpm_rpm_v2) keys its post-call increment on
<model_id>:<litellm_model_name>, so every such call was silently skipped and
enforce_model_rate_limits / usage-based-routing-v2 never saw the usage.
"""
import datetime
from typing import Final
from unittest.mock import AsyncMock, MagicMock
import pytest
import litellm
from litellm.litellm_core_utils.litellm_logging import (
Logging,
get_standard_logging_object_payload,
)
from litellm.router_utils.pre_call_checks.model_rate_limit_check import (
ModelRateLimitingCheck,
)
DEPLOYMENT_MODEL: Final = "anthropic/claude-opus-4-8"
DEPLOYMENT_ID: Final = "dep-anth"
def _call_kwargs() -> dict:
# model_call_details keeps the full deployment model (provider prefix included);
# Logging.model is the provider-stripped name, which must NOT be used here
return {
"model": DEPLOYMENT_MODEL,
"litellm_params": {
"model": DEPLOYMENT_MODEL,
"metadata": {"model_group": "m-group", "model_info": {"id": DEPLOYMENT_ID}},
},
"call_type": "anthropic_messages",
"litellm_call_id": "call-45702",
"stream": False,
}
def _anthropic_result() -> dict:
# the native /v1/messages response reaches the payload builder as a plain dict
return {
"id": "msg_45702",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-8",
"content": [{"type": "text", "text": "hi"}],
"usage": {"input_tokens": 1, "output_tokens": 1},
}
def _logging_obj() -> Logging:
return Logging(
model=DEPLOYMENT_MODEL,
messages=[{"role": "user", "content": "hi"}],
stream=False,
call_type="anthropic_messages",
start_time=datetime.datetime.now(),
litellm_call_id="call-45702",
function_id="fid-45702",
)
def _payload(status: str = "success") -> dict:
now: Final = datetime.datetime.now()
return get_standard_logging_object_payload(
kwargs=_call_kwargs(),
init_response_obj=_anthropic_result(),
start_time=now,
end_time=now,
logging_obj=_logging_obj(),
status=status,
)
def test_messages_plain_dict_result_backfills_litellm_model_name() -> None:
payload: Final = _payload()
assert payload["hidden_params"]["litellm_model_name"] == DEPLOYMENT_MODEL
assert payload["model_id"] == DEPLOYMENT_ID
def test_messages_plain_dict_failure_payload_stays_unmarked() -> None:
# failure payloads carry no model name on the object path either; keep it that way
payload: Final = _payload(status="failure")
assert payload["hidden_params"]["litellm_model_name"] is None
def test_object_result_hidden_params_win_over_backfill() -> None:
response: Final = litellm.ModelResponse()
response._hidden_params["litellm_model_name"] = "anthropic/claude-opus-4-8"
kwargs: Final = _call_kwargs()
kwargs["model"] = "m-group"
now: Final = datetime.datetime.now()
payload: Final = get_standard_logging_object_payload(
kwargs=kwargs,
init_response_obj=response,
start_time=now,
end_time=now,
logging_obj=_logging_obj(),
status="success",
)
assert payload["hidden_params"]["litellm_model_name"] == "anthropic/claude-opus-4-8"
@pytest.mark.asyncio
async def test_backfilled_name_keys_the_deployment_tpm_increment() -> None:
# the pre-call check keys on <model_id>:<litellm_params.model>; the post-call
# increment must land on the same key - a provider-stripped name would miss it
mock_cache: Final = MagicMock()
mock_cache.async_increment_cache = AsyncMock()
check: Final = ModelRateLimitingCheck(dual_cache=mock_cache)
await check.async_log_success_event({"standard_logging_object": _payload()}, None, None, None)
mock_cache.async_increment_cache.assert_called_once()
_, kwarg_params = mock_cache.async_increment_cache.call_args
deployment: Final = {
"litellm_params": {"model": DEPLOYMENT_MODEL},
"model_info": {"id": DEPLOYMENT_ID},
}
tpm_key, _ = check._get_cache_keys( # pyright: ignore[reportPrivateUsage] # same seam the pre-call check uses
deployment, litellm.utils.get_utc_datetime().strftime("%H-%M")
)
assert kwarg_params["key"] == tpm_key