feat(ui): settings.tsx

enable/disable outage alerts on admin ui
This commit is contained in:
Krrish Dholakia 2024-05-24 20:04:15 -07:00
parent d25ed9c4d3
commit 9b90f91515
19 changed files with 410 additions and 355 deletions

View file

@ -1,7 +1,7 @@
#### What this does #### #### What this does ####
# Class for sending Slack Alerts # # Class for sending Slack Alerts #
import dotenv, os, traceback 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
@ -31,20 +31,6 @@ class OutageModel(TypedDict):
last_updated_at: float last_updated_at: float
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",
]
class LiteLLMBase(BaseModel): class LiteLLMBase(BaseModel):
""" """
Implements default functions, all pydantic objects should have. Implements default functions, all pydantic objects should have.
@ -65,8 +51,8 @@ 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 * 60 # 1 hour outage_alert_ttl: int = 1 * 60 # 1 minute ttl
minor_outage_alert_threshold: int = 3 minor_outage_alert_threshold: int = 5
major_outage_alert_threshold: int = 10 major_outage_alert_threshold: int = 10
max_outage_alert_list_size: int = 10 # prevent memory leak max_outage_alert_list_size: int = 10 # prevent memory leak

File diff suppressed because one or more lines are too long

View file

@ -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()}]);

View file

@ -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()}]);

View file

@ -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))}();

View file

@ -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>

View file

@ -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

View file

@ -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: api_base: http://0.0.0.0:8080
model: openai/my-fake-model api_key: ''
api_base: http://0.0.0.0:8080 model: openai/my-fake-model
api_key: "" model_name: gpt-3.5-turbo-fake-model
- model_name: gpt-3.5-turbo - litellm_params:
litellm_params: api_base: https://my-endpoint-europe-berri-992.openai.azure.com/
model: azure/gpt-35-turbo api_key: os.environ/AZURE_EUROPE_API_KEY
api_base: https://my-endpoint-europe-berri-992.openai.azure.com/ model: azure/gpt-35-turbo
api_key: os.environ/AZURE_EUROPE_API_KEY model_name: gpt-3.5-turbo
- model_name: gpt-3.5-turbo - litellm_params:
litellm_params: api_base: https://openai-gpt-4-test-v-1.openai.azure.com/
model: azure/chatgpt-v-2 api_key: os.environ/AZURE_API_KEY
api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: '2023-05-15'
api_version: "2023-05-15" model: azure/chatgpt-v-2
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 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

View file

@ -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",
) )

View file

