diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 20ff285a73..e5ea35a398 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1346,3 +1346,61 @@ class InvitationModel(LiteLLMBase): class ConfigFieldInfo(LiteLLMBase): field_name: str field_value: Any + + +class CallbackOnUI(LiteLLMBase): + litellm_callback_name: str + litellm_callback_params: Optional[list] + ui_callback_name: str + + +class AllCallbacks(LiteLLMBase): + langfuse: CallbackOnUI = CallbackOnUI( + litellm_callback_name="langfuse", + ui_callback_name="Langfuse", + litellm_callback_params=[ + "LANGFUSE_PUBLIC_KEY", + "LANGFUSE_SECRET_KEY", + ], + ) + + otel: CallbackOnUI = CallbackOnUI( + litellm_callback_name="otel", + ui_callback_name="OpenTelemetry", + litellm_callback_params=[ + "OTEL_EXPORTER", + "OTEL_ENDPOINT", + "OTEL_HEADERS", + ], + ) + + s3: CallbackOnUI = CallbackOnUI( + litellm_callback_name="s3", + ui_callback_name="s3 Bucket (AWS)", + litellm_callback_params=[ + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_REGION_NAME", + ], + ) + + openmeter: CallbackOnUI = CallbackOnUI( + litellm_callback_name="openmeter", + ui_callback_name="OpenMeter", + litellm_callback_params=[ + "OPENMETER_API_ENDPOINT", + "OPENMETER_API_KEY", + ], + ) + + custom_callback_api: CallbackOnUI = CallbackOnUI( + litellm_callback_name="custom_callback_api", + litellm_callback_params=["GENERIC_LOGGER_ENDPOINT"], + ui_callback_name="Custom Callback API", + ) + + datadog: CallbackOnUI = CallbackOnUI( + litellm_callback_name="datadog", + litellm_callback_params=["DD_API_KEY", "DD_SITE"], + ui_callback_name="Datadog", + ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 6d6a1e36bd..eda8af66c9 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3129,9 +3129,9 @@ class ProxyConfig: if "alerting" in _general_settings: if ( general_settings is not None - and general_settings["alerting"] is not None + and general_settings.get("alerting", None) is not None and isinstance(general_settings["alerting"], list) - and _general_settings["alerting"] is not None + and _general_settings.get("alerting", None) is not None and isinstance(_general_settings["alerting"], list) ): for alert in _general_settings["alerting"]: @@ -13435,6 +13435,14 @@ async def update_config(config_info: ConfigYAML): if prisma_client is None: raise Exception("No DB Connected") + if store_model_in_db is not True: + raise HTTPException( + status_code=500, + detail={ + "error": "Set `'STORE_MODEL_IN_DB='True'` in your env to enable this feature." + }, + ) + updated_settings = config_info.json(exclude_none=True) updated_settings = prisma_client.jsonify_object(updated_settings) for k, v in updated_settings.items(): @@ -13858,6 +13866,8 @@ async def get_config(): try: import base64 + all_available_callbacks = AllCallbacks() + config_data = await proxy_config.get_config() _litellm_settings = config_data.get("litellm_settings", {}) _general_settings = config_data.get("general_settings", {}) @@ -13992,11 +14002,13 @@ async def get_config(): _router_settings = {} else: _router_settings = llm_router.get_settings() + return { "status": "success", "callbacks": _data_to_return, "alerts": alerting_data, "router_settings": _router_settings, + "available_callbacks": all_available_callbacks, } except Exception as e: traceback.print_exc() diff --git a/litellm/utils.py b/litellm/utils.py index 4dcee6be45..4ad6ff9899 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1270,7 +1270,11 @@ class Logging: api_base ) # used for alerting masked_headers = { - k: (v[:-20] + "*" * 20) if (isinstance(v, str) and len(v) > 20) else v + k: ( + (v[:-44] + "*" * 44) + if (isinstance(v, str) and len(v) > 44) + else "*****" + ) for k, v in headers.items() } formatted_headers = " ".join( @@ -1309,8 +1313,7 @@ class Logging: # check if user wants the raw request logged to their logging provider (like LangFuse) _litellm_params = self.model_call_details.get("litellm_params", {}) _metadata = _litellm_params.get("metadata", {}) or {} - if _metadata.get("log_raw_request", False) is True: - _metadata["raw_request"] = curl_command + _metadata["raw_request"] = curl_command if self.logger_fn and callable(self.logger_fn): try: diff --git a/ui/litellm-dashboard/src/components/settings.tsx b/ui/litellm-dashboard/src/components/settings.tsx index 91f35c2496..0ca13e6ebd 100644 --- a/ui/litellm-dashboard/src/components/settings.tsx +++ b/ui/litellm-dashboard/src/components/settings.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { Card, - Title, + Subtitle, Table, TableHead, @@ -23,15 +23,25 @@ import { TabList, Tab, Callout, + SelectItem, + Icon, } from "@tremor/react"; + +import { + PencilAltIcon +} from "@heroicons/react/outline"; + +import { Modal, Typography, Form, Input, Select, Button as Button2, message } from "antd"; + +const { Title, Paragraph } = Typography; import { getCallbacksCall, setCallbacksCall, serviceHealthCheck, } from "./networking"; -import { Modal, Form, Input, Select, Button as Button2, message } from "antd"; import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider"; import AlertingSettings from "./alerting/alerting_settings"; +import FormItem from "antd/es/form/FormItem"; interface SettingsPageProps { accessToken: string | null; userRole: string | null; @@ -39,6 +49,15 @@ interface SettingsPageProps { premiumUser: boolean; } + +interface genericCallbackParams { + + litellm_callback_name: string // what to send in request + ui_callback_name: string // what to show on UI + litellm_callback_params: string[] | null // known required params for this callback +} + + interface AlertingVariables { SLACK_WEBHOOK_URL: string | null; LANGFUSE_PUBLIC_KEY: string | null; @@ -92,7 +111,7 @@ const Settings: React.FC = ({ premiumUser, }) => { const [callbacks, setCallbacks] = - useState(defaultLoggingObject); + useState([]); const [alerts, setAlerts] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); const [form] = Form.useForm(); @@ -104,6 +123,15 @@ const Settings: React.FC = ({ >({}); const [activeAlerts, setActiveAlerts] = useState([]); + const [showAddCallbacksModal, setShowAddCallbacksModal] = useState(false); + const [allCallbacks, setAllCallbacks] = useState([]); + + const [selectedCallbacktoAdd, setSelectedCallbacktoAdd] = useState(null); + const [selectedCallbackParams, setSelectedCallbackParams] = useState([]); + + const [showEditCallback, setShowEditCallback] = useState(false); + const [selectedEditCallback, setSelectedEditCallback] = useState(null); + const handleSwitchChange = (alertName: string) => { if (activeAlerts.includes(alertName)) { setActiveAlerts(activeAlerts.filter((alert) => alert !== alertName)); @@ -128,23 +156,9 @@ const Settings: React.FC = ({ } getCallbacksCall(accessToken, userID, userRole).then((data) => { console.log("callbacks", data); - let updatedCallbacks: any[] = defaultLoggingObject; - updatedCallbacks = updatedCallbacks.map((item: any) => { - const callback = data.callbacks.find( - (cb: any) => cb.name === item.name - ); - if (callback) { - return { - ...item, - variables: { ...item.variables, ...callback.variables }, - }; - } else { - return item; - } - }); - - setCallbacks(updatedCallbacks); + setCallbacks(data.callbacks); + setAllCallbacks(data.available_callbacks); // setCallbacks(callbacks_data); let alerts_data = data.alerts; @@ -225,6 +239,86 @@ const Settings: React.FC = ({ message.success("Email settings updated successfully"); } + const updateCallbackCall = async (formValues: Record) => { + if (!accessToken) { + return; + } + + let env_vars: Record = {}; + // add all other variables + Object.entries(formValues).forEach(([key, value]) => { + if (key !== "callback") { + env_vars[key] = value + } + }); + let payload = { + environment_variables: env_vars, + } + + + try { + let newCallback = await setCallbacksCall(accessToken, payload); + message.success(`Callback added successfully`); + setIsModalVisible(false); + form.resetFields(); + setSelectedCallback(null); + } catch (error) { + message.error("Failed to add callback: " + error, 20); + } + } + + + + + const addNewCallbackCall = async (formValues: Record) => { + if (!accessToken) { + return; + } + let new_callback = formValues?.callback + + let env_vars: Record = {}; + // add all other variables + Object.entries(formValues).forEach(([key, value]) => { + if (key !== "callback") { + env_vars[key] = value + } + }); + + let payload = { + environment_variables: env_vars, + litellm_settings: { + success_callback: [new_callback], + }, + } + + + try { + let newCallback = await setCallbacksCall(accessToken, payload); + message.success(`Callback ${new_callback} added successfully`); + setIsModalVisible(false); + form.resetFields(); + setSelectedCallback(null); + } catch (error) { + message.error("Failed to add callback: " + error, 20); + } + } + + + + const handleSelectedCallbackChange = (callbackObject: genericCallbackParams) => { + + console.log("inside handleSelectedCallbackChange", callbackObject); + setSelectedCallback(callbackObject.litellm_callback_name); + + console.log("all callbacks", allCallbacks); + if (callbackObject && callbackObject.litellm_callback_params) { + setSelectedCallbackParams(callbackObject.litellm_callback_params); + console.log("selectedCallbackParams", selectedCallbackParams); + } else { + setSelectedCallbackParams([]); + } + }; + const handleSaveAlerts = () => { if (!accessToken) { return; @@ -398,10 +492,7 @@ const Settings: React.FC = ({ return (
- + Logging Callbacks @@ -411,68 +502,61 @@ const Settings: React.FC = ({ - + Active Logging Callbacks + + + + + - Callback - Callback Env Vars + Callback Name + {/* Callback Env Vars */} + {callbacks - .filter((callback) => callback.name !== "slack") .map((callback, index) => ( - + + - {callback.name} + {callback.name} -
    - {Object.entries(callback.variables ?? {}) - .filter(([key, value]) => - key.toLowerCase().includes(callback.name) - ) - .map(([key, value]) => ( -
  • - {key} - {key === "LANGFUSE_HOST" ? ( -

    - default value=https://cloud.langfuse.com -

    - ) : ( -
    - )} - -
  • - ))} -
- - + + { + setSelectedEditCallback(callback); + setShowEditCallback(true); + }} + /> + +
))}
+
+
- @@ -566,7 +650,7 @@ const Settings: React.FC = ({ - Email Settings + Email Settings LiteLLM Docs: email alerts
@@ -704,73 +788,113 @@ const Settings: React.FC = ({
+ setShowAddCallbacksModal(false)} + footer={null} > -
- LiteLLM Docs: Logging + + + + + <> + + - langfuse - openmeter - - + {allCallbacks && + Object.values(allCallbacks).map((callback) => ( + + {callback.ui_callback_name} + + ))} + + - {selectedCallback === "langfuse" && ( - <> - - - - - - - - )} - - {selectedCallback == "openmeter" && ( - <> - - - - - )} + { + selectedCallbackParams && selectedCallbackParams.map((param) => ( + + + + )) + }
- Save + Save
+ +
-
+ + + setShowEditCallback(false)} + footer={null} + > + +
+ <> + { + selectedEditCallback && selectedEditCallback.variables && Object.entries(selectedEditCallback.variables).map(([param, value]) => ( + + + + )) + } + + + +
+ Save +
+ +
+ + +
+ ); };