feat: add global fallback config, fix no-owner crash, improve email greeting
- Add `default_key_max_budget_alert_emails` litellm_settings config as global fallback for all virtual keys (per-key metadata takes priority) - Fix crash when key has no user_id/user_email by passing recipient email to _get_email_params (same pattern as team soft budget path) - Use owner email for greeting, falling back to key_alias or token - Rename setting from default_max_budget_alert_emails to default_key_max_budget_alert_emails for clarity
This commit is contained in:
parent
ea509ec6b9
commit
41a719a537
@ -376,6 +376,7 @@ datadog_params: Optional[Union[DatadogInitParams, Dict]] = None
|
||||
aws_sqs_callback_params: Optional[Dict] = None
|
||||
generic_logger_headers: Optional[Dict] = None
|
||||
default_key_generate_params: Optional[Dict] = None
|
||||
default_key_max_budget_alert_emails: Optional[Dict[str, list]] = None
|
||||
upperbound_key_generate_params: Optional[LiteLLM_UpperboundKeyGenerateParams] = None
|
||||
key_generation_settings: Optional["StandardKeyGenerationConfig"] = None
|
||||
default_internal_user_params: Optional[Dict] = None
|
||||
|
||||
@ -2966,7 +2966,7 @@ async def _virtual_key_max_budget_alert_check(
|
||||
owner_email = user_obj.user_email if user_obj else None
|
||||
alert_email_config = (valid_token.metadata or {}).get(
|
||||
"max_budget_alert_emails"
|
||||
)
|
||||
) or litellm.default_key_max_budget_alert_emails
|
||||
|
||||
if isinstance(alert_email_config, dict) and alert_email_config:
|
||||
# New path: pass the map through, let the email handler decide what to fire
|
||||
|
||||
@ -1652,6 +1652,86 @@ async def test_virtual_key_max_budget_alert_check_old_path_below_threshold_no_al
|
||||
assert alert_triggered is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_virtual_key_max_budget_alert_check_global_fallback():
|
||||
"""Test that litellm.default_key_max_budget_alert_emails is used when key metadata has no map"""
|
||||
alert_triggered = False
|
||||
captured_call_info = None
|
||||
|
||||
class MockProxyLogging:
|
||||
async def budget_alerts(self, type, user_info):
|
||||
nonlocal alert_triggered, captured_call_info
|
||||
alert_triggered = True
|
||||
captured_call_info = user_info
|
||||
|
||||
global_config = {
|
||||
"50": ["global-finance@co.com"],
|
||||
"75": ["global-finance@co.com", "global-lead@co.com"],
|
||||
}
|
||||
valid_token = UserAPIKeyAuth(
|
||||
token="test-token",
|
||||
spend=30.0,
|
||||
max_budget=100.0,
|
||||
user_id="test-user",
|
||||
key_alias="test-key",
|
||||
metadata={}, # no per-key config
|
||||
)
|
||||
|
||||
import litellm
|
||||
original = litellm.default_key_max_budget_alert_emails
|
||||
try:
|
||||
litellm.default_key_max_budget_alert_emails = global_config
|
||||
await _virtual_key_max_budget_alert_check(
|
||||
valid_token=valid_token,
|
||||
proxy_logging_obj=MockProxyLogging(),
|
||||
user_obj=None,
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
assert alert_triggered is True
|
||||
assert captured_call_info.max_budget_alert_emails == global_config
|
||||
finally:
|
||||
litellm.default_key_max_budget_alert_emails = original
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_virtual_key_max_budget_alert_check_per_key_overrides_global():
|
||||
"""Test that per-key metadata takes priority over global fallback"""
|
||||
captured_call_info = None
|
||||
|
||||
class MockProxyLogging:
|
||||
async def budget_alerts(self, type, user_info):
|
||||
nonlocal captured_call_info
|
||||
captured_call_info = user_info
|
||||
|
||||
per_key_config = {"50": ["per-key@co.com"]}
|
||||
global_config = {"75": ["global@co.com"]}
|
||||
|
||||
valid_token = UserAPIKeyAuth(
|
||||
token="test-token",
|
||||
spend=30.0,
|
||||
max_budget=100.0,
|
||||
user_id="test-user",
|
||||
key_alias="test-key",
|
||||
metadata={"max_budget_alert_emails": per_key_config},
|
||||
)
|
||||
|
||||
import litellm
|
||||
original = litellm.default_key_max_budget_alert_emails
|
||||
try:
|
||||
litellm.default_key_max_budget_alert_emails = global_config
|
||||
await _virtual_key_max_budget_alert_check(
|
||||
valid_token=valid_token,
|
||||
proxy_logging_obj=MockProxyLogging(),
|
||||
user_obj=None,
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
assert captured_call_info.max_budget_alert_emails == per_key_config
|
||||
finally:
|
||||
litellm.default_key_max_budget_alert_emails = original
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_fuzzy_user_object_case_insensitive_email():
|
||||
"""Test that _get_fuzzy_user_object uses case-insensitive email lookup"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user