@ -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",
) )
for _id in user_id_information: if user_id_information is not None:
await user_api_key_cache.async_set_cache( for _id in user_id_information:
key=_id["user_id"], await user_api_key_cache.async_set_cache(
value=_id, key=_id["user_id"],
ttl=UserAPIKeyCacheTTLEnum.user_information_cache.value, value=_id,
) 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:
@ -9633,7 +9622,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)
@ -10760,7 +10749,10 @@ async def get_config():
} }
) )
_router_settings = llm_router.get_settings() if llm_router is None:
_router_settings = {}
else:
_router_settings = llm_router.get_settings()
return { return {
"status": "success", "status": "success",
"callbacks": _data_to_return, "callbacks": _data_to_return,
@ -10950,6 +10942,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

View file

@ -12,6 +12,7 @@ from litellm.proxy._types import (
LiteLLM_TeamTable, LiteLLM_TeamTable,
Member, Member,
CallInfo, CallInfo,
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
@ -42,7 +43,7 @@ import smtplib, re
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta from datetime import datetime, timedelta
from litellm.integrations.slack_alerting import SlackAlerting, AlertType from litellm.integrations.slack_alerting import SlackAlerting
from typing_extensions import overload from typing_extensions import overload
@ -2566,13 +2567,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

File diff suppressed because one or more lines are too long

View file

@ -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>

View file

@ -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

View file

@ -17,14 +17,18 @@ import {
TextInput, TextInput,
Switch, Switch,
Col, Col,
TabPanel, TabPanel,
TabPanels, TabPanels,
TabGroup, TabGroup,
TabList, TabList,
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,20 +182,22 @@ 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 = () => {
if (!accessToken) { if (!accessToken) {
return; return;
} }
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,19 +207,19 @@ 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,
}, },
}; };
console.log("payload", payload); console.log("payload", payload);
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,119 +354,150 @@ 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>
<Tab value="2">Alerting</Tab> <Tab value="2">Alerting</Tab>
</TabList> </TabList>
<TabPanels> <TabPanels>
<TabPanel> <TabPanel>
<Card>
<Card > <Table>
<Table> <TableHead>
<TableHead> <TableRow>
<TableRow> <TableHeaderCell>Callback</TableHeaderCell>
<TableHeaderCell>Callback</TableHeaderCell> <TableHeaderCell>Callback Env Vars</TableHeaderCell>
<TableHeaderCell>Callback Env Vars</TableHeaderCell> </TableRow>
</TableRow> </TableHead>
</TableHead> <TableBody>
<TableBody> {callbacks
{callbacks.filter((callback) => callback.name !== "slack").map((callback, index) => ( .filter((callback) => callback.name !== "slack")
<TableRow key={index}> .map((callback, index) => (
<TableCell> <TableRow key={index}>
<Badge color="emerald">{callback.name}</Badge> <TableCell>
</TableCell> <Badge color="emerald">{callback.name}</Badge>
<TableCell> </TableCell>
<ul> <TableCell>
{Object.entries(callback.variables ?? {}).filter(([key, value]) => key.toLowerCase().includes(callback.name)).map(([key, value]) => ( <ul>
<li key={key}> {Object.entries(callback.variables ?? {})
<Text className="mt-2">{key}</Text> .filter(([key, value]) =>
{key === "LANGFUSE_HOST" ? ( key.toLowerCase().includes(callback.name)
<p>default value=https://cloud.langfuse.com</p> )
) : ( .map(([key, value]) => (
<div></div> <li key={key}>
)} <Text className="mt-2">{key}</Text>
<TextInput name={key} defaultValue={value as string} type="password" /> {key === "LANGFUSE_HOST" ? (
</li> <p>
))} default value=https://cloud.langfuse.com
</ul> </p>
<Button className="mt-2" onClick={() => handleSaveChanges(callback)}> ) : (
Save Changes <div></div>
</Button> )}
<Button onClick={() => serviceHealthCheck(accessToken, callback.name)} className="mx-2"> <TextInput
Test Callback name={key}
</Button> defaultValue={value as string}
</TableCell> type="password"
</TableRow> />
))} </li>
</TableBody> ))}
</Table> </ul>
<Button
</Card> className="mt-2"
</TabPanel> onClick={() => handleSaveChanges(callback)}
>
Save Changes
</Button>
<Button
onClick={() =>
serviceHealthCheck(accessToken, callback.name)
}
className="mx-2"
>
Test Callback
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</TabPanel>
<TabPanel> <TabPanel>
<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>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell></TableHeaderCell>
<TableHeaderCell></TableHeaderCell>
<TableHeaderCell>Slack Webhook URL</TableHeaderCell>
</TableRow>
</TableHead>
<Card> <TableBody>
<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> {Object.entries(alerts_to_UI_NAME).map(
<Table> ([key, value], index) => (
<TableHead> <TableRow key={index}>
<TableRow> <TableCell>
<TableHeaderCell></TableHeaderCell> <Switch
<TableHeaderCell></TableHeaderCell> id="switch"
<TableHeaderCell>Slack Webhook URL</TableHeaderCell> name="switch"
</TableRow> checked={isAlertOn(key)}
</TableHead> onChange={() => handleSwitchChange(key)}
/>
<TableBody> </TableCell>
{Object.entries(alerts_to_UI_NAME).map(([key, value], index) => ( <TableCell>
<TableRow key={index}> <Text>{value}</Text>
<TableCell> </TableCell>
<Switch <TableCell>
id="switch" <TextInput
name="switch" name={key}
checked={isAlertOn(key)} type="password"
onChange={() => handleSwitchChange(key)} defaultValue={
/> alertToWebhooks && alertToWebhooks[key]
</TableCell> ? alertToWebhooks[key]
<TableCell> : (catchAllWebhookURL as string)
<Text>{value}</Text> }
</TableCell> ></TextInput>
<TableCell> </TableCell>
<TextInput name={key} type="password" defaultValue={alertToWebhooks && alertToWebhooks[key] ? alertToWebhooks[key] : catchAllWebhookURL as string}> </TableRow>
)
</TextInput> )}
</TableCell> </TableBody>
</TableRow> </Table>
))} <Button size="xs" className="mt-2" onClick={handleSaveAlerts}>
</TableBody> Save Changes
</Table> </Button>
<Button size="xs" className="mt-2" onClick={handleSaveAlerts}>
Save Changes
</Button>
<Button onClick={() => serviceHealthCheck(accessToken, "slack")} className="mx-2"> <Button
Test Alerts onClick={() => serviceHealthCheck(accessToken, "slack")}
</Button> className="mx-2"
>
Test Alerts
</Button>
</Card>
</TabPanel>
</Card>
</TabPanel>
</TabPanels> </TabPanels>
</TabGroup> </TabGroup>
</Grid> </Grid>
<Modal <Modal
@ -472,8 +519,8 @@ const Settings: React.FC<SettingsPageProps> = ({
<Select.Option value="openmeter">openmeter</Select.Option> <Select.Option value="openmeter">openmeter</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
{selectedCallback === 'langfuse' && ( {selectedCallback === "langfuse" && (
<> <>
<Form.Item <Form.Item
label="LANGFUSE_PUBLIC_KEY" label="LANGFUSE_PUBLIC_KEY"
@ -482,7 +529,7 @@ const Settings: React.FC<SettingsPageProps> = ({
{ required: true, message: "Please enter the public key" }, { required: true, message: "Please enter the public key" },
]} ]}
> >
<TextInput type="password"/> <TextInput type="password" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -492,24 +539,27 @@ const Settings: React.FC<SettingsPageProps> = ({
{ required: true, message: "Please enter the private key" }, { required: true, message: "Please enter the private key" },
]} ]}
> >
<TextInput type="password"/> <TextInput type="password" />
</Form.Item> </Form.Item>
</> </>
)} )}
{ {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"/> },
</Form.Item> ]}
</> >
} <TextInput type="password" />
</Form.Item>
</>
)}
<div style={{ textAlign: "right", marginTop: "10px" }}> <div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2> <Button2 htmlType="submit">Save</Button2>
@ -520,4 +570,4 @@ const Settings: React.FC<SettingsPageProps> = ({
); );
}; };
export default Settings; export default Settings;