forked from phoenix/litellm-mirror
Merge pull request #3826 from BerriAI/litellm_send_email_alerts
[Feat] Enterprise - Send Email Alerts when user, key crosses budget
This commit is contained in:
commit
354fe584e7
3 changed files with 95 additions and 5 deletions
|
@ -663,10 +663,10 @@ class SlackAlerting(CustomLogger):
|
|||
# check if crossed budget
|
||||
if user_info.spend >= user_info.max_budget:
|
||||
event = "budget_crossed"
|
||||
event_message += "Budget Crossed"
|
||||
event_message += f"Budget Crossed\n Total Budget:`{user_info.max_budget}`"
|
||||
elif percent_left <= 0.05:
|
||||
event = "threshold_crossed"
|
||||
event_message += "5% Threshold Crossed"
|
||||
event_message += "5% Threshold Crossed "
|
||||
elif percent_left <= 0.15:
|
||||
event = "threshold_crossed"
|
||||
event_message += "15% Threshold Crossed"
|
||||
|
@ -781,6 +781,57 @@ Model Info:
|
|||
|
||||
return False
|
||||
|
||||
async def send_email_alert_using_smtp(self, webhook_event: WebhookEvent) -> bool:
|
||||
"""
|
||||
Sends structured Email alert to an SMTP server
|
||||
|
||||
Currently only implemented for budget alerts
|
||||
|
||||
Returns -> True if sent, False if not.
|
||||
"""
|
||||
from litellm.proxy.utils import send_email
|
||||
|
||||
event_name = webhook_event.event_message
|
||||
recipient_email = webhook_event.user_email
|
||||
user_name = webhook_event.user_id
|
||||
max_budget = webhook_event.max_budget
|
||||
email_html_content = "Alert from LiteLLM Server"
|
||||
if recipient_email is None:
|
||||
verbose_proxy_logger.error(
|
||||
"Trying to send email alert to no recipient", extra=webhook_event.dict()
|
||||
)
|
||||
|
||||
if webhook_event.event == "budget_crossed":
|
||||
email_html_content = f"""
|
||||
<h1>LiteLLM</h1>
|
||||
|
||||
<p> Hi {user_name}, <br/>
|
||||
|
||||
Your LLM API usage this month has reached your account's monthly budget of ${max_budget} <br /> <br />
|
||||
|
||||
API requests will be rejected until either (a) you increase your monthly budget or (b) your monthly usage resets at the beginning of the next calendar month. <br /> <br />
|
||||
|
||||
If you have any questions, please send an email to support@berri.ai <br /> <br />
|
||||
|
||||
Best, <br />
|
||||
The LiteLLM team <br />
|
||||
"""
|
||||
|
||||
payload = webhook_event.model_dump_json()
|
||||
email_event = {
|
||||
"to": recipient_email,
|
||||
"subject": f"LiteLLM: {event_name}",
|
||||
"html": email_html_content,
|
||||
}
|
||||
|
||||
response = await send_email(
|
||||
receiver_email=email_event["to"],
|
||||
subject=email_event["subject"],
|
||||
html=email_event["html"],
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
async def send_alert(
|
||||
self,
|
||||
message: str,
|
||||
|
@ -823,6 +874,14 @@ Model Info:
|
|||
):
|
||||
await self.send_webhook_alert(webhook_event=user_info)
|
||||
|
||||
if (
|
||||
"email" in self.alerting
|
||||
and alert_type == "budget_alerts"
|
||||
and user_info is not None
|
||||
):
|
||||
# only send budget alerts over Email
|
||||
await self.send_email_alert_using_smtp(webhook_event=user_info)
|
||||
|
||||
if "slack" not in self.alerting:
|
||||
return
|
||||
|
||||
|
|
|
@ -1062,12 +1062,26 @@ async def user_api_key_auth(
|
|||
|
||||
# Check 4. Token Spend is under budget
|
||||
if valid_token.spend is not None and valid_token.max_budget is not None:
|
||||
|
||||
####################################
|
||||
# collect information for alerting #
|
||||
####################################
|
||||
|
||||
user_email = None
|
||||
# Check if the token has any user id information
|
||||
if user_id_information is not None:
|
||||
if isinstance(user_id_information, list):
|
||||
user_id_information = user_id_information[0]
|
||||
user_email = user_id_information.get("user_email", None)
|
||||
|
||||
call_info = CallInfo(
|
||||
token=valid_token.token,
|
||||
spend=valid_token.spend,
|
||||
max_budget=valid_token.max_budget,
|
||||
user_id=valid_token.user_id,
|
||||
team_id=valid_token.team_id,
|
||||
user_email=user_email,
|
||||
key_alias=valid_token.key_alias,
|
||||
)
|
||||
asyncio.create_task(
|
||||
proxy_logging_obj.budget_alerts(
|
||||
|
@ -1076,6 +1090,10 @@ async def user_api_key_auth(
|
|||
)
|
||||
)
|
||||
|
||||
####################################
|
||||
# collect information for alerting #
|
||||
####################################
|
||||
|
||||
if valid_token.spend >= valid_token.max_budget:
|
||||
raise Exception(
|
||||
f"ExceededTokenBudget: Current spend for token: {valid_token.spend}; Max Budget for Token: {valid_token.max_budget}"
|
||||
|
|
|
@ -1819,7 +1819,7 @@ async def _cache_user_row(
|
|||
return
|
||||
|
||||
|
||||
async def send_email(sender_name, sender_email, receiver_email, subject, html):
|
||||
async def send_email(receiver_email, subject, html):
|
||||
"""
|
||||
smtp_host,
|
||||
smtp_port,
|
||||
|
@ -1829,13 +1829,27 @@ async def send_email(sender_name, sender_email, receiver_email, subject, html):
|
|||
sender_email,
|
||||
"""
|
||||
## SERVER SETUP ##
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
from litellm.proxy.proxy_server import CommonProxyErrors
|
||||
|
||||
# Check if user is premium - This is an Enterprise only Feature
|
||||
if premium_user != True:
|
||||
raise Exception(
|
||||
f"Trying to use Email Alerting\n {CommonProxyErrors.not_premium_user.value}"
|
||||
)
|
||||
# Done Checking
|
||||
|
||||
smtp_host = os.getenv("SMTP_HOST")
|
||||
smtp_port = os.getenv("SMTP_PORT", 587) # default to port 587
|
||||
smtp_username = os.getenv("SMTP_USERNAME")
|
||||
smtp_password = os.getenv("SMTP_PASSWORD")
|
||||
sender_email = os.getenv("SMTP_SENDER_EMAIL", None)
|
||||
if sender_email is None:
|
||||
raise Exception("Trying to use SMTP, but SMTP_SENDER_EMAIL is not set")
|
||||
|
||||
## EMAIL SETUP ##
|
||||
email_message = MIMEMultipart()
|
||||
email_message["From"] = f"{sender_name} <{sender_email}>"
|
||||
email_message["From"] = sender_email
|
||||
email_message["To"] = receiver_email
|
||||
email_message["Subject"] = subject
|
||||
|
||||
|
@ -1843,7 +1857,6 @@ async def send_email(sender_name, sender_email, receiver_email, subject, html):
|
|||
email_message.attach(MIMEText(html, "html"))
|
||||
|
||||
try:
|
||||
print_verbose(f"SMTP Connection Init")
|
||||
# Establish a secure connection with the SMTP server
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
if os.getenv("SMTP_TLS", "True") != "False":
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue