feat(logging): add user and team level spend and budget to StandardLoggingPayload metadata (#33459)

* feat(logging): add user and team level spend and budget to StandardLoggingPayload metadata

* fix(logging): include user and team budget fields in dummy standard logging payload
This commit is contained in:
yucheng-berri 2026-07-16 12:01:11 -07:00 committed by GitHub
parent db800152c0
commit 899ddef219
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 92 additions and 0 deletions

View file

@ -113,6 +113,10 @@ class PagerDutyAlerting(SlackAlerting):
user_api_key_spend=_meta.get("user_api_key_spend"),
user_api_key_max_budget=_meta.get("user_api_key_max_budget"),
user_api_key_budget_reset_at=_meta.get("user_api_key_budget_reset_at"),
user_api_key_user_spend=_meta.get("user_api_key_user_spend"),
user_api_key_user_max_budget=_meta.get("user_api_key_user_max_budget"),
user_api_key_team_spend=_meta.get("user_api_key_team_spend"),
user_api_key_team_max_budget=_meta.get("user_api_key_team_max_budget"),
user_api_key_org_id=_meta.get("user_api_key_org_id"),
user_api_key_org_alias=_meta.get("user_api_key_org_alias"),
user_api_key_team_id=_meta.get("user_api_key_team_id"),
@ -196,6 +200,10 @@ class PagerDutyAlerting(SlackAlerting):
if user_api_key_dict.budget_reset_at
else None
),
user_api_key_user_spend=user_api_key_dict.user_spend,
user_api_key_user_max_budget=user_api_key_dict.user_max_budget,
user_api_key_team_spend=user_api_key_dict.team_spend,
user_api_key_team_max_budget=user_api_key_dict.team_max_budget,
user_api_key_org_id=user_api_key_dict.org_id,
user_api_key_org_alias=user_api_key_dict.organization_alias,
user_api_key_team_id=user_api_key_dict.team_id,

View file

@ -4597,6 +4597,10 @@ class StandardLoggingPayloadSetup:
user_api_key_spend=None,
user_api_key_max_budget=None,
user_api_key_budget_reset_at=None,
user_api_key_user_spend=None,
user_api_key_user_max_budget=None,
user_api_key_team_spend=None,
user_api_key_team_max_budget=None,
user_api_key_team_id=None,
user_api_key_org_id=None,
user_api_key_org_alias=None,
@ -5428,6 +5432,10 @@ def get_standard_logging_metadata(
user_api_key_spend=None,
user_api_key_max_budget=None,
user_api_key_budget_reset_at=None,
user_api_key_user_spend=None,
user_api_key_user_max_budget=None,
user_api_key_team_spend=None,
user_api_key_team_max_budget=None,
user_api_key_team_id=None,
user_api_key_org_id=None,
user_api_key_org_alias=None,
@ -5527,6 +5535,10 @@ def create_dummy_standard_logging_payload() -> StandardLoggingPayload:
user_api_key_team_id=str("test_team"),
user_api_key_user_id=str("test_user"),
user_api_key_team_alias=str("test_team_alias"),
user_api_key_user_spend=None,
user_api_key_user_max_budget=None,
user_api_key_team_spend=None,
user_api_key_team_max_budget=None,
user_api_key_org_id=None,
spend_logs_metadata=None,
requester_ip_address=str("127.0.0.1"),

View file

@ -949,6 +949,10 @@ class LiteLLMProxyRequestSetup:
user_api_key_alias=user_api_key_dict.key_alias,
user_api_key_spend=user_api_key_dict.spend,
user_api_key_max_budget=user_api_key_dict.max_budget,
user_api_key_user_spend=user_api_key_dict.user_spend,
user_api_key_user_max_budget=user_api_key_dict.user_max_budget,
user_api_key_team_spend=user_api_key_dict.team_spend,
user_api_key_team_max_budget=user_api_key_dict.team_max_budget,
user_api_key_team_id=user_api_key_dict.team_id,
user_api_key_project_id=user_api_key_dict.project_id,
user_api_key_project_alias=user_api_key_dict.project_alias,

View file

@ -2475,6 +2475,10 @@ class StandardLoggingUserAPIKeyMetadata(TypedDict):
user_api_key_spend: Optional[float]
user_api_key_max_budget: Optional[float]
user_api_key_budget_reset_at: Optional[str]
user_api_key_user_spend: Optional[float]
user_api_key_user_max_budget: Optional[float]
user_api_key_team_spend: Optional[float]
user_api_key_team_max_budget: Optional[float]
user_api_key_org_id: Optional[str]
user_api_key_org_alias: Optional[str]
user_api_key_team_id: Optional[str]

View file

@ -2688,6 +2688,70 @@ def test_get_sanitized_user_information_from_key_includes_guardrails_metadata():
assert result["user_api_key_auth_metadata"]["other_field"] == "value"
def test_user_and_team_spend_and_budget_flow_to_standard_logging_metadata():
"""
Full flow: UserAPIKeyAuth -> get_sanitized_user_information_from_key ->
get_standard_logging_metadata. User-level and team-level spend + max budget
must reach the StandardLoggingPayload metadata that custom loggers receive,
alongside the key-level values
"""
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
user_api_key_dict = UserAPIKeyAuth(
api_key="test-key-hash",
spend=1.5,
max_budget=10.0,
user_id="test-user",
user_spend=25.5,
user_max_budget=100.0,
team_id="test-team",
team_spend=250.75,
team_max_budget=1000.0,
)
sanitized = LiteLLMProxyRequestSetup.get_sanitized_user_information_from_key(
user_api_key_dict=user_api_key_dict
)
assert sanitized["user_api_key_spend"] == 1.5
assert sanitized["user_api_key_max_budget"] == 10.0
assert sanitized["user_api_key_user_spend"] == 25.5
assert sanitized["user_api_key_user_max_budget"] == 100.0
assert sanitized["user_api_key_team_spend"] == 250.75
assert sanitized["user_api_key_team_max_budget"] == 1000.0
logging_metadata = StandardLoggingPayloadSetup.get_standard_logging_metadata(
dict(sanitized)
)
assert logging_metadata["user_api_key_user_spend"] == 25.5
assert logging_metadata["user_api_key_user_max_budget"] == 100.0
assert logging_metadata["user_api_key_team_spend"] == 250.75
assert logging_metadata["user_api_key_team_max_budget"] == 1000.0
def test_user_and_team_spend_and_budget_default_to_none_in_standard_logging_metadata():
"""
Keys with no user or team level budgets report None for the new fields in the
StandardLoggingPayload metadata instead of raising
"""
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
user_api_key_dict = UserAPIKeyAuth(api_key="test-key-hash")
sanitized = LiteLLMProxyRequestSetup.get_sanitized_user_information_from_key(
user_api_key_dict=user_api_key_dict
)
logging_metadata = StandardLoggingPayloadSetup.get_standard_logging_metadata(
dict(sanitized)
)
assert logging_metadata["user_api_key_user_spend"] is None
assert logging_metadata["user_api_key_user_max_budget"] is None
assert logging_metadata["user_api_key_team_spend"] is None
assert logging_metadata["user_api_key_team_max_budget"] is None
@pytest.mark.asyncio
async def test_team_guardrails_append_to_key_guardrails():
"""