mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
Merge pull request #3829 from BerriAI/litellm_send_alerts_making_new_key
[Feat] - send Email alerts when making new key
This commit is contained in:
commit
732d57cf48
4 changed files with 140 additions and 10 deletions
|
|
@ -14,6 +14,7 @@ from pydantic import BaseModel
|
|||
from enum import Enum
|
||||
from datetime import datetime as dt, timedelta, timezone
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.proxy._types import WebhookEvent
|
||||
import random
|
||||
from typing import TypedDict
|
||||
from openai import APIError
|
||||
|
|
@ -30,6 +31,13 @@ class OutageModel(TypedDict):
|
|||
major_alert_sent: bool
|
||||
last_updated_at: float
|
||||
|
||||
# we use this for the email header, please send a test email if you change this. verify it looks good on email
|
||||
LITELLM_LOGO_URL = "https://litellm-listing.s3.amazonaws.com/litellm_logo.png"
|
||||
EMAIL_LOGO_URL = os.getenv(
|
||||
"SMTP_SENDER_LOGO", "https://litellm-listing.s3.amazonaws.com/litellm_logo.png"
|
||||
)
|
||||
EMAIL_SUPPORT_CONTACT = os.getenv("EMAIL_SUPPORT_CONTACT", "support@berri.ai")
|
||||
|
||||
|
||||
class LiteLLMBase(BaseModel):
|
||||
"""
|
||||
|
|
@ -57,12 +65,6 @@ class SlackAlertingArgs(LiteLLMBase):
|
|||
max_outage_alert_list_size: int = 10 # prevent memory leak
|
||||
|
||||
|
||||
class WebhookEvent(CallInfo):
|
||||
event: Literal["budget_crossed", "threshold_crossed", "projected_limit_exceeded"]
|
||||
event_group: Literal["user", "key", "team", "proxy"]
|
||||
event_message: str # human-readable description of event
|
||||
|
||||
|
||||
class DeploymentMetrics(LiteLLMBase):
|
||||
"""
|
||||
Metrics per deployment, stored in cache
|
||||
|
|
@ -664,6 +666,9 @@ class SlackAlerting(CustomLogger):
|
|||
_id = user_info.token
|
||||
|
||||
# percent of max_budget left to spend
|
||||
if user_info.max_budget is None:
|
||||
return
|
||||
|
||||
if user_info.max_budget > 0:
|
||||
percent_left = (
|
||||
user_info.max_budget - user_info.spend
|
||||
|
|
@ -946,6 +951,103 @@ Model Info:
|
|||
|
||||
return False
|
||||
|
||||
async def send_key_created_email(self, webhook_event: WebhookEvent) -> bool:
|
||||
from litellm.proxy.utils import send_email
|
||||
|
||||
if self.alerting is None or "email" not in self.alerting:
|
||||
# do nothing if user does not want email alerts
|
||||
return False
|
||||
|
||||
# make sure this is a premium user
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
from litellm.proxy.proxy_server import CommonProxyErrors, prisma_client
|
||||
|
||||
if premium_user != True:
|
||||
raise Exception(
|
||||
f"Trying to use Email Alerting on key creation\n {CommonProxyErrors.not_premium_user.value}"
|
||||
)
|
||||
|
||||
event_name = webhook_event.event_message
|
||||
recipient_email = webhook_event.user_email
|
||||
recipient_user_id = webhook_event.user_id
|
||||
if (
|
||||
recipient_email is None
|
||||
and recipient_user_id is not None
|
||||
and prisma_client is not None
|
||||
):
|
||||
user_row = await prisma_client.db.litellm_usertable.find_unique(
|
||||
where={"user_id": recipient_user_id}
|
||||
)
|
||||
|
||||
if user_row is not None:
|
||||
recipient_email = user_row.user_email
|
||||
|
||||
key_name = webhook_event.key_alias
|
||||
key_token = webhook_event.token
|
||||
key_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()
|
||||
)
|
||||
email_html_content = f"""
|
||||
<img src="{EMAIL_LOGO_URL}" alt="LiteLLM Logo" width="150" height="50" />
|
||||
|
||||
<p> Hi {recipient_email}, <br/>
|
||||
|
||||
I'm happy to provide you with an OpenAI Proxy API Key, loaded with ${key_budget} per month. <br /> <br />
|
||||
|
||||
<b>
|
||||
Key: <pre>{key_token}</pre> <br>
|
||||
</b>
|
||||
|
||||
<h2>Usage Example</h2>
|
||||
|
||||
Detailed Documentation on <a href="https://docs.litellm.ai/docs/proxy/user_keys">Usage with OpenAI Python SDK, Langchain, LlamaIndex, Curl</a>
|
||||
|
||||
<pre>
|
||||
|
||||
import openai
|
||||
client = openai.OpenAI(
|
||||
api_key="{key_token}",
|
||||
base_url={os.getenv("PROXY_BASE_URL", "http://0.0.0.0:4000")}
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-3.5-turbo", # model to send to the proxy
|
||||
messages = [
|
||||
{{
|
||||
"role": "user",
|
||||
"content": "this is a test request, write a short poem"
|
||||
}}
|
||||
]
|
||||
)
|
||||
|
||||
</pre>
|
||||
|
||||
|
||||
If you have any questions, please send an email to {EMAIL_SUPPORT_CONTACT} <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_email_alert_using_smtp(self, webhook_event: WebhookEvent) -> bool:
|
||||
"""
|
||||
Sends structured Email alert to an SMTP server
|
||||
|
|
@ -968,15 +1070,15 @@ Model Info:
|
|||
|
||||
if webhook_event.event == "budget_crossed":
|
||||
email_html_content = f"""
|
||||
<h1>LiteLLM</h1>
|
||||
<img src="{EMAIL_LOGO_URL}" alt="LiteLLM Logo" width="150" height="50" />
|
||||
|
||||
<p> Hi {user_name}, <br/>
|
||||
|
||||
Your LLM API usage this month has reached your account's monthly budget of ${max_budget} <br /> <br />
|
||||
Your LLM API usage this month has reached your account's <b> monthly budget of ${max_budget} </b> <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 />
|
||||
If you have any questions, please send an email to {EMAIL_SUPPORT_CONTACT} <br /> <br />
|
||||
|
||||
Best, <br />
|
||||
The LiteLLM team <br />
|
||||
|
|
|
|||
|
|
@ -1096,7 +1096,7 @@ class CallInfo(LiteLLMBase):
|
|||
"""Used for slack budget alerting"""
|
||||
|
||||
spend: float
|
||||
max_budget: float
|
||||
max_budget: Optional[float] = None
|
||||
token: str = Field(description="Hashed value of that key")
|
||||
user_id: Optional[str] = None
|
||||
team_id: Optional[str] = None
|
||||
|
|
@ -1104,3 +1104,11 @@ class CallInfo(LiteLLMBase):
|
|||
key_alias: Optional[str] = None
|
||||
projected_exceeded_date: Optional[str] = None
|
||||
projected_spend: Optional[float] = None
|
||||
|
||||
|
||||
class WebhookEvent(CallInfo):
|
||||
event: Literal[
|
||||
"budget_crossed", "threshold_crossed", "projected_limit_exceeded", "key_created"
|
||||
]
|
||||
event_group: Literal["user", "key", "team", "proxy"]
|
||||
event_message: str # human-readable description of event
|
||||
|
|
|
|||
|
|
@ -5402,6 +5402,25 @@ async def generate_key_fn(
|
|||
response["soft_budget"] = (
|
||||
data.soft_budget
|
||||
) # include the user-input soft budget in the response
|
||||
event = WebhookEvent(
|
||||
event="key_created",
|
||||
event_group="key",
|
||||
event_message=f"API Key Created",
|
||||
token=response.get("token", ""),
|
||||
spend=response.get("spend", 0.0),
|
||||
max_budget=response.get("max_budget", 0.0),
|
||||
user_id=response.get("user_id", None),
|
||||
team_id=response.get("team_id", "Default Team"),
|
||||
key_alias=response.get("key_alias", None),
|
||||
)
|
||||
|
||||
# If user configured email alerting - send an Email letting their end-user know the key was created
|
||||
asyncio.create_task(
|
||||
proxy_logging_obj.slack_alerting_instance.send_key_created_email(
|
||||
webhook_event=event,
|
||||
)
|
||||
)
|
||||
|
||||
return GenerateKeyResponse(**response)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from litellm.proxy._types import (
|
|||
LiteLLM_TeamTable,
|
||||
Member,
|
||||
CallInfo,
|
||||
WebhookEvent,
|
||||
AlertType,
|
||||
)
|
||||
from litellm.caching import DualCache, RedisCache
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue