mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 11:43:54 +00:00
Merge branch 'main' into litellm_send_alerts_making_new_key
This commit is contained in:
commit
d3a8306952
26 changed files with 974 additions and 550 deletions
|
@ -1,7 +1,7 @@
|
||||||
#### What this does ####
|
#### What this does ####
|
||||||
# Class for sending Slack Alerts #
|
# Class for sending Slack Alerts #
|
||||||
import dotenv, os
|
import dotenv, os, traceback
|
||||||
from litellm.proxy._types import UserAPIKeyAuth, CallInfo
|
from litellm.proxy._types import UserAPIKeyAuth, CallInfo, AlertType
|
||||||
from litellm._logging import verbose_logger, verbose_proxy_logger
|
from litellm._logging import verbose_logger, verbose_proxy_logger
|
||||||
import litellm, threading
|
import litellm, threading
|
||||||
from typing import List, Literal, Any, Union, Optional, Dict
|
from typing import List, Literal, Any, Union, Optional, Dict
|
||||||
|
@ -16,6 +16,20 @@ from datetime import datetime as dt, timedelta, timezone
|
||||||
from litellm.integrations.custom_logger import CustomLogger
|
from litellm.integrations.custom_logger import CustomLogger
|
||||||
from litellm.proxy._types import WebhookEvent
|
from litellm.proxy._types import WebhookEvent
|
||||||
import random
|
import random
|
||||||
|
from typing import TypedDict
|
||||||
|
from openai import APIError
|
||||||
|
|
||||||
|
import litellm.types
|
||||||
|
import litellm.types.router
|
||||||
|
|
||||||
|
|
||||||
|
class OutageModel(TypedDict):
|
||||||
|
model_id: str
|
||||||
|
alerts: List[int]
|
||||||
|
deployment_ids: List[str]
|
||||||
|
minor_alert_sent: bool
|
||||||
|
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
|
# 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"
|
LITELLM_LOGO_URL = "https://litellm-listing.s3.amazonaws.com/litellm_logo.png"
|
||||||
|
@ -45,6 +59,10 @@ class SlackAlertingArgs(LiteLLMBase):
|
||||||
)
|
)
|
||||||
report_check_interval: int = 5 * 60 # 5 minutes
|
report_check_interval: int = 5 * 60 # 5 minutes
|
||||||
budget_alert_ttl: int = 24 * 60 * 60 # 24 hours
|
budget_alert_ttl: int = 24 * 60 * 60 # 24 hours
|
||||||
|
outage_alert_ttl: int = 1 * 60 # 1 minute ttl
|
||||||
|
minor_outage_alert_threshold: int = 5
|
||||||
|
major_outage_alert_threshold: int = 10
|
||||||
|
max_outage_alert_list_size: int = 10 # prevent memory leak
|
||||||
|
|
||||||
|
|
||||||
class DeploymentMetrics(LiteLLMBase):
|
class DeploymentMetrics(LiteLLMBase):
|
||||||
|
@ -88,19 +106,7 @@ class SlackAlerting(CustomLogger):
|
||||||
internal_usage_cache: Optional[DualCache] = None,
|
internal_usage_cache: Optional[DualCache] = None,
|
||||||
alerting_threshold: float = 300, # threshold for slow / hanging llm responses (in seconds)
|
alerting_threshold: float = 300, # threshold for slow / hanging llm responses (in seconds)
|
||||||
alerting: Optional[List] = [],
|
alerting: Optional[List] = [],
|
||||||
alert_types: List[
|
alert_types: List[AlertType] = [
|
||||||
Literal[
|
|
||||||
"llm_exceptions",
|
|
||||||
"llm_too_slow",
|
|
||||||
"llm_requests_hanging",
|
|
||||||
"budget_alerts",
|
|
||||||
"db_exceptions",
|
|
||||||
"daily_reports",
|
|
||||||
"spend_reports",
|
|
||||||
"cooldown_deployment",
|
|
||||||
"new_model_added",
|
|
||||||
]
|
|
||||||
] = [
|
|
||||||
"llm_exceptions",
|
"llm_exceptions",
|
||||||
"llm_too_slow",
|
"llm_too_slow",
|
||||||
"llm_requests_hanging",
|
"llm_requests_hanging",
|
||||||
|
@ -110,6 +116,7 @@ class SlackAlerting(CustomLogger):
|
||||||
"spend_reports",
|
"spend_reports",
|
||||||
"cooldown_deployment",
|
"cooldown_deployment",
|
||||||
"new_model_added",
|
"new_model_added",
|
||||||
|
"outage_alerts",
|
||||||
],
|
],
|
||||||
alert_to_webhook_url: Optional[
|
alert_to_webhook_url: Optional[
|
||||||
Dict
|
Dict
|
||||||
|
@ -126,6 +133,7 @@ class SlackAlerting(CustomLogger):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.alerting_args = SlackAlertingArgs(**alerting_args)
|
self.alerting_args = SlackAlertingArgs(**alerting_args)
|
||||||
self.default_webhook_url = default_webhook_url
|
self.default_webhook_url = default_webhook_url
|
||||||
|
self.llm_router: Optional[litellm.Router] = None
|
||||||
|
|
||||||
def update_values(
|
def update_values(
|
||||||
self,
|
self,
|
||||||
|
@ -134,6 +142,7 @@ class SlackAlerting(CustomLogger):
|
||||||
alert_types: Optional[List] = None,
|
alert_types: Optional[List] = None,
|
||||||
alert_to_webhook_url: Optional[Dict] = None,
|
alert_to_webhook_url: Optional[Dict] = None,
|
||||||
alerting_args: Optional[Dict] = None,
|
alerting_args: Optional[Dict] = None,
|
||||||
|
llm_router: Optional[litellm.Router] = None,
|
||||||
):
|
):
|
||||||
if alerting is not None:
|
if alerting is not None:
|
||||||
self.alerting = alerting
|
self.alerting = alerting
|
||||||
|
@ -149,6 +158,8 @@ class SlackAlerting(CustomLogger):
|
||||||
self.alert_to_webhook_url = alert_to_webhook_url
|
self.alert_to_webhook_url = alert_to_webhook_url
|
||||||
else:
|
else:
|
||||||
self.alert_to_webhook_url.update(alert_to_webhook_url)
|
self.alert_to_webhook_url.update(alert_to_webhook_url)
|
||||||
|
if llm_router is not None:
|
||||||
|
self.llm_router = llm_router
|
||||||
|
|
||||||
async def deployment_in_cooldown(self):
|
async def deployment_in_cooldown(self):
|
||||||
pass
|
pass
|
||||||
|
@ -701,6 +712,158 @@ class SlackAlerting(CustomLogger):
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _count_outage_alerts(self, alerts: List[int]) -> str:
|
||||||
|
"""
|
||||||
|
Parameters:
|
||||||
|
- alerts: List[int] -> list of error codes (either 408 or 500+)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- str -> formatted string. This is an alert message, giving a human-friendly description of the errors.
|
||||||
|
"""
|
||||||
|
error_breakdown = {"Timeout Errors": 0, "API Errors": 0, "Unknown Errors": 0}
|
||||||
|
for alert in alerts:
|
||||||
|
if alert == 408:
|
||||||
|
error_breakdown["Timeout Errors"] += 1
|
||||||
|
elif alert >= 500:
|
||||||
|
error_breakdown["API Errors"] += 1
|
||||||
|
else:
|
||||||
|
error_breakdown["Unknown Errors"] += 1
|
||||||
|
|
||||||
|
error_msg = ""
|
||||||
|
for key, value in error_breakdown.items():
|
||||||
|
if value > 0:
|
||||||
|
error_msg += "\n{}: {}\n".format(key, value)
|
||||||
|
|
||||||
|
return error_msg
|
||||||
|
|
||||||
|
async def outage_alerts(
|
||||||
|
self,
|
||||||
|
exception: APIError,
|
||||||
|
deployment_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Send slack alert if model is badly configured / having an outage (408, 401, 429, >=500).
|
||||||
|
|
||||||
|
key = model_id
|
||||||
|
|
||||||
|
value = {
|
||||||
|
- model_id
|
||||||
|
- threshold
|
||||||
|
- alerts []
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl = 1hr
|
||||||
|
max_alerts_size = 10
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
outage_value: Optional[OutageModel] = await self.internal_usage_cache.async_get_cache(key=deployment_id) # type: ignore
|
||||||
|
if (
|
||||||
|
getattr(exception, "status_code", None) is None
|
||||||
|
or (
|
||||||
|
exception.status_code != 408 # type: ignore
|
||||||
|
and exception.status_code < 500 # type: ignore
|
||||||
|
)
|
||||||
|
or self.llm_router is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
### EXTRACT MODEL DETAILS ###
|
||||||
|
deployment = self.llm_router.get_deployment(model_id=deployment_id)
|
||||||
|
if deployment is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
model = deployment.litellm_params.model
|
||||||
|
provider = deployment.litellm_params.custom_llm_provider
|
||||||
|
if provider is None:
|
||||||
|
try:
|
||||||
|
model, provider, _, _ = litellm.get_llm_provider(model=model)
|
||||||
|
except Exception as e:
|
||||||
|
provider = ""
|
||||||
|
api_base = litellm.get_api_base(
|
||||||
|
model=model, optional_params=deployment.litellm_params
|
||||||
|
)
|
||||||
|
|
||||||
|
if outage_value is None:
|
||||||
|
outage_value = OutageModel(
|
||||||
|
model_id=deployment_id,
|
||||||
|
alerts=[exception.status_code], # type: ignore
|
||||||
|
deployment_ids=[deployment_id],
|
||||||
|
minor_alert_sent=False,
|
||||||
|
major_alert_sent=False,
|
||||||
|
last_updated_at=time.time(),
|
||||||
|
)
|
||||||
|
|
||||||
|
## add to cache ##
|
||||||
|
await self.internal_usage_cache.async_set_cache(
|
||||||
|
key=deployment_id,
|
||||||
|
value=outage_value,
|
||||||
|
ttl=self.alerting_args.outage_alert_ttl,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
outage_value["alerts"].append(exception.status_code) # type: ignore
|
||||||
|
outage_value["deployment_ids"].append(deployment_id)
|
||||||
|
outage_value["last_updated_at"] = time.time()
|
||||||
|
|
||||||
|
## MINOR OUTAGE ALERT SENT ##
|
||||||
|
if (
|
||||||
|
outage_value["minor_alert_sent"] == False
|
||||||
|
and len(outage_value["alerts"])
|
||||||
|
>= self.alerting_args.minor_outage_alert_threshold
|
||||||
|
):
|
||||||
|
msg = f"""\n\n
|
||||||
|
*⚠️ Minor Service Outage*
|
||||||
|
|
||||||
|
*Model Name:* `{model}`
|
||||||
|
*Provider:* `{provider}`
|
||||||
|
*API Base:* `{api_base}`
|
||||||
|
|
||||||
|
*Errors:*
|
||||||
|
{self._count_outage_alerts(alerts=outage_value["alerts"])}
|
||||||
|
|
||||||
|
|
||||||
|
*Last Check:* `{round(time.time() - outage_value["last_updated_at"], 4)}s ago`\n\n
|
||||||
|
"""
|
||||||
|
# send minor alert
|
||||||
|
_result_val = self.send_alert(
|
||||||
|
message=msg, level="Medium", alert_type="outage_alerts"
|
||||||
|
)
|
||||||
|
if _result_val is not None:
|
||||||
|
await _result_val
|
||||||
|
# set to true
|
||||||
|
outage_value["minor_alert_sent"] = True
|
||||||
|
elif (
|
||||||
|
outage_value["major_alert_sent"] == False
|
||||||
|
and len(outage_value["alerts"])
|
||||||
|
>= self.alerting_args.major_outage_alert_threshold
|
||||||
|
):
|
||||||
|
msg = f"""\n\n
|
||||||
|
*⚠️ Major Service Outage*
|
||||||
|
|
||||||
|
*Model Name:* `{model}`
|
||||||
|
*Provider:* `{provider}`
|
||||||
|
*API Base:* `{api_base}`
|
||||||
|
|
||||||
|
*Errors:*
|
||||||
|
{self._count_outage_alerts(alerts=outage_value["alerts"])}
|
||||||
|
|
||||||
|
|
||||||
|
*Last Check:* `{round(time.time() - outage_value["last_updated_at"], 4)}s ago`\n\n
|
||||||
|
"""
|
||||||
|
# send minor alert
|
||||||
|
await self.send_alert(
|
||||||
|
message=msg, level="High", alert_type="outage_alerts"
|
||||||
|
)
|
||||||
|
# set to true
|
||||||
|
outage_value["major_alert_sent"] = True
|
||||||
|
|
||||||
|
## update cache ##
|
||||||
|
await self.internal_usage_cache.async_set_cache(
|
||||||
|
key=deployment_id, value=outage_value
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
async def model_added_alert(
|
async def model_added_alert(
|
||||||
self, model_name: str, litellm_model_name: str, passed_model_info: Any
|
self, model_name: str, litellm_model_name: str, passed_model_info: Any
|
||||||
):
|
):
|
||||||
|
@ -750,10 +913,12 @@ Model Info:
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await self.send_alert(
|
alert_val = self.send_alert(
|
||||||
message=message, level="Low", alert_type="new_model_added"
|
message=message, level="Low", alert_type="new_model_added"
|
||||||
)
|
)
|
||||||
pass
|
|
||||||
|
if alert_val is not None and asyncio.iscoroutine(alert_val):
|
||||||
|
await alert_val
|
||||||
|
|
||||||
async def model_removed_alert(self, model_name: str):
|
async def model_removed_alert(self, model_name: str):
|
||||||
pass
|
pass
|
||||||
|
@ -948,6 +1113,7 @@ Model Info:
|
||||||
"spend_reports",
|
"spend_reports",
|
||||||
"new_model_added",
|
"new_model_added",
|
||||||
"cooldown_deployment",
|
"cooldown_deployment",
|
||||||
|
"outage_alerts",
|
||||||
],
|
],
|
||||||
user_info: Optional[WebhookEvent] = None,
|
user_info: Optional[WebhookEvent] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
@ -1071,10 +1237,12 @@ Model Info:
|
||||||
|
|
||||||
async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time):
|
async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time):
|
||||||
"""Log failure + deployment latency"""
|
"""Log failure + deployment latency"""
|
||||||
|
_litellm_params = kwargs.get("litellm_params", {})
|
||||||
|
_model_info = _litellm_params.get("model_info", {}) or {}
|
||||||
|
model_id = _model_info.get("id", "")
|
||||||
|
try:
|
||||||
if "daily_reports" in self.alert_types:
|
if "daily_reports" in self.alert_types:
|
||||||
model_id = (
|
try:
|
||||||
kwargs.get("litellm_params", {}).get("model_info", {}).get("id", "")
|
|
||||||
)
|
|
||||||
await self.async_update_daily_reports(
|
await self.async_update_daily_reports(
|
||||||
DeploymentMetrics(
|
DeploymentMetrics(
|
||||||
id=model_id,
|
id=model_id,
|
||||||
|
@ -1083,6 +1251,39 @@ Model Info:
|
||||||
updated_at=litellm.utils.get_utc_datetime(),
|
updated_at=litellm.utils.get_utc_datetime(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
verbose_logger.debug(f"Exception raises -{str(e)}")
|
||||||
|
|
||||||
|
if "outage_alerts" in self.alert_types and isinstance(
|
||||||
|
kwargs.get("exception", ""), APIError
|
||||||
|
):
|
||||||
|
_litellm_params = litellm.types.router.LiteLLM_Params(
|
||||||
|
model=kwargs.get("model", ""),
|
||||||
|
**kwargs.get("litellm_params", {}),
|
||||||
|
**kwargs.get("optional_params", {}),
|
||||||
|
)
|
||||||
|
_region_name = litellm.utils._get_model_region(
|
||||||
|
custom_llm_provider=kwargs.get("custom_llm_provider", ""),
|
||||||
|
litellm_params=_litellm_params,
|
||||||
|
)
|
||||||
|
# if region name not known, default to api base #
|
||||||
|
if _region_name is None:
|
||||||
|
_region_name = litellm.get_api_base(
|
||||||
|
model=kwargs.get("model", ""),
|
||||||
|
optional_params={
|
||||||
|
**kwargs.get("litellm_params", {}),
|
||||||
|
**kwargs.get("optional_params", {}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if _region_name is None:
|
||||||
|
_region_name = ""
|
||||||
|
|
||||||
|
await self.outage_alerts(
|
||||||
|
exception=kwargs["exception"],
|
||||||
|
deployment_id=model_id,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
async def _run_scheduler_helper(self, llm_router) -> bool:
|
async def _run_scheduler_helper(self, llm_router) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,29 +1,55 @@
|
||||||
|
import traceback
|
||||||
|
from litellm._logging import verbose_logger
|
||||||
|
import litellm
|
||||||
|
|
||||||
|
|
||||||
class TraceloopLogger:
|
class TraceloopLogger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
try:
|
||||||
from traceloop.sdk.tracing.tracing import TracerWrapper
|
from traceloop.sdk.tracing.tracing import TracerWrapper
|
||||||
from traceloop.sdk import Traceloop
|
from traceloop.sdk import Traceloop
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
verbose_logger.error(
|
||||||
|
f"Traceloop not installed, try running 'pip install traceloop-sdk' to fix this error: {e}\n{traceback.format_exc()}"
|
||||||
|
)
|
||||||
|
|
||||||
Traceloop.init(app_name="Litellm-Server", disable_batch=True)
|
Traceloop.init(
|
||||||
|
app_name="Litellm-Server",
|
||||||
|
disable_batch=True,
|
||||||
|
)
|
||||||
self.tracer_wrapper = TracerWrapper()
|
self.tracer_wrapper = TracerWrapper()
|
||||||
|
|
||||||
def log_event(self, kwargs, response_obj, start_time, end_time, print_verbose):
|
def log_event(
|
||||||
from opentelemetry.trace import SpanKind
|
self,
|
||||||
|
kwargs,
|
||||||
|
response_obj,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
user_id,
|
||||||
|
print_verbose,
|
||||||
|
level="DEFAULT",
|
||||||
|
status_message=None,
|
||||||
|
):
|
||||||
|
from opentelemetry import trace
|
||||||
|
from opentelemetry.trace import SpanKind, Status, StatusCode
|
||||||
from opentelemetry.semconv.ai import SpanAttributes
|
from opentelemetry.semconv.ai import SpanAttributes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tracer = self.tracer_wrapper.get_tracer()
|
print_verbose(
|
||||||
|
f"Traceloop Logging - Enters logging function for model {kwargs}"
|
||||||
|
)
|
||||||
|
|
||||||
model = kwargs.get("model")
|
tracer = self.tracer_wrapper.get_tracer()
|
||||||
|
|
||||||
# LiteLLM uses the standard OpenAI library, so it's already handled by Traceloop SDK
|
# LiteLLM uses the standard OpenAI library, so it's already handled by Traceloop SDK
|
||||||
if kwargs.get("litellm_params").get("custom_llm_provider") == "openai":
|
if kwargs.get("litellm_params").get("custom_llm_provider") == "openai":
|
||||||
return
|
return
|
||||||
|
|
||||||
optional_params = kwargs.get("optional_params", {})
|
optional_params = kwargs.get("optional_params", {})
|
||||||
with tracer.start_as_current_span(
|
span = tracer.start_span(
|
||||||
"litellm.completion",
|
"litellm.completion", kind=SpanKind.CLIENT, start_time=start_time
|
||||||
kind=SpanKind.CLIENT,
|
)
|
||||||
) as span:
|
|
||||||
if span.is_recording():
|
if span.is_recording():
|
||||||
span.set_attribute(
|
span.set_attribute(
|
||||||
SpanAttributes.LLM_REQUEST_MODEL, kwargs.get("model")
|
SpanAttributes.LLM_REQUEST_MODEL, kwargs.get("model")
|
||||||
|
@ -50,9 +76,7 @@ class TraceloopLogger:
|
||||||
if "tools" in optional_params or "functions" in optional_params:
|
if "tools" in optional_params or "functions" in optional_params:
|
||||||
span.set_attribute(
|
span.set_attribute(
|
||||||
SpanAttributes.LLM_REQUEST_FUNCTIONS,
|
SpanAttributes.LLM_REQUEST_FUNCTIONS,
|
||||||
optional_params.get(
|
optional_params.get("tools", optional_params.get("functions")),
|
||||||
"tools", optional_params.get("functions")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if "user" in optional_params:
|
if "user" in optional_params:
|
||||||
span.set_attribute(
|
span.set_attribute(
|
||||||
|
@ -65,7 +89,8 @@ class TraceloopLogger:
|
||||||
)
|
)
|
||||||
if "temperature" in optional_params:
|
if "temperature" in optional_params:
|
||||||
span.set_attribute(
|
span.set_attribute(
|
||||||
SpanAttributes.LLM_TEMPERATURE, kwargs.get("temperature")
|
SpanAttributes.LLM_REQUEST_TEMPERATURE,
|
||||||
|
kwargs.get("temperature"),
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx, prompt in enumerate(kwargs.get("messages")):
|
for idx, prompt in enumerate(kwargs.get("messages")):
|
||||||
|
@ -110,5 +135,15 @@ class TraceloopLogger:
|
||||||
choice.get("message").get("content"),
|
choice.get("message").get("content"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
level == "ERROR"
|
||||||
|
and status_message is not None
|
||||||
|
and isinstance(status_message, str)
|
||||||
|
):
|
||||||
|
span.record_exception(Exception(status_message))
|
||||||
|
span.set_status(Status(StatusCode.ERROR, status_message))
|
||||||
|
|
||||||
|
span.end(end_time)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_verbose(f"Traceloop Layer Error - {e}")
|
print_verbose(f"Traceloop Layer Error - {e}")
|
||||||
|
|
|
@ -126,7 +126,7 @@ def convert_to_ollama_image(openai_image_url: str):
|
||||||
else:
|
else:
|
||||||
base64_data = openai_image_url
|
base64_data = openai_image_url
|
||||||
|
|
||||||
return base64_data;
|
return base64_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "Error: Unable to fetch image from URL" in str(e):
|
if "Error: Unable to fetch image from URL" in str(e):
|
||||||
raise e
|
raise e
|
||||||
|
@ -134,6 +134,7 @@ def convert_to_ollama_image(openai_image_url: str):
|
||||||
"""Image url not in expected format. Example Expected input - "image_url": "data:image/jpeg;base64,{base64_image}". """
|
"""Image url not in expected format. Example Expected input - "image_url": "data:image/jpeg;base64,{base64_image}". """
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ollama_pt(
|
def ollama_pt(
|
||||||
model, messages
|
model, messages
|
||||||
): # https://github.com/ollama/ollama/blob/af4cf55884ac54b9e637cd71dadfe9b7a5685877/docs/modelfile.md#template
|
): # https://github.com/ollama/ollama/blob/af4cf55884ac54b9e637cd71dadfe9b7a5685877/docs/modelfile.md#template
|
||||||
|
@ -166,7 +167,9 @@ def ollama_pt(
|
||||||
if element["type"] == "text":
|
if element["type"] == "text":
|
||||||
prompt += element["text"]
|
prompt += element["text"]
|
||||||
elif element["type"] == "image_url":
|
elif element["type"] == "image_url":
|
||||||
base64_image = convert_to_ollama_image(element["image_url"]["url"])
|
base64_image = convert_to_ollama_image(
|
||||||
|
element["image_url"]["url"]
|
||||||
|
)
|
||||||
images.append(base64_image)
|
images.append(base64_image)
|
||||||
return {"prompt": prompt, "images": images}
|
return {"prompt": prompt, "images": images}
|
||||||
else:
|
else:
|
||||||
|
@ -1533,6 +1536,7 @@ def _gemini_vision_convert_messages(messages: list):
|
||||||
# Case 2: Base64 image data
|
# Case 2: Base64 image data
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
|
|
||||||
# Extract the base64 image data
|
# Extract the base64 image data
|
||||||
base64_data = img.split("base64,")[1]
|
base64_data = img.split("base64,")[1]
|
||||||
|
|
||||||
|
|
|
@ -420,6 +420,8 @@ def mock_completion(
|
||||||
api_key="mock-key",
|
api_key="mock-key",
|
||||||
)
|
)
|
||||||
if isinstance(mock_response, Exception):
|
if isinstance(mock_response, Exception):
|
||||||
|
if isinstance(mock_response, openai.APIError):
|
||||||
|
raise mock_response
|
||||||
raise litellm.APIError(
|
raise litellm.APIError(
|
||||||
status_code=500, # type: ignore
|
status_code=500, # type: ignore
|
||||||
message=str(mock_response),
|
message=str(mock_response),
|
||||||
|
@ -463,7 +465,9 @@ def mock_completion(
|
||||||
|
|
||||||
return model_response
|
return model_response
|
||||||
|
|
||||||
except:
|
except Exception as e:
|
||||||
|
if isinstance(e, openai.APIError):
|
||||||
|
raise e
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise Exception("Mock completion response failed")
|
raise Exception("Mock completion response failed")
|
||||||
|
|
||||||
|
@ -864,6 +868,7 @@ def completion(
|
||||||
user=user,
|
user=user,
|
||||||
optional_params=optional_params,
|
optional_params=optional_params,
|
||||||
litellm_params=litellm_params,
|
litellm_params=litellm_params,
|
||||||
|
custom_llm_provider=custom_llm_provider,
|
||||||
)
|
)
|
||||||
if mock_response:
|
if mock_response:
|
||||||
return mock_completion(
|
return mock_completion(
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{11837:function(n,e,t){Promise.resolve().then(t.t.bind(t,99646,23)),Promise.resolve().then(t.t.bind(t,63385,23))},63385:function(){},99646:function(n){n.exports={style:{fontFamily:"'__Inter_12bbc4', '__Inter_Fallback_12bbc4'",fontStyle:"normal"},className:"__className_12bbc4"}}},function(n){n.O(0,[971,69,744],function(){return n(n.s=11837)}),_N_E=n.O()}]);
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{93553:function(n,e,t){Promise.resolve().then(t.t.bind(t,63385,23)),Promise.resolve().then(t.t.bind(t,99646,23))},63385:function(){},99646:function(n){n.exports={style:{fontFamily:"'__Inter_12bbc4', '__Inter_Fallback_12bbc4'",fontStyle:"normal"},className:"__className_12bbc4"}}},function(n){n.O(0,[971,69,744],function(){return n(n.s=93553)}),_N_E=n.O()}]);
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e](n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){return"static/css/103fe7af2014a1c2.css"},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/ui/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e](n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){return"static/css/9e367ab966b14e29.css"},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/ui/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-f7340db8b64cd999.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-f960ab1e6d32b002.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-04708d7d4a17c1ee.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>LiteLLM Dashboard</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-f7340db8b64cd999.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/103fe7af2014a1c2.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[94430,[\"936\",\"static/chunks/2f6dbc85-052c4579f80d66ae.js\",\"507\",\"static/chunks/507-0aee992ad94e4137.js\",\"931\",\"static/chunks/app/page-9547f131c3870082.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/103fe7af2014a1c2.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"9Kn8POydvrC2EQ8cCUuvp\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_12bbc4\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"LiteLLM Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>
|
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-3cc604e175425ddd.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-f960ab1e6d32b002.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-04708d7d4a17c1ee.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>LiteLLM Dashboard</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-3cc604e175425ddd.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/9e367ab966b14e29.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[94430,[\"936\",\"static/chunks/2f6dbc85-052c4579f80d66ae.js\",\"507\",\"static/chunks/507-0aee992ad94e4137.js\",\"931\",\"static/chunks/app/page-7219129f052f09c7.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/9e367ab966b14e29.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"x4G9dq-RO3w0GR_CKU40g\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_12bbc4\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"LiteLLM Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>
|
|
@ -1,7 +1,7 @@
|
||||||
2:I[77831,[],""]
|
2:I[77831,[],""]
|
||||||
3:I[94430,["936","static/chunks/2f6dbc85-052c4579f80d66ae.js","507","static/chunks/507-0aee992ad94e4137.js","931","static/chunks/app/page-9547f131c3870082.js"],""]
|
3:I[94430,["936","static/chunks/2f6dbc85-052c4579f80d66ae.js","507","static/chunks/507-0aee992ad94e4137.js","931","static/chunks/app/page-7219129f052f09c7.js"],""]
|
||||||
4:I[5613,[],""]
|
4:I[5613,[],""]
|
||||||
5:I[31778,[],""]
|
5:I[31778,[],""]
|
||||||
0:["9Kn8POydvrC2EQ8cCUuvp",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_12bbc4","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/103fe7af2014a1c2.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
|
0:["x4G9dq-RO3w0GR_CKU40g",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_12bbc4","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/9e367ab966b14e29.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
|
||||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
|
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
|
||||||
1:null
|
1:null
|
||||||
|
|
|
@ -1,30 +1,47 @@
|
||||||
|
general_settings:
|
||||||
|
alert_to_webhook_url:
|
||||||
|
budget_alerts: https://hooks.slack.com/services/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
|
||||||
|
daily_reports: https://hooks.slack.com/services/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
|
||||||
|
db_exceptions: https://hooks.slack.com/services/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
|
||||||
|
llm_exceptions: https://hooks.slack.com/services/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
|
||||||
|
llm_requests_hanging: https://hooks.slack.com/services/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
|
||||||
|
llm_too_slow: https://hooks.slack.com/services/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
|
||||||
|
outage_alerts: https://hooks.slack.com/services/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
|
||||||
|
alert_types:
|
||||||
|
- llm_exceptions
|
||||||
|
- llm_too_slow
|
||||||
|
- llm_requests_hanging
|
||||||
|
- budget_alerts
|
||||||
|
- db_exceptions
|
||||||
|
- daily_reports
|
||||||
|
- spend_reports
|
||||||
|
- cooldown_deployment
|
||||||
|
- new_model_added
|
||||||
|
- outage_alerts
|
||||||
|
alerting:
|
||||||
|
- slack
|
||||||
|
database_connection_pool_limit: 100
|
||||||
|
database_connection_timeout: 60
|
||||||
|
health_check_interval: 300
|
||||||
|
ui_access_mode: all
|
||||||
|
litellm_settings:
|
||||||
|
json_logs: true
|
||||||
model_list:
|
model_list:
|
||||||
- model_name: gpt-3.5-turbo-fake-model
|
- litellm_params:
|
||||||
litellm_params:
|
|
||||||
model: openai/my-fake-model
|
|
||||||
api_base: http://0.0.0.0:8080
|
api_base: http://0.0.0.0:8080
|
||||||
api_key: ""
|
api_key: ''
|
||||||
- model_name: gpt-3.5-turbo
|
model: openai/my-fake-model
|
||||||
litellm_params:
|
model_name: gpt-3.5-turbo-fake-model
|
||||||
model: azure/gpt-35-turbo
|
- litellm_params:
|
||||||
api_base: https://my-endpoint-europe-berri-992.openai.azure.com/
|
api_base: https://my-endpoint-europe-berri-992.openai.azure.com/
|
||||||
api_key: os.environ/AZURE_EUROPE_API_KEY
|
api_key: os.environ/AZURE_EUROPE_API_KEY
|
||||||
- model_name: gpt-3.5-turbo
|
model: azure/gpt-35-turbo
|
||||||
litellm_params:
|
model_name: gpt-3.5-turbo
|
||||||
model: azure/chatgpt-v-2
|
- litellm_params:
|
||||||
api_base: https://openai-gpt-4-test-v-1.openai.azure.com/
|
api_base: https://openai-gpt-4-test-v-1.openai.azure.com/
|
||||||
api_version: "2023-05-15"
|
api_key: os.environ/AZURE_API_KEY
|
||||||
api_key: os.environ/AZURE_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault
|
api_version: '2023-05-15'
|
||||||
|
model: azure/chatgpt-v-2
|
||||||
|
model_name: gpt-3.5-turbo
|
||||||
router_settings:
|
router_settings:
|
||||||
enable_pre_call_checks: true
|
enable_pre_call_checks: true
|
||||||
|
|
||||||
litellm_settings:
|
|
||||||
json_logs: True
|
|
||||||
|
|
||||||
general_settings:
|
|
||||||
alerting: ["slack"]
|
|
||||||
alerting_args:
|
|
||||||
report_check_interval: 10
|
|
||||||
enable_jwt_auth: True
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,19 @@ import uuid, json, sys, os
|
||||||
from litellm.types.router import UpdateRouterConfig
|
from litellm.types.router import UpdateRouterConfig
|
||||||
from litellm.types.utils import ProviderField
|
from litellm.types.utils import ProviderField
|
||||||
|
|
||||||
|
AlertType = Literal[
|
||||||
|
"llm_exceptions",
|
||||||
|
"llm_too_slow",
|
||||||
|
"llm_requests_hanging",
|
||||||
|
"budget_alerts",
|
||||||
|
"db_exceptions",
|
||||||
|
"daily_reports",
|
||||||
|
"spend_reports",
|
||||||
|
"cooldown_deployment",
|
||||||
|
"new_model_added",
|
||||||
|
"outage_alerts",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def hash_token(token: str):
|
def hash_token(token: str):
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -855,17 +868,7 @@ class ConfigGeneralSettings(LiteLLMBase):
|
||||||
None,
|
None,
|
||||||
description="List of alerting integrations. Today, just slack - `alerting: ['slack']`",
|
description="List of alerting integrations. Today, just slack - `alerting: ['slack']`",
|
||||||
)
|
)
|
||||||
alert_types: Optional[
|
alert_types: Optional[List[AlertType]] = Field(
|
||||||
List[
|
|
||||||
Literal[
|
|
||||||
"llm_exceptions",
|
|
||||||
"llm_too_slow",
|
|
||||||
"llm_requests_hanging",
|
|
||||||
"budget_alerts",
|
|
||||||
"db_exceptions",
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = Field(
|
|
||||||
None,
|
None,
|
||||||
description="List of alerting types. By default it is all alerts",
|
description="List of alerting types. By default it is all alerts",
|
||||||
)
|
)
|
||||||
|
|
|
@ -484,9 +484,9 @@ async def user_api_key_auth(
|
||||||
verbose_proxy_logger.debug("is_jwt: %s", is_jwt)
|
verbose_proxy_logger.debug("is_jwt: %s", is_jwt)
|
||||||
if is_jwt:
|
if is_jwt:
|
||||||
# check if valid token
|
# check if valid token
|
||||||
valid_token = await jwt_handler.auth_jwt(token=api_key)
|
jwt_valid_token: dict = await jwt_handler.auth_jwt(token=api_key)
|
||||||
# get scopes
|
# get scopes
|
||||||
scopes = jwt_handler.get_scopes(token=valid_token)
|
scopes = jwt_handler.get_scopes(token=jwt_valid_token)
|
||||||
|
|
||||||
# check if admin
|
# check if admin
|
||||||
is_admin = jwt_handler.is_admin(scopes=scopes)
|
is_admin = jwt_handler.is_admin(scopes=scopes)
|
||||||
|
@ -509,7 +509,9 @@ async def user_api_key_auth(
|
||||||
f"Admin not allowed to access this route. Route={route}, Allowed Routes={actual_routes}"
|
f"Admin not allowed to access this route. Route={route}, Allowed Routes={actual_routes}"
|
||||||
)
|
)
|
||||||
# get team id
|
# get team id
|
||||||
team_id = jwt_handler.get_team_id(token=valid_token, default_value=None)
|
team_id = jwt_handler.get_team_id(
|
||||||
|
token=jwt_valid_token, default_value=None
|
||||||
|
)
|
||||||
|
|
||||||
if team_id is None and jwt_handler.is_required_team_id() == True:
|
if team_id is None and jwt_handler.is_required_team_id() == True:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
@ -539,7 +541,9 @@ async def user_api_key_auth(
|
||||||
)
|
)
|
||||||
|
|
||||||
# [OPTIONAL] track spend for an org id - `LiteLLM_OrganizationTable`
|
# [OPTIONAL] track spend for an org id - `LiteLLM_OrganizationTable`
|
||||||
org_id = jwt_handler.get_org_id(token=valid_token, default_value=None)
|
org_id = jwt_handler.get_org_id(
|
||||||
|
token=jwt_valid_token, default_value=None
|
||||||
|
)
|
||||||
if org_id is not None:
|
if org_id is not None:
|
||||||
_ = await get_org_object(
|
_ = await get_org_object(
|
||||||
org_id=org_id,
|
org_id=org_id,
|
||||||
|
@ -548,7 +552,9 @@ async def user_api_key_auth(
|
||||||
)
|
)
|
||||||
# [OPTIONAL] track spend against an internal employee - `LiteLLM_UserTable`
|
# [OPTIONAL] track spend against an internal employee - `LiteLLM_UserTable`
|
||||||
user_object = None
|
user_object = None
|
||||||
user_id = jwt_handler.get_user_id(token=valid_token, default_value=None)
|
user_id = jwt_handler.get_user_id(
|
||||||
|
token=jwt_valid_token, default_value=None
|
||||||
|
)
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
# get the user object
|
# get the user object
|
||||||
user_object = await get_user_object(
|
user_object = await get_user_object(
|
||||||
|
@ -561,7 +567,7 @@ async def user_api_key_auth(
|
||||||
# [OPTIONAL] track spend against an external user - `LiteLLM_EndUserTable`
|
# [OPTIONAL] track spend against an external user - `LiteLLM_EndUserTable`
|
||||||
end_user_object = None
|
end_user_object = None
|
||||||
end_user_id = jwt_handler.get_end_user_id(
|
end_user_id = jwt_handler.get_end_user_id(
|
||||||
token=valid_token, default_value=None
|
token=jwt_valid_token, default_value=None
|
||||||
)
|
)
|
||||||
if end_user_id is not None:
|
if end_user_id is not None:
|
||||||
# get the end-user object
|
# get the end-user object
|
||||||
|
@ -595,7 +601,7 @@ async def user_api_key_auth(
|
||||||
user_id=litellm_proxy_admin_name,
|
user_id=litellm_proxy_admin_name,
|
||||||
max_budget=litellm.max_budget,
|
max_budget=litellm.max_budget,
|
||||||
spend=global_proxy_spend,
|
spend=global_proxy_spend,
|
||||||
token=valid_token["token"],
|
token=jwt_valid_token["token"],
|
||||||
)
|
)
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
proxy_logging_obj.budget_alerts(
|
proxy_logging_obj.budget_alerts(
|
||||||
|
@ -693,7 +699,9 @@ async def user_api_key_auth(
|
||||||
### CHECK IF ADMIN ###
|
### CHECK IF ADMIN ###
|
||||||
# note: never string compare api keys, this is vulenerable to a time attack. Use secrets.compare_digest instead
|
# note: never string compare api keys, this is vulenerable to a time attack. Use secrets.compare_digest instead
|
||||||
## Check CACHE
|
## Check CACHE
|
||||||
valid_token = user_api_key_cache.get_cache(key=hash_token(api_key))
|
valid_token: Optional[UserAPIKeyAuth] = user_api_key_cache.get_cache(
|
||||||
|
key=hash_token(api_key)
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
valid_token is not None
|
valid_token is not None
|
||||||
and isinstance(valid_token, UserAPIKeyAuth)
|
and isinstance(valid_token, UserAPIKeyAuth)
|
||||||
|
@ -762,23 +770,19 @@ async def user_api_key_auth(
|
||||||
original_api_key = api_key # (Patch: For DynamoDB Backwards Compatibility)
|
original_api_key = api_key # (Patch: For DynamoDB Backwards Compatibility)
|
||||||
if api_key.startswith("sk-"):
|
if api_key.startswith("sk-"):
|
||||||
api_key = hash_token(token=api_key)
|
api_key = hash_token(token=api_key)
|
||||||
valid_token = user_api_key_cache.get_cache(key=api_key)
|
valid_token: Optional[UserAPIKeyAuth] = user_api_key_cache.get_cache( # type: ignore
|
||||||
|
key=api_key
|
||||||
|
)
|
||||||
if valid_token is None:
|
if valid_token is None:
|
||||||
## check db
|
## check db
|
||||||
verbose_proxy_logger.debug("api key: %s", api_key)
|
verbose_proxy_logger.debug("api key: %s", api_key)
|
||||||
if prisma_client is not None:
|
if prisma_client is not None:
|
||||||
valid_token = await prisma_client.get_data(
|
_valid_token: Optional[BaseModel] = await prisma_client.get_data(
|
||||||
token=api_key, table_name="combined_view"
|
token=api_key, table_name="combined_view"
|
||||||
)
|
)
|
||||||
elif custom_db_client is not None:
|
if _valid_token is not None:
|
||||||
try:
|
valid_token = UserAPIKeyAuth(
|
||||||
valid_token = await custom_db_client.get_data(
|
**_valid_token.model_dump(exclude_none=True)
|
||||||
key=api_key, table_name="key"
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
# (Patch: For DynamoDB Backwards Compatibility)
|
|
||||||
valid_token = await custom_db_client.get_data(
|
|
||||||
key=original_api_key, table_name="key"
|
|
||||||
)
|
)
|
||||||
verbose_proxy_logger.debug("Token from db: %s", valid_token)
|
verbose_proxy_logger.debug("Token from db: %s", valid_token)
|
||||||
elif valid_token is not None and isinstance(valid_token, UserAPIKeyAuth):
|
elif valid_token is not None and isinstance(valid_token, UserAPIKeyAuth):
|
||||||
|
@ -793,8 +797,8 @@ async def user_api_key_auth(
|
||||||
"allowed_model_region"
|
"allowed_model_region"
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id_information = None
|
user_id_information: Optional[List] = None
|
||||||
if valid_token:
|
if valid_token is not None:
|
||||||
# Got Valid Token from Cache, DB
|
# Got Valid Token from Cache, DB
|
||||||
# Run checks for
|
# Run checks for
|
||||||
# 1. If token can call model
|
# 1. If token can call model
|
||||||
|
@ -915,16 +919,13 @@ async def user_api_key_auth(
|
||||||
table_name="user",
|
table_name="user",
|
||||||
query_type="find_all",
|
query_type="find_all",
|
||||||
)
|
)
|
||||||
|
if user_id_information is not None:
|
||||||
for _id in user_id_information:
|
for _id in user_id_information:
|
||||||
await user_api_key_cache.async_set_cache(
|
await user_api_key_cache.async_set_cache(
|
||||||
key=_id["user_id"],
|
key=_id["user_id"],
|
||||||
value=_id,
|
value=_id,
|
||||||
ttl=UserAPIKeyCacheTTLEnum.user_information_cache.value,
|
ttl=UserAPIKeyCacheTTLEnum.user_information_cache.value,
|
||||||
)
|
)
|
||||||
if custom_db_client is not None:
|
|
||||||
user_id_information = await custom_db_client.get_data(
|
|
||||||
key=valid_token.user_id, table_name="user"
|
|
||||||
)
|
|
||||||
|
|
||||||
verbose_proxy_logger.debug(
|
verbose_proxy_logger.debug(
|
||||||
f"user_id_information: {user_id_information}"
|
f"user_id_information: {user_id_information}"
|
||||||
|
@ -1067,12 +1068,13 @@ async def user_api_key_auth(
|
||||||
# collect information for alerting #
|
# collect information for alerting #
|
||||||
####################################
|
####################################
|
||||||
|
|
||||||
user_email = None
|
user_email: Optional[str] = None
|
||||||
# Check if the token has any user id information
|
# Check if the token has any user id information
|
||||||
if user_id_information is not None:
|
if user_id_information is not None:
|
||||||
if isinstance(user_id_information, list):
|
specific_user_id_information = user_id_information[0]
|
||||||
user_id_information = user_id_information[0]
|
_user_email = specific_user_id_information.get("user_email", None)
|
||||||
user_email = user_id_information.get("user_email", None)
|
if _user_email is not None:
|
||||||
|
user_email = str(_user_email)
|
||||||
|
|
||||||
call_info = CallInfo(
|
call_info = CallInfo(
|
||||||
token=valid_token.token,
|
token=valid_token.token,
|
||||||
|
@ -1229,24 +1231,11 @@ async def user_api_key_auth(
|
||||||
value=valid_token,
|
value=valid_token,
|
||||||
ttl=UserAPIKeyCacheTTLEnum.key_information_cache.value,
|
ttl=UserAPIKeyCacheTTLEnum.key_information_cache.value,
|
||||||
)
|
)
|
||||||
valid_token_dict = _get_pydantic_json_dict(valid_token)
|
valid_token_dict = valid_token.model_dump(exclude_none=True)
|
||||||
valid_token_dict.pop("token", None)
|
valid_token_dict.pop("token", None)
|
||||||
|
|
||||||
if _end_user_object is not None:
|
if _end_user_object is not None:
|
||||||
valid_token_dict.update(end_user_params)
|
valid_token_dict.update(end_user_params)
|
||||||
"""
|
|
||||||
asyncio create task to update the user api key cache with the user db table as well
|
|
||||||
|
|
||||||
This makes the user row data accessible to pre-api call hooks.
|
|
||||||
"""
|
|
||||||
if custom_db_client is not None:
|
|
||||||
asyncio.create_task(
|
|
||||||
_cache_user_row(
|
|
||||||
user_id=valid_token.user_id,
|
|
||||||
cache=user_api_key_cache,
|
|
||||||
db=custom_db_client,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not _is_user_proxy_admin(user_id_information): # if non-admin
|
if not _is_user_proxy_admin(user_id_information): # if non-admin
|
||||||
if route in LiteLLMRoutes.openai_routes.value:
|
if route in LiteLLMRoutes.openai_routes.value:
|
||||||
|
@ -3026,7 +3015,7 @@ class ProxyConfig:
|
||||||
general_settings["alert_types"] = _general_settings["alert_types"]
|
general_settings["alert_types"] = _general_settings["alert_types"]
|
||||||
proxy_logging_obj.alert_types = general_settings["alert_types"]
|
proxy_logging_obj.alert_types = general_settings["alert_types"]
|
||||||
proxy_logging_obj.slack_alerting_instance.update_values(
|
proxy_logging_obj.slack_alerting_instance.update_values(
|
||||||
alert_types=general_settings["alert_types"]
|
alert_types=general_settings["alert_types"], llm_router=llm_router
|
||||||
)
|
)
|
||||||
|
|
||||||
if "alert_to_webhook_url" in _general_settings:
|
if "alert_to_webhook_url" in _general_settings:
|
||||||
|
@ -3034,7 +3023,8 @@ class ProxyConfig:
|
||||||
"alert_to_webhook_url"
|
"alert_to_webhook_url"
|
||||||
]
|
]
|
||||||
proxy_logging_obj.slack_alerting_instance.update_values(
|
proxy_logging_obj.slack_alerting_instance.update_values(
|
||||||
alert_to_webhook_url=general_settings["alert_to_webhook_url"]
|
alert_to_webhook_url=general_settings["alert_to_webhook_url"],
|
||||||
|
llm_router=llm_router,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _update_general_settings(self, db_general_settings: Optional[Json]):
|
async def _update_general_settings(self, db_general_settings: Optional[Json]):
|
||||||
|
@ -3602,6 +3592,9 @@ async def startup_event():
|
||||||
## Error Tracking ##
|
## Error Tracking ##
|
||||||
error_tracking()
|
error_tracking()
|
||||||
|
|
||||||
|
## UPDATE SLACK ALERTING ##
|
||||||
|
proxy_logging_obj.slack_alerting_instance.update_values(llm_router=llm_router)
|
||||||
|
|
||||||
db_writer_client = HTTPHandler()
|
db_writer_client = HTTPHandler()
|
||||||
|
|
||||||
proxy_logging_obj._init_litellm_callbacks() # INITIALIZE LITELLM CALLBACKS ON SERVER STARTUP <- do this to catch any logging errors on startup, not when calls are being made
|
proxy_logging_obj._init_litellm_callbacks() # INITIALIZE LITELLM CALLBACKS ON SERVER STARTUP <- do this to catch any logging errors on startup, not when calls are being made
|
||||||
|
@ -9648,7 +9641,7 @@ async def google_login(request: Request):
|
||||||
)
|
)
|
||||||
|
|
||||||
####### Detect DB + MASTER KEY in .env #######
|
####### Detect DB + MASTER KEY in .env #######
|
||||||
if prisma_client is None and master_key is None:
|
if prisma_client is None or master_key is None:
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
|
|
||||||
return HTMLResponse(content=missing_keys_html_form, status_code=200)
|
return HTMLResponse(content=missing_keys_html_form, status_code=200)
|
||||||
|
@ -10775,6 +10768,9 @@ async def get_config():
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if llm_router is None:
|
||||||
|
_router_settings = {}
|
||||||
|
else:
|
||||||
_router_settings = llm_router.get_settings()
|
_router_settings = llm_router.get_settings()
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
|
@ -10965,6 +10961,10 @@ async def health_services_endpoint(
|
||||||
test_message = f"Budget Alert test alert"
|
test_message = f"Budget Alert test alert"
|
||||||
elif alert_type == "db_exceptions":
|
elif alert_type == "db_exceptions":
|
||||||
test_message = f"DB Exception test alert"
|
test_message = f"DB Exception test alert"
|
||||||
|
elif alert_type == "outage_alerts":
|
||||||
|
test_message = f"Outage Alert Exception test alert"
|
||||||
|
elif alert_type == "daily_reports":
|
||||||
|
test_message = f"Daily Reports test alert"
|
||||||
|
|
||||||
await proxy_logging_obj.alerting_handler(
|
await proxy_logging_obj.alerting_handler(
|
||||||
message=test_message, level="Low", alert_type=alert_type
|
message=test_message, level="Low", alert_type=alert_type
|
||||||
|
|
|
@ -13,6 +13,7 @@ from litellm.proxy._types import (
|
||||||
Member,
|
Member,
|
||||||
CallInfo,
|
CallInfo,
|
||||||
WebhookEvent,
|
WebhookEvent,
|
||||||
|
AlertType,
|
||||||
)
|
)
|
||||||
from litellm.caching import DualCache, RedisCache
|
from litellm.caching import DualCache, RedisCache
|
||||||
from litellm.router import Deployment, ModelInfo, LiteLLM_Params
|
from litellm.router import Deployment, ModelInfo, LiteLLM_Params
|
||||||
|
@ -79,19 +80,7 @@ class ProxyLogging:
|
||||||
self.cache_control_check = _PROXY_CacheControlCheck()
|
self.cache_control_check = _PROXY_CacheControlCheck()
|
||||||
self.alerting: Optional[List] = None
|
self.alerting: Optional[List] = None
|
||||||
self.alerting_threshold: float = 300 # default to 5 min. threshold
|
self.alerting_threshold: float = 300 # default to 5 min. threshold
|
||||||
self.alert_types: List[
|
self.alert_types: List[AlertType] = [
|
||||||
Literal[
|
|
||||||
"llm_exceptions",
|
|
||||||
"llm_too_slow",
|
|
||||||
"llm_requests_hanging",
|
|
||||||
"budget_alerts",
|
|
||||||
"db_exceptions",
|
|
||||||
"daily_reports",
|
|
||||||
"spend_reports",
|
|
||||||
"cooldown_deployment",
|
|
||||||
"new_model_added",
|
|
||||||
]
|
|
||||||
] = [
|
|
||||||
"llm_exceptions",
|
"llm_exceptions",
|
||||||
"llm_too_slow",
|
"llm_too_slow",
|
||||||
"llm_requests_hanging",
|
"llm_requests_hanging",
|
||||||
|
@ -101,6 +90,7 @@ class ProxyLogging:
|
||||||
"spend_reports",
|
"spend_reports",
|
||||||
"cooldown_deployment",
|
"cooldown_deployment",
|
||||||
"new_model_added",
|
"new_model_added",
|
||||||
|
"outage_alerts",
|
||||||
]
|
]
|
||||||
self.slack_alerting_instance = SlackAlerting(
|
self.slack_alerting_instance = SlackAlerting(
|
||||||
alerting_threshold=self.alerting_threshold,
|
alerting_threshold=self.alerting_threshold,
|
||||||
|
@ -114,21 +104,7 @@ class ProxyLogging:
|
||||||
alerting: Optional[List],
|
alerting: Optional[List],
|
||||||
alerting_threshold: Optional[float],
|
alerting_threshold: Optional[float],
|
||||||
redis_cache: Optional[RedisCache],
|
redis_cache: Optional[RedisCache],
|
||||||
alert_types: Optional[
|
alert_types: Optional[List[AlertType]] = None,
|
||||||
List[
|
|
||||||
Literal[
|
|
||||||
"llm_exceptions",
|
|
||||||
"llm_too_slow",
|
|
||||||
"llm_requests_hanging",
|
|
||||||
"budget_alerts",
|
|
||||||
"db_exceptions",
|
|
||||||
"daily_reports",
|
|
||||||
"spend_reports",
|
|
||||||
"cooldown_deployment",
|
|
||||||
"new_model_added",
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = None,
|
|
||||||
alerting_args: Optional[dict] = None,
|
alerting_args: Optional[dict] = None,
|
||||||
):
|
):
|
||||||
self.alerting = alerting
|
self.alerting = alerting
|
||||||
|
@ -2592,13 +2568,13 @@ def _is_valid_team_configs(team_id=None, team_config=None, request_data=None):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def _is_user_proxy_admin(user_id_information=None):
|
def _is_user_proxy_admin(user_id_information: Optional[list]):
|
||||||
if (
|
if user_id_information is None:
|
||||||
user_id_information == None
|
|
||||||
or len(user_id_information) == 0
|
|
||||||
or user_id_information[0] == None
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if len(user_id_information) == 0 or user_id_information[0] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
_user = user_id_information[0]
|
_user = user_id_information[0]
|
||||||
if (
|
if (
|
||||||
_user.get("user_role", None) is not None
|
_user.get("user_role", None) is not None
|
||||||
|
|
|
@ -3324,7 +3324,7 @@ class Router:
|
||||||
invalid_model_indices.append(idx)
|
invalid_model_indices.append(idx)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
## INVALID PARAMS ## -> catch 'gpt-3.5-turbo-16k' not supporting 'response_object' param
|
## INVALID PARAMS ## -> catch 'gpt-3.5-turbo-16k' not supporting 'response_format' param
|
||||||
if request_kwargs is not None and litellm.drop_params == False:
|
if request_kwargs is not None and litellm.drop_params == False:
|
||||||
# get supported params
|
# get supported params
|
||||||
model, custom_llm_provider, _, _ = litellm.get_llm_provider(
|
model, custom_llm_provider, _, _ = litellm.get_llm_provider(
|
||||||
|
@ -3342,10 +3342,10 @@ class Router:
|
||||||
non_default_params = litellm.utils.get_non_default_params(
|
non_default_params = litellm.utils.get_non_default_params(
|
||||||
passed_params=request_kwargs
|
passed_params=request_kwargs
|
||||||
)
|
)
|
||||||
special_params = ["response_object"]
|
special_params = ["response_format"]
|
||||||
# check if all params are supported
|
# check if all params are supported
|
||||||
for k, v in non_default_params.items():
|
for k, v in non_default_params.items():
|
||||||
if k not in supported_openai_params:
|
if k not in supported_openai_params and k in special_params:
|
||||||
# if not -> invalid model
|
# if not -> invalid model
|
||||||
verbose_router_logger.debug(
|
verbose_router_logger.debug(
|
||||||
f"INVALID MODEL INDEX @ REQUEST KWARG FILTERING, k={k}"
|
f"INVALID MODEL INDEX @ REQUEST KWARG FILTERING, k={k}"
|
||||||
|
@ -3876,13 +3876,13 @@ class Router:
|
||||||
_api_base = litellm.get_api_base(
|
_api_base = litellm.get_api_base(
|
||||||
model=_model_name, optional_params=temp_litellm_params
|
model=_model_name, optional_params=temp_litellm_params
|
||||||
)
|
)
|
||||||
asyncio.create_task(
|
# asyncio.create_task(
|
||||||
proxy_logging_obj.slack_alerting_instance.send_alert(
|
# proxy_logging_obj.slack_alerting_instance.send_alert(
|
||||||
message=f"Router: Cooling down Deployment:\nModel Name: `{_model_name}`\nAPI Base: `{_api_base}`\nCooldown Time: `{cooldown_time} seconds`\nException Status Code: `{str(exception_status)}`\n\nChange 'cooldown_time' + 'allowed_fails' under 'Router Settings' on proxy UI, or via config - https://docs.litellm.ai/docs/proxy/reliability#fallbacks--retries--timeouts--cooldowns",
|
# message=f"Router: Cooling down Deployment:\nModel Name: `{_model_name}`\nAPI Base: `{_api_base}`\nCooldown Time: `{cooldown_time} seconds`\nException Status Code: `{str(exception_status)}`\n\nChange 'cooldown_time' + 'allowed_fails' under 'Router Settings' on proxy UI, or via config - https://docs.litellm.ai/docs/proxy/reliability#fallbacks--retries--timeouts--cooldowns",
|
||||||
alert_type="cooldown_deployment",
|
# alert_type="cooldown_deployment",
|
||||||
level="Low",
|
# level="Low",
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
# What is this?
|
# What is this?
|
||||||
## Tests slack alerting on proxy logging object
|
## Tests slack alerting on proxy logging object
|
||||||
|
|
||||||
import sys, json, uuid, random
|
import sys, json, uuid, random, httpx
|
||||||
import os
|
import os
|
||||||
import io, asyncio
|
import io, asyncio
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
# import logging
|
# import logging
|
||||||
# logging.basicConfig(level=logging.DEBUG)
|
# logging.basicConfig(level=logging.DEBUG)
|
||||||
|
@ -23,6 +24,7 @@ from unittest.mock import AsyncMock
|
||||||
import pytest
|
import pytest
|
||||||
from litellm.router import AlertingConfig, Router
|
from litellm.router import AlertingConfig, Router
|
||||||
from litellm.proxy._types import CallInfo
|
from litellm.proxy._types import CallInfo
|
||||||
|
from openai import APIError
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -495,3 +497,109 @@ async def test_webhook_alerting(alerting_type):
|
||||||
user_info=user_info,
|
user_info=user_info,
|
||||||
)
|
)
|
||||||
mock_send_alert.assert_awaited_once()
|
mock_send_alert.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"model, api_base, llm_provider, vertex_project, vertex_location",
|
||||||
|
[
|
||||||
|
("gpt-3.5-turbo", None, "openai", None, None),
|
||||||
|
(
|
||||||
|
"azure/gpt-3.5-turbo",
|
||||||
|
"https://openai-gpt-4-test-v-1.openai.azure.com",
|
||||||
|
"azure",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
("gemini-pro", None, "vertex_ai", "hardy-device-38811", "us-central1"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("error_code", [500, 408, 400])
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_outage_alerting_called(
|
||||||
|
model, api_base, llm_provider, vertex_project, vertex_location, error_code
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If call fails, outage alert is called
|
||||||
|
|
||||||
|
If multiple calls fail, outage alert is sent
|
||||||
|
"""
|
||||||
|
slack_alerting = SlackAlerting(alerting=["webhook"])
|
||||||
|
|
||||||
|
litellm.callbacks = [slack_alerting]
|
||||||
|
|
||||||
|
error_to_raise: Optional[APIError] = None
|
||||||
|
|
||||||
|
if error_code == 400:
|
||||||
|
print("RAISING 400 ERROR CODE")
|
||||||
|
error_to_raise = litellm.BadRequestError(
|
||||||
|
message="this is a bad request",
|
||||||
|
model=model,
|
||||||
|
llm_provider=llm_provider,
|
||||||
|
)
|
||||||
|
elif error_code == 408:
|
||||||
|
print("RAISING 408 ERROR CODE")
|
||||||
|
error_to_raise = litellm.Timeout(
|
||||||
|
message="A timeout occurred", model=model, llm_provider=llm_provider
|
||||||
|
)
|
||||||
|
elif error_code == 500:
|
||||||
|
print("RAISING 500 ERROR CODE")
|
||||||
|
error_to_raise = litellm.ServiceUnavailableError(
|
||||||
|
message="API is unavailable",
|
||||||
|
model=model,
|
||||||
|
llm_provider=llm_provider,
|
||||||
|
response=httpx.Response(
|
||||||
|
status_code=503,
|
||||||
|
request=httpx.Request(
|
||||||
|
method="completion",
|
||||||
|
url="https://github.com/BerriAI/litellm",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
router = Router(
|
||||||
|
model_list=[
|
||||||
|
{
|
||||||
|
"model_name": model,
|
||||||
|
"litellm_params": {
|
||||||
|
"model": model,
|
||||||
|
"api_key": os.getenv("AZURE_API_KEY"),
|
||||||
|
"api_base": api_base,
|
||||||
|
"vertex_location": vertex_location,
|
||||||
|
"vertex_project": vertex_project,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
num_retries=0,
|
||||||
|
allowed_fails=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
slack_alerting.update_values(llm_router=router)
|
||||||
|
with patch.object(
|
||||||
|
slack_alerting, "outage_alerts", new=AsyncMock()
|
||||||
|
) as mock_send_alert:
|
||||||
|
try:
|
||||||
|
await router.acompletion(
|
||||||
|
model=model,
|
||||||
|
messages=[{"role": "user", "content": "Hey!"}],
|
||||||
|
mock_response=error_to_raise,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_send_alert.assert_called_once()
|
||||||
|
|
||||||
|
with patch.object(slack_alerting, "send_alert", new=AsyncMock()) as mock_send_alert:
|
||||||
|
for _ in range(3):
|
||||||
|
try:
|
||||||
|
await router.acompletion(
|
||||||
|
model=model,
|
||||||
|
messages=[{"role": "user", "content": "Hey!"}],
|
||||||
|
mock_response=error_to_raise,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
if error_code == 500 or error_code == 408:
|
||||||
|
mock_send_alert.assert_called_once()
|
||||||
|
else:
|
||||||
|
mock_send_alert.assert_not_called()
|
||||||
|
|
|
@ -1,49 +1,35 @@
|
||||||
# Commented out for now - since traceloop break ci/cd
|
import sys
|
||||||
# import sys
|
import os
|
||||||
# import os
|
import time
|
||||||
# import io, asyncio
|
import pytest
|
||||||
|
import litellm
|
||||||
|
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
||||||
|
from traceloop.sdk import Traceloop
|
||||||
|
|
||||||
# sys.path.insert(0, os.path.abspath('../..'))
|
sys.path.insert(0, os.path.abspath("../.."))
|
||||||
|
|
||||||
# from litellm import completion
|
|
||||||
# import litellm
|
|
||||||
# litellm.num_retries = 3
|
|
||||||
# litellm.success_callback = [""]
|
|
||||||
# import time
|
|
||||||
# import pytest
|
|
||||||
# from traceloop.sdk import Traceloop
|
|
||||||
# Traceloop.init(app_name="test-litellm", disable_batch=True)
|
|
||||||
|
|
||||||
|
|
||||||
# def test_traceloop_logging():
|
@pytest.fixture()
|
||||||
# try:
|
def exporter():
|
||||||
# litellm.set_verbose = True
|
exporter = InMemorySpanExporter()
|
||||||
# response = litellm.completion(
|
Traceloop.init(
|
||||||
# model="gpt-3.5-turbo",
|
app_name="test_litellm",
|
||||||
# messages=[{"role": "user", "content":"This is a test"}],
|
disable_batch=True,
|
||||||
# max_tokens=1000,
|
exporter=exporter,
|
||||||
# temperature=0.7,
|
)
|
||||||
# timeout=5,
|
litellm.success_callback = ["traceloop"]
|
||||||
# )
|
litellm.set_verbose = True
|
||||||
# print(f"response: {response}")
|
|
||||||
# except Exception as e:
|
return exporter
|
||||||
# pytest.fail(f"An exception occurred - {e}")
|
|
||||||
# # test_traceloop_logging()
|
|
||||||
|
|
||||||
|
|
||||||
# # def test_traceloop_logging_async():
|
@pytest.mark.parametrize("model", ["claude-instant-1.2", "gpt-3.5-turbo"])
|
||||||
# # try:
|
def test_traceloop_logging(exporter, model):
|
||||||
# # litellm.set_verbose = True
|
|
||||||
# # async def test_acompletion():
|
litellm.completion(
|
||||||
# # return await litellm.acompletion(
|
model=model,
|
||||||
# # model="gpt-3.5-turbo",
|
messages=[{"role": "user", "content": "This is a test"}],
|
||||||
# # messages=[{"role": "user", "content":"This is a test"}],
|
max_tokens=1000,
|
||||||
# # max_tokens=1000,
|
temperature=0.7,
|
||||||
# # temperature=0.7,
|
timeout=5,
|
||||||
# # timeout=5,
|
)
|
||||||
# # )
|
|
||||||
# # response = asyncio.run(test_acompletion())
|
|
||||||
# # print(f"response: {response}")
|
|
||||||
# # except Exception as e:
|
|
||||||
# # pytest.fail(f"An exception occurred - {e}")
|
|
||||||
# # test_traceloop_logging_async()
|
|
||||||
|
|
|
@ -2027,6 +2027,7 @@ class Logging:
|
||||||
response_obj=result,
|
response_obj=result,
|
||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
end_time=end_time,
|
end_time=end_time,
|
||||||
|
user_id=kwargs.get("user", None),
|
||||||
print_verbose=print_verbose,
|
print_verbose=print_verbose,
|
||||||
)
|
)
|
||||||
if callback == "s3":
|
if callback == "s3":
|
||||||
|
@ -2598,6 +2599,17 @@ class Logging:
|
||||||
level="ERROR",
|
level="ERROR",
|
||||||
kwargs=self.model_call_details,
|
kwargs=self.model_call_details,
|
||||||
)
|
)
|
||||||
|
if callback == "traceloop":
|
||||||
|
traceloopLogger.log_event(
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
response_obj=None,
|
||||||
|
user_id=kwargs.get("user", None),
|
||||||
|
print_verbose=print_verbose,
|
||||||
|
status_message=str(exception),
|
||||||
|
level="ERROR",
|
||||||
|
kwargs=self.model_call_details,
|
||||||
|
)
|
||||||
if callback == "prometheus":
|
if callback == "prometheus":
|
||||||
global prometheusLogger
|
global prometheusLogger
|
||||||
verbose_logger.debug("reaches prometheus for success logging!")
|
verbose_logger.debug("reaches prometheus for success logging!")
|
||||||
|
@ -6286,7 +6298,9 @@ def get_model_region(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_api_base(model: str, optional_params: dict) -> Optional[str]:
|
def get_api_base(
|
||||||
|
model: str, optional_params: Union[dict, LiteLLM_Params]
|
||||||
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Returns the api base used for calling the model.
|
Returns the api base used for calling the model.
|
||||||
|
|
||||||
|
@ -6306,7 +6320,9 @@ def get_api_base(model: str, optional_params: dict) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if "model" in optional_params:
|
if isinstance(optional_params, LiteLLM_Params):
|
||||||
|
_optional_params = optional_params
|
||||||
|
elif "model" in optional_params:
|
||||||
_optional_params = LiteLLM_Params(**optional_params)
|
_optional_params = LiteLLM_Params(**optional_params)
|
||||||
else: # prevent needing to copy and pop the dict
|
else: # prevent needing to copy and pop the dict
|
||||||
_optional_params = LiteLLM_Params(
|
_optional_params = LiteLLM_Params(
|
||||||
|
@ -6699,6 +6715,8 @@ def get_llm_provider(
|
||||||
Returns the provider for a given model name - e.g. 'azure/chatgpt-v-2' -> 'azure'
|
Returns the provider for a given model name - e.g. 'azure/chatgpt-v-2' -> 'azure'
|
||||||
|
|
||||||
For router -> Can also give the whole litellm param dict -> this function will extract the relevant details
|
For router -> Can also give the whole litellm param dict -> this function will extract the relevant details
|
||||||
|
|
||||||
|
Raises Error - if unable to map model to a provider
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
## IF LITELLM PARAMS GIVEN ##
|
## IF LITELLM PARAMS GIVEN ##
|
||||||
|
@ -8632,7 +8650,16 @@ def exception_type(
|
||||||
)
|
)
|
||||||
elif hasattr(original_exception, "status_code"):
|
elif hasattr(original_exception, "status_code"):
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
if original_exception.status_code == 401:
|
if original_exception.status_code == 400:
|
||||||
|
exception_mapping_worked = True
|
||||||
|
raise BadRequestError(
|
||||||
|
message=f"{exception_provider} - {message}",
|
||||||
|
llm_provider=custom_llm_provider,
|
||||||
|
model=model,
|
||||||
|
response=original_exception.response,
|
||||||
|
litellm_debug_info=extra_information,
|
||||||
|
)
|
||||||
|
elif original_exception.status_code == 401:
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
raise AuthenticationError(
|
raise AuthenticationError(
|
||||||
message=f"{exception_provider} - {message}",
|
message=f"{exception_provider} - {message}",
|
||||||
|
@ -9145,6 +9172,7 @@ def exception_type(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if hasattr(original_exception, "status_code"):
|
if hasattr(original_exception, "status_code"):
|
||||||
if original_exception.status_code == 400:
|
if original_exception.status_code == 400:
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
|
@ -9825,7 +9853,16 @@ def exception_type(
|
||||||
)
|
)
|
||||||
elif hasattr(original_exception, "status_code"):
|
elif hasattr(original_exception, "status_code"):
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
if original_exception.status_code == 401:
|
if original_exception.status_code == 400:
|
||||||
|
exception_mapping_worked = True
|
||||||
|
raise BadRequestError(
|
||||||
|
message=f"AzureException - {original_exception.message}",
|
||||||
|
llm_provider="azure",
|
||||||
|
model=model,
|
||||||
|
litellm_debug_info=extra_information,
|
||||||
|
response=original_exception.response,
|
||||||
|
)
|
||||||
|
elif original_exception.status_code == 401:
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
raise AuthenticationError(
|
raise AuthenticationError(
|
||||||
message=f"AzureException - {original_exception.message}",
|
message=f"AzureException - {original_exception.message}",
|
||||||
|
@ -9842,7 +9879,7 @@ def exception_type(
|
||||||
litellm_debug_info=extra_information,
|
litellm_debug_info=extra_information,
|
||||||
llm_provider="azure",
|
llm_provider="azure",
|
||||||
)
|
)
|
||||||
if original_exception.status_code == 422:
|
elif original_exception.status_code == 422:
|
||||||
exception_mapping_worked = True
|
exception_mapping_worked = True
|
||||||
raise BadRequestError(
|
raise BadRequestError(
|
||||||
message=f"AzureException - {original_exception.message}",
|
message=f"AzureException - {original_exception.message}",
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-f7340db8b64cd999.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-f960ab1e6d32b002.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-04708d7d4a17c1ee.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>LiteLLM Dashboard</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-f7340db8b64cd999.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/103fe7af2014a1c2.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[94430,[\"936\",\"static/chunks/2f6dbc85-052c4579f80d66ae.js\",\"507\",\"static/chunks/507-0aee992ad94e4137.js\",\"931\",\"static/chunks/app/page-9547f131c3870082.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/103fe7af2014a1c2.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"9Kn8POydvrC2EQ8cCUuvp\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_12bbc4\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"LiteLLM Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>
|
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-3cc604e175425ddd.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-f960ab1e6d32b002.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-04708d7d4a17c1ee.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>LiteLLM Dashboard</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-3cc604e175425ddd.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/9e367ab966b14e29.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[94430,[\"936\",\"static/chunks/2f6dbc85-052c4579f80d66ae.js\",\"507\",\"static/chunks/507-0aee992ad94e4137.js\",\"931\",\"static/chunks/app/page-7219129f052f09c7.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/9e367ab966b14e29.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"x4G9dq-RO3w0GR_CKU40g\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_12bbc4\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"LiteLLM Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>
|
|
@ -1,7 +1,7 @@
|
||||||
2:I[77831,[],""]
|
2:I[77831,[],""]
|
||||||
3:I[94430,["936","static/chunks/2f6dbc85-052c4579f80d66ae.js","507","static/chunks/507-0aee992ad94e4137.js","931","static/chunks/app/page-9547f131c3870082.js"],""]
|
3:I[94430,["936","static/chunks/2f6dbc85-052c4579f80d66ae.js","507","static/chunks/507-0aee992ad94e4137.js","931","static/chunks/app/page-7219129f052f09c7.js"],""]
|
||||||
4:I[5613,[],""]
|
4:I[5613,[],""]
|
||||||
5:I[31778,[],""]
|
5:I[31778,[],""]
|
||||||
0:["9Kn8POydvrC2EQ8cCUuvp",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_12bbc4","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/103fe7af2014a1c2.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
|
0:["x4G9dq-RO3w0GR_CKU40g",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_12bbc4","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/9e367ab966b14e29.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
|
||||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
|
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
|
||||||
1:null
|
1:null
|
||||||
|
|
|
@ -24,7 +24,11 @@ import {
|
||||||
Tab,
|
Tab,
|
||||||
Callout,
|
Callout,
|
||||||
} from "@tremor/react";
|
} from "@tremor/react";
|
||||||
import { getCallbacksCall, setCallbacksCall, serviceHealthCheck } from "./networking";
|
import {
|
||||||
|
getCallbacksCall,
|
||||||
|
setCallbacksCall,
|
||||||
|
serviceHealthCheck,
|
||||||
|
} from "./networking";
|
||||||
import { Modal, Form, Input, Select, Button as Button2, message } from "antd";
|
import { Modal, Form, Input, Select, Button as Button2, message } from "antd";
|
||||||
import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider";
|
import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider";
|
||||||
|
|
||||||
|
@ -35,67 +39,69 @@ interface SettingsPageProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AlertingVariables {
|
interface AlertingVariables {
|
||||||
SLACK_WEBHOOK_URL: string | null,
|
SLACK_WEBHOOK_URL: string | null;
|
||||||
LANGFUSE_PUBLIC_KEY: string | null,
|
LANGFUSE_PUBLIC_KEY: string | null;
|
||||||
LANGFUSE_SECRET_KEY: string | null,
|
LANGFUSE_SECRET_KEY: string | null;
|
||||||
LANGFUSE_HOST: string | null
|
LANGFUSE_HOST: string | null;
|
||||||
OPENMETER_API_KEY: string | null
|
OPENMETER_API_KEY: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AlertingObject {
|
interface AlertingObject {
|
||||||
name: string,
|
name: string;
|
||||||
variables: AlertingVariables
|
variables: AlertingVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLoggingObject: AlertingObject[] = [
|
const defaultLoggingObject: AlertingObject[] = [
|
||||||
{
|
{
|
||||||
"name": "slack",
|
name: "slack",
|
||||||
"variables": {
|
variables: {
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": null,
|
LANGFUSE_PUBLIC_KEY: null,
|
||||||
"LANGFUSE_SECRET_KEY": null,
|
LANGFUSE_SECRET_KEY: null,
|
||||||
"OPENMETER_API_KEY": null,
|
OPENMETER_API_KEY: null,
|
||||||
"SLACK_WEBHOOK_URL": null
|
SLACK_WEBHOOK_URL: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "langfuse",
|
name: "langfuse",
|
||||||
"variables": {
|
variables: {
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": null,
|
LANGFUSE_PUBLIC_KEY: null,
|
||||||
"LANGFUSE_SECRET_KEY": null,
|
LANGFUSE_SECRET_KEY: null,
|
||||||
"OPENMETER_API_KEY": null,
|
OPENMETER_API_KEY: null,
|
||||||
"SLACK_WEBHOOK_URL": null
|
SLACK_WEBHOOK_URL: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "openmeter",
|
name: "openmeter",
|
||||||
"variables": {
|
variables: {
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": null,
|
LANGFUSE_PUBLIC_KEY: null,
|
||||||
"LANGFUSE_SECRET_KEY": null,
|
LANGFUSE_SECRET_KEY: null,
|
||||||
"OPENMETER_API_KEY": null,
|
OPENMETER_API_KEY: null,
|
||||||
"SLACK_WEBHOOK_URL": null
|
SLACK_WEBHOOK_URL: null,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const Settings: React.FC<SettingsPageProps> = ({
|
const Settings: React.FC<SettingsPageProps> = ({
|
||||||
accessToken,
|
accessToken,
|
||||||
userRole,
|
userRole,
|
||||||
userID,
|
userID,
|
||||||
}) => {
|
}) => {
|
||||||
const [callbacks, setCallbacks] = useState<AlertingObject[]>(defaultLoggingObject);
|
const [callbacks, setCallbacks] =
|
||||||
|
useState<AlertingObject[]>(defaultLoggingObject);
|
||||||
const [alerts, setAlerts] = useState<any[]>([]);
|
const [alerts, setAlerts] = useState<any[]>([]);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [selectedCallback, setSelectedCallback] = useState<string | null>(null);
|
const [selectedCallback, setSelectedCallback] = useState<string | null>(null);
|
||||||
const [selectedAlertValues, setSelectedAlertValues] = useState([]);
|
const [selectedAlertValues, setSelectedAlertValues] = useState([]);
|
||||||
const [catchAllWebhookURL, setCatchAllWebhookURL] = useState<string>("");
|
const [catchAllWebhookURL, setCatchAllWebhookURL] = useState<string>("");
|
||||||
const [alertToWebhooks, setAlertToWebhooks] = useState<Record<string, string>>({});
|
const [alertToWebhooks, setAlertToWebhooks] = useState<
|
||||||
|
Record<string, string>
|
||||||
|
>({});
|
||||||
const [activeAlerts, setActiveAlerts] = useState<string[]>([]);
|
const [activeAlerts, setActiveAlerts] = useState<string[]>([]);
|
||||||
|
|
||||||
|
|
||||||
const handleSwitchChange = (alertName: string) => {
|
const handleSwitchChange = (alertName: string) => {
|
||||||
if (activeAlerts.includes(alertName)) {
|
if (activeAlerts.includes(alertName)) {
|
||||||
setActiveAlerts(activeAlerts.filter((alert) => alert !== alertName));
|
setActiveAlerts(activeAlerts.filter((alert) => alert !== alertName));
|
||||||
|
@ -104,13 +110,14 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alerts_to_UI_NAME: Record<string, string> = {
|
const alerts_to_UI_NAME: Record<string, string> = {
|
||||||
"llm_exceptions": "LLM Exceptions",
|
llm_exceptions: "LLM Exceptions",
|
||||||
"llm_too_slow": "LLM Responses Too Slow",
|
llm_too_slow: "LLM Responses Too Slow",
|
||||||
"llm_requests_hanging": "LLM Requests Hanging",
|
llm_requests_hanging: "LLM Requests Hanging",
|
||||||
"budget_alerts": "Budget Alerts (API Keys, Users)",
|
budget_alerts: "Budget Alerts (API Keys, Users)",
|
||||||
"db_exceptions": "Database Exceptions (Read/Write)",
|
db_exceptions: "Database Exceptions (Read/Write)",
|
||||||
"daily_reports": "Weekly/Monthly Spend Reports",
|
daily_reports: "Weekly/Monthly Spend Reports",
|
||||||
}
|
outage_alerts: "Outage Alerts",
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accessToken || !userRole || !userID) {
|
if (!accessToken || !userRole || !userID) {
|
||||||
|
@ -121,15 +128,20 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
let updatedCallbacks: any[] = defaultLoggingObject;
|
let updatedCallbacks: any[] = defaultLoggingObject;
|
||||||
|
|
||||||
updatedCallbacks = updatedCallbacks.map((item: any) => {
|
updatedCallbacks = updatedCallbacks.map((item: any) => {
|
||||||
const callback = data.callbacks.find((cb: any) => cb.name === item.name);
|
const callback = data.callbacks.find(
|
||||||
|
(cb: any) => cb.name === item.name
|
||||||
|
);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
return { ...item, variables: { ...item.variables, ...callback.variables } };
|
return {
|
||||||
|
...item,
|
||||||
|
variables: { ...item.variables, ...callback.variables },
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setCallbacks(updatedCallbacks)
|
setCallbacks(updatedCallbacks);
|
||||||
// setCallbacks(callbacks_data);
|
// setCallbacks(callbacks_data);
|
||||||
|
|
||||||
let alerts_data = data.alerts;
|
let alerts_data = data.alerts;
|
||||||
|
@ -145,7 +157,6 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
setActiveAlerts(active_alerts);
|
setActiveAlerts(active_alerts);
|
||||||
setCatchAllWebhookURL(catch_all_webhook);
|
setCatchAllWebhookURL(catch_all_webhook);
|
||||||
setAlertToWebhooks(_alert_info.alerts_to_webhook);
|
setAlertToWebhooks(_alert_info.alerts_to_webhook);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,10 +164,9 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
});
|
});
|
||||||
}, [accessToken, userRole, userID]);
|
}, [accessToken, userRole, userID]);
|
||||||
|
|
||||||
|
|
||||||
const isAlertOn = (alertName: string) => {
|
const isAlertOn = (alertName: string) => {
|
||||||
return activeAlerts && activeAlerts.includes(alertName);
|
return activeAlerts && activeAlerts.includes(alertName);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleAddCallback = () => {
|
const handleAddCallback = () => {
|
||||||
console.log("Add callback clicked");
|
console.log("Add callback clicked");
|
||||||
|
@ -172,7 +182,7 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
const handleChange = (values: any) => {
|
const handleChange = (values: any) => {
|
||||||
setSelectedAlertValues(values);
|
setSelectedAlertValues(values);
|
||||||
// Here, you can perform any additional logic with the selected values
|
// Here, you can perform any additional logic with the selected values
|
||||||
console.log('Selected values:', values);
|
console.log("Selected values:", values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveAlerts = () => {
|
const handleSaveAlerts = () => {
|
||||||
|
@ -182,10 +192,12 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
|
|
||||||
const updatedAlertToWebhooks: Record<string, string> = {};
|
const updatedAlertToWebhooks: Record<string, string> = {};
|
||||||
Object.entries(alerts_to_UI_NAME).forEach(([key, value]) => {
|
Object.entries(alerts_to_UI_NAME).forEach(([key, value]) => {
|
||||||
const webhookInput = document.querySelector(`input[name="${key}"]`) as HTMLInputElement;
|
const webhookInput = document.querySelector(
|
||||||
|
`input[name="${key}"]`
|
||||||
|
) as HTMLInputElement;
|
||||||
console.log("key", key);
|
console.log("key", key);
|
||||||
console.log("webhookInput", webhookInput);
|
console.log("webhookInput", webhookInput);
|
||||||
const newWebhookValue = webhookInput?.value || '';
|
const newWebhookValue = webhookInput?.value || "";
|
||||||
console.log("newWebhookValue", newWebhookValue);
|
console.log("newWebhookValue", newWebhookValue);
|
||||||
updatedAlertToWebhooks[key] = newWebhookValue;
|
updatedAlertToWebhooks[key] = newWebhookValue;
|
||||||
});
|
});
|
||||||
|
@ -195,7 +207,7 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
const payload = {
|
const payload = {
|
||||||
general_settings: {
|
general_settings: {
|
||||||
alert_to_webhook_url: updatedAlertToWebhooks,
|
alert_to_webhook_url: updatedAlertToWebhooks,
|
||||||
alert_types: activeAlerts
|
alert_types: activeAlerts,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,10 +216,10 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
try {
|
try {
|
||||||
setCallbacksCall(accessToken, payload);
|
setCallbacksCall(accessToken, payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('Failed to update alerts: ' + error, 20);
|
message.error("Failed to update alerts: " + error, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success('Alerts updated successfully');
|
message.success("Alerts updated successfully");
|
||||||
};
|
};
|
||||||
const handleSaveChanges = (callback: any) => {
|
const handleSaveChanges = (callback: any) => {
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
|
@ -215,7 +227,11 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedVariables = Object.fromEntries(
|
const updatedVariables = Object.fromEntries(
|
||||||
Object.entries(callback.variables).map(([key, value]) => [key, (document.querySelector(`input[name="${key}"]`) as HTMLInputElement)?.value || value])
|
Object.entries(callback.variables).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
(document.querySelector(`input[name="${key}"]`) as HTMLInputElement)
|
||||||
|
?.value || value,
|
||||||
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("updatedVariables", updatedVariables);
|
console.log("updatedVariables", updatedVariables);
|
||||||
|
@ -224,8 +240,8 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
const payload = {
|
const payload = {
|
||||||
environment_variables: updatedVariables,
|
environment_variables: updatedVariables,
|
||||||
litellm_settings: {
|
litellm_settings: {
|
||||||
"success_callback": [callback.name]
|
success_callback: [callback.name],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -246,82 +262,82 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
// Call API to add the callback
|
// Call API to add the callback
|
||||||
console.log("Form values:", values);
|
console.log("Form values:", values);
|
||||||
let payload;
|
let payload;
|
||||||
if (values.callback === 'langfuse') {
|
if (values.callback === "langfuse") {
|
||||||
payload = {
|
payload = {
|
||||||
environment_variables: {
|
environment_variables: {
|
||||||
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
|
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
|
||||||
LANGFUSE_SECRET_KEY: values.langfusePrivateKey
|
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
|
||||||
},
|
},
|
||||||
litellm_settings: {
|
litellm_settings: {
|
||||||
success_callback: [values.callback]
|
success_callback: [values.callback],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
setCallbacksCall(accessToken, payload);
|
setCallbacksCall(accessToken, payload);
|
||||||
let newCallback: AlertingObject = {
|
let newCallback: AlertingObject = {
|
||||||
"name": values.callback,
|
name: values.callback,
|
||||||
"variables": {
|
variables: {
|
||||||
"SLACK_WEBHOOK_URL": null,
|
SLACK_WEBHOOK_URL: null,
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": values.langfusePublicKey,
|
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
|
||||||
"LANGFUSE_SECRET_KEY": values.langfusePrivateKey,
|
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
|
||||||
OPENMETER_API_KEY: null
|
OPENMETER_API_KEY: null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
// add langfuse to callbacks
|
// add langfuse to callbacks
|
||||||
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
||||||
} else if (values.callback === 'slack') {
|
} else if (values.callback === "slack") {
|
||||||
console.log(`values.slackWebhookUrl: ${values.slackWebhookUrl}`)
|
console.log(`values.slackWebhookUrl: ${values.slackWebhookUrl}`);
|
||||||
payload = {
|
payload = {
|
||||||
general_settings: {
|
general_settings: {
|
||||||
alerting: ["slack"],
|
alerting: ["slack"],
|
||||||
alerting_threshold: 300
|
alerting_threshold: 300,
|
||||||
},
|
},
|
||||||
environment_variables: {
|
environment_variables: {
|
||||||
SLACK_WEBHOOK_URL: values.slackWebhookUrl
|
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
setCallbacksCall(accessToken, payload);
|
setCallbacksCall(accessToken, payload);
|
||||||
|
|
||||||
// add slack to callbacks
|
// add slack to callbacks
|
||||||
console.log(`values.callback: ${values.callback}`)
|
console.log(`values.callback: ${values.callback}`);
|
||||||
|
|
||||||
let newCallback: AlertingObject = {
|
let newCallback: AlertingObject = {
|
||||||
"name": values.callback,
|
name: values.callback,
|
||||||
"variables": {
|
variables: {
|
||||||
"SLACK_WEBHOOK_URL": values.slackWebhookUrl,
|
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": null,
|
LANGFUSE_PUBLIC_KEY: null,
|
||||||
"LANGFUSE_SECRET_KEY": null,
|
LANGFUSE_SECRET_KEY: null,
|
||||||
"OPENMETER_API_KEY": null
|
OPENMETER_API_KEY: null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
||||||
} else if (values.callback == "openmeter") {
|
} else if (values.callback == "openmeter") {
|
||||||
console.log(`values.openMeterApiKey: ${values.openMeterApiKey}`)
|
console.log(`values.openMeterApiKey: ${values.openMeterApiKey}`);
|
||||||
payload = {
|
payload = {
|
||||||
environment_variables: {
|
environment_variables: {
|
||||||
OPENMETER_API_KEY: values.openMeterApiKey,
|
OPENMETER_API_KEY: values.openMeterApiKey,
|
||||||
},
|
},
|
||||||
litellm_settings: {
|
litellm_settings: {
|
||||||
success_callback: [values.callback]
|
success_callback: [values.callback],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
setCallbacksCall(accessToken, payload);
|
setCallbacksCall(accessToken, payload);
|
||||||
let newCallback: AlertingObject = {
|
let newCallback: AlertingObject = {
|
||||||
"name": values.callback,
|
name: values.callback,
|
||||||
"variables": {
|
variables: {
|
||||||
"SLACK_WEBHOOK_URL": null,
|
SLACK_WEBHOOK_URL: null,
|
||||||
"LANGFUSE_HOST": null,
|
LANGFUSE_HOST: null,
|
||||||
"LANGFUSE_PUBLIC_KEY": null,
|
LANGFUSE_PUBLIC_KEY: null,
|
||||||
"LANGFUSE_SECRET_KEY": null,
|
LANGFUSE_SECRET_KEY: null,
|
||||||
OPENMETER_API_KEY: values.openMeterAPIKey
|
OPENMETER_API_KEY: values.openMeterAPIKey,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
// add langfuse to callbacks
|
// add langfuse to callbacks
|
||||||
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
|
||||||
} else {
|
} else {
|
||||||
payload = {
|
payload = {
|
||||||
error: 'Invalid callback value'
|
error: "Invalid callback value",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
|
@ -338,13 +354,14 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`callbacks: ${callbacks}`)
|
console.log(`callbacks: ${callbacks}`);
|
||||||
return (
|
return (
|
||||||
<div className="w-full mx-4">
|
<div className="w-full mx-4">
|
||||||
<Grid numItems={1} className="gap-2 p-8 w-full mt-2">
|
<Grid numItems={1} className="gap-2 p-8 w-full mt-2">
|
||||||
<Callout title="[UI] Presidio PII + Guardrails Coming Soon. https://docs.litellm.ai/docs/proxy/pii_masking" color="sky">
|
<Callout
|
||||||
|
title="[UI] Presidio PII + Guardrails Coming Soon. https://docs.litellm.ai/docs/proxy/pii_masking"
|
||||||
</Callout>
|
color="sky"
|
||||||
|
></Callout>
|
||||||
<TabGroup>
|
<TabGroup>
|
||||||
<TabList variant="line" defaultValue="1">
|
<TabList variant="line" defaultValue="1">
|
||||||
<Tab value="1">Logging Callbacks</Tab>
|
<Tab value="1">Logging Callbacks</Tab>
|
||||||
|
@ -352,7 +369,6 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
@ -362,29 +378,49 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{callbacks.filter((callback) => callback.name !== "slack").map((callback, index) => (
|
{callbacks
|
||||||
|
.filter((callback) => callback.name !== "slack")
|
||||||
|
.map((callback, index) => (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge color="emerald">{callback.name}</Badge>
|
<Badge color="emerald">{callback.name}</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(callback.variables ?? {}).filter(([key, value]) => key.toLowerCase().includes(callback.name)).map(([key, value]) => (
|
{Object.entries(callback.variables ?? {})
|
||||||
|
.filter(([key, value]) =>
|
||||||
|
key.toLowerCase().includes(callback.name)
|
||||||
|
)
|
||||||
|
.map(([key, value]) => (
|
||||||
<li key={key}>
|
<li key={key}>
|
||||||
<Text className="mt-2">{key}</Text>
|
<Text className="mt-2">{key}</Text>
|
||||||
{key === "LANGFUSE_HOST" ? (
|
{key === "LANGFUSE_HOST" ? (
|
||||||
<p>default value=https://cloud.langfuse.com</p>
|
<p>
|
||||||
|
default value=https://cloud.langfuse.com
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div></div>
|
<div></div>
|
||||||
)}
|
)}
|
||||||
<TextInput name={key} defaultValue={value as string} type="password" />
|
<TextInput
|
||||||
|
name={key}
|
||||||
|
defaultValue={value as string}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<Button className="mt-2" onClick={() => handleSaveChanges(callback)}>
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
onClick={() => handleSaveChanges(callback)}
|
||||||
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => serviceHealthCheck(accessToken, callback.name)} className="mx-2">
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
serviceHealthCheck(accessToken, callback.name)
|
||||||
|
}
|
||||||
|
className="mx-2"
|
||||||
|
>
|
||||||
Test Callback
|
Test Callback
|
||||||
</Button>
|
</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -392,14 +428,22 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Text className="my-2">Alerts are only supported for Slack Webhook URLs. Get your webhook urls from <a href="https://api.slack.com/messaging/webhooks" target="_blank" style={{color: 'blue'}}>here</a></Text>
|
<Text className="my-2">
|
||||||
|
Alerts are only supported for Slack Webhook URLs. Get your
|
||||||
|
webhook urls from{" "}
|
||||||
|
<a
|
||||||
|
href="https://api.slack.com/messaging/webhooks"
|
||||||
|
target="_blank"
|
||||||
|
style={{ color: "blue" }}
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</Text>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -410,7 +454,8 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{Object.entries(alerts_to_UI_NAME).map(([key, value], index) => (
|
{Object.entries(alerts_to_UI_NAME).map(
|
||||||
|
([key, value], index) => (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -424,33 +469,35 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
<Text>{value}</Text>
|
<Text>{value}</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<TextInput name={key} type="password" defaultValue={alertToWebhooks && alertToWebhooks[key] ? alertToWebhooks[key] : catchAllWebhookURL as string}>
|
<TextInput
|
||||||
|
name={key}
|
||||||
</TextInput>
|
type="password"
|
||||||
|
defaultValue={
|
||||||
|
alertToWebhooks && alertToWebhooks[key]
|
||||||
|
? alertToWebhooks[key]
|
||||||
|
: (catchAllWebhookURL as string)
|
||||||
|
}
|
||||||
|
></TextInput>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<Button size="xs" className="mt-2" onClick={handleSaveAlerts}>
|
<Button size="xs" className="mt-2" onClick={handleSaveAlerts}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={() => serviceHealthCheck(accessToken, "slack")} className="mx-2">
|
<Button
|
||||||
|
onClick={() => serviceHealthCheck(accessToken, "slack")}
|
||||||
|
className="mx-2"
|
||||||
|
>
|
||||||
Test Alerts
|
Test Alerts
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -473,7 +520,7 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{selectedCallback === 'langfuse' && (
|
{selectedCallback === "langfuse" && (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="LANGFUSE_PUBLIC_KEY"
|
label="LANGFUSE_PUBLIC_KEY"
|
||||||
|
@ -497,19 +544,22 @@ const Settings: React.FC<SettingsPageProps> = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{
|
{selectedCallback == "openmeter" && (
|
||||||
selectedCallback == "openmeter" && <>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="OPENMETER_API_KEY"
|
label="OPENMETER_API_KEY"
|
||||||
name="openMeterApiKey"
|
name="openMeterApiKey"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: "Please enter the openmeter api key" },
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please enter the openmeter api key",
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<TextInput type="password" />
|
<TextInput type="password" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||||
<Button2 htmlType="submit">Save</Button2>
|
<Button2 htmlType="submit">Save</Button2>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue