feat(proxy_server.py): Enabling Admin to control general settings on proxy ui

This commit is contained in:
Krrish Dholakia 2024-05-15 15:26:57 -07:00
parent d9ad7c6218
commit 6a357b4275
3 changed files with 515 additions and 174 deletions

View file

@ -711,6 +711,11 @@ class DynamoDBArgs(LiteLLMBase):
assume_role_aws_session_name: Optional[str] = None assume_role_aws_session_name: Optional[str] = None
class ConfigFieldUpdate(LiteLLMBase):
field_name: str
field_value: Any
class ConfigGeneralSettings(LiteLLMBase): class ConfigGeneralSettings(LiteLLMBase):
""" """
Documents all the fields supported by `general_settings` in config.yaml Documents all the fields supported by `general_settings` in config.yaml

View file

@ -234,6 +234,7 @@ class SpecialModelNames(enum.Enum):
class CommonProxyErrors(enum.Enum): class CommonProxyErrors(enum.Enum):
db_not_connected_error = "DB not connected" db_not_connected_error = "DB not connected"
no_llm_router = "No models configured on proxy" no_llm_router = "No models configured on proxy"
not_allowed_access = "Admin-only endpoint. Not allowed to access this."
@app.exception_handler(ProxyException) @app.exception_handler(ProxyException)
@ -9389,7 +9390,7 @@ async def auth_callback(request: Request):
return RedirectResponse(url=litellm_dashboard_ui) return RedirectResponse(url=litellm_dashboard_ui)
#### BASIC ENDPOINTS #### #### CONFIG MANAGEMENT ####
@router.post( @router.post(
"/config/update", "/config/update",
tags=["config.yaml"], tags=["config.yaml"],
@ -9525,6 +9526,219 @@ async def update_config(config_info: ConfigYAML):
) )
### CONFIG GENERAL SETTINGS
"""
- Update config settings
- Get config settings
Keep it more precise, to prevent overwrite other values unintentially
"""
@router.post(
"/config/field/update",
tags=["config.yaml"],
dependencies=[Depends(user_api_key_auth)],
)
async def update_config_general_settings(
data: ConfigFieldUpdate,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Update a specific field in litellm general settings
"""
global prisma_client
## VALIDATION ##
"""
- Check if prisma_client is None
- Check if user allowed to call this endpoint (admin-only)
- Check if param in general settings
- Check if config value is valid type
"""
if prisma_client is None:
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.not_allowed_access.value},
)
if data.field_name not in ConfigGeneralSettings.model_fields:
raise HTTPException(
status_code=400,
detail={"error": "Invalid field={} passed in.".format(data.field_name)},
)
try:
cgs = ConfigGeneralSettings(**{data.field_name: data.field_value})
except:
raise HTTPException(
status_code=400,
detail={
"error": "Invalid type of field value={} passed in.".format(
type(data.field_value),
)
},
)
## get general settings from db
db_general_settings = await prisma_client.db.litellm_config.find_first(
where={"param_name": "general_settings"}
)
### update value
if db_general_settings is None or db_general_settings.param_value is None:
general_settings = {}
else:
general_settings = dict(db_general_settings.param_value)
## update db
general_settings[data.field_name] = data.field_value
response = await prisma_client.db.litellm_config.upsert(
where={"param_name": "general_settings"},
data={
"create": {"param_name": "general_settings", "param_value": json.dumps(general_settings)}, # type: ignore
"update": {"param_value": json.dumps(general_settings)}, # type: ignore
},
)
return response
@router.get(
"/config/field/info",
tags=["config.yaml"],
dependencies=[Depends(user_api_key_auth)],
)
async def get_config_general_settings(
field_name: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
global prisma_client
## VALIDATION ##
"""
- Check if prisma_client is None
- Check if user allowed to call this endpoint (admin-only)
- Check if param in general settings
"""
if prisma_client is None:
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.not_allowed_access.value},
)
if field_name not in ConfigGeneralSettings.model_fields:
raise HTTPException(
status_code=400,
detail={"error": "Invalid field={} passed in.".format(field_name)},
)
## get general settings from db
db_general_settings = await prisma_client.db.litellm_config.find_first(
where={"param_name": "general_settings"}
)
### pop the value
if db_general_settings is None or db_general_settings.param_value is None:
raise HTTPException(
status_code=400,
detail={"error": "Field name={} not in DB".format(field_name)},
)
else:
general_settings = dict(db_general_settings.param_value)
if field_name in general_settings:
return {
"field_name": field_name,
"field_value": general_settings[field_name],
}
else:
raise HTTPException(
status_code=400,
detail={"error": "Field name={} not in DB".format(field_name)},
)
@router.post(
"/config/field/delete",
tags=["config.yaml"],
dependencies=[Depends(user_api_key_auth)],
)
async def delete_config_general_settings(
field_name: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Delete the db value of this field in litellm general settings. Resets it to it's initial default value on litellm.
"""
global prisma_client
## VALIDATION ##
"""
- Check if prisma_client is None
- Check if user allowed to call this endpoint (admin-only)
- Check if param in general settings
"""
if prisma_client is None:
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.not_allowed_access.value},
)
if field_name not in ConfigGeneralSettings.model_fields:
raise HTTPException(
status_code=400,
detail={"error": "Invalid field={} passed in.".format(field_name)},
)
## get general settings from db
db_general_settings = await prisma_client.db.litellm_config.find_first(
where={"param_name": "general_settings"}
)
### pop the value
if db_general_settings is None or db_general_settings.param_value is None:
raise HTTPException(
status_code=400,
detail={"error": "Field name={} not in config".format(field_name)},
)
else:
general_settings = dict(db_general_settings.param_value)
## update db
general_settings.pop(field_name)
response = await prisma_client.db.litellm_config.upsert(
where={"param_name": "general_settings"},
data={
"create": {"param_name": "general_settings", "param_value": json.dumps(general_settings)}, # type: ignore
"update": {"param_value": json.dumps(general_settings)}, # type: ignore
},
)
return response
@router.get( @router.get(
"/get/config/callbacks", "/get/config/callbacks",
tags=["config.yaml"], tags=["config.yaml"],
@ -9692,6 +9906,7 @@ async def config_yaml_endpoint(config_info: ConfigYAML):
return {"hello": "world"} return {"hello": "world"}
#### BASIC ENDPOINTS ####
@router.get( @router.get(
"/test", "/test",
tags=["health"], tags=["health"],

View file

@ -23,12 +23,30 @@ import {
AccordionHeader, AccordionHeader,
AccordionList, AccordionList,
} from "@tremor/react"; } from "@tremor/react";
import { TabPanel, TabPanels, TabGroup, TabList, Tab, Icon } from "@tremor/react"; import {
import { getCallbacksCall, setCallbacksCall, serviceHealthCheck } from "./networking"; TabPanel,
TabPanels,
TabGroup,
TabList,
Tab,
Icon,
} from "@tremor/react";
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 { InformationCircleIcon, PencilAltIcon, PencilIcon, StatusOnlineIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline"; import {
InformationCircleIcon,
PencilAltIcon,
PencilIcon,
StatusOnlineIcon,
TrashIcon,
RefreshIcon,
} from "@heroicons/react/outline";
import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider"; import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider";
import AddFallbacks from "./add_fallbacks" import AddFallbacks from "./add_fallbacks";
import openai from "openai"; import openai from "openai";
import Paragraph from "antd/es/skeleton/Paragraph"; import Paragraph from "antd/es/skeleton/Paragraph";
@ -36,7 +54,7 @@ interface GeneralSettingsPageProps {
accessToken: string | null; accessToken: string | null;
userRole: string | null; userRole: string | null;
userID: string | null; userID: string | null;
modelData: any modelData: any;
} }
async function testFallbackModelResponse( async function testFallbackModelResponse(
@ -65,24 +83,39 @@ async function testFallbackModelResponse(
}, },
], ],
// @ts-ignore // @ts-ignore
mock_testing_fallbacks: true mock_testing_fallbacks: true,
}); });
message.success( message.success(
<span> <span>
Test model=<strong>{selectedModel}</strong>, received model=<strong>{response.model}</strong>. Test model=<strong>{selectedModel}</strong>, received model=
See <a href="#" onClick={() => window.open('https://docs.litellm.ai/docs/proxy/reliability', '_blank')} style={{ textDecoration: 'underline', color: 'blue' }}>curl</a> <strong>{response.model}</strong>. See{" "}
<a
href="#"
onClick={() =>
window.open(
"https://docs.litellm.ai/docs/proxy/reliability",
"_blank"
)
}
style={{ textDecoration: "underline", color: "blue" }}
>
curl
</a>
</span> </span>
); );
} catch (error) { } catch (error) {
message.error(`Error occurred while generating model response. Please try again. Error: ${error}`, 20); message.error(
`Error occurred while generating model response. Please try again. Error: ${error}`,
20
);
} }
} }
interface AccordionHeroProps { interface AccordionHeroProps {
selectedStrategy: string | null; selectedStrategy: string | null;
strategyArgs: routingStrategyArgs; strategyArgs: routingStrategyArgs;
paramExplanation: { [key: string]: string } paramExplanation: { [key: string]: string };
} }
interface routingStrategyArgs { interface routingStrategyArgs {
@ -91,16 +124,21 @@ interface routingStrategyArgs {
} }
const defaultLowestLatencyArgs: routingStrategyArgs = { const defaultLowestLatencyArgs: routingStrategyArgs = {
"ttl": 3600, ttl: 3600,
"lowest_latency_buffer": 0 lowest_latency_buffer: 0,
} };
export const AccordionHero: React.FC<AccordionHeroProps> = ({ selectedStrategy, strategyArgs, paramExplanation }) => ( export const AccordionHero: React.FC<AccordionHeroProps> = ({
selectedStrategy,
strategyArgs,
paramExplanation,
}) => (
<Accordion> <Accordion>
<AccordionHeader className="text-sm font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong">Routing Strategy Specific Args</AccordionHeader> <AccordionHeader className="text-sm font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong">
Routing Strategy Specific Args
</AccordionHeader>
<AccordionBody> <AccordionBody>
{ {selectedStrategy == "latency-based-routing" ? (
selectedStrategy == "latency-based-routing" ?
<Card> <Card>
<Table> <Table>
<TableHead> <TableHead>
@ -114,13 +152,24 @@ export const AccordionHero: React.FC<AccordionHeroProps> = ({ selectedStrategy,
<TableRow key={param}> <TableRow key={param}>
<TableCell> <TableCell>
<Text>{param}</Text> <Text>{param}</Text>
<p style={{fontSize: '0.65rem', color: '#808080', fontStyle: 'italic'}} className="mt-1">{paramExplanation[param]}</p> <p
style={{
fontSize: "0.65rem",
color: "#808080",
fontStyle: "italic",
}}
className="mt-1"
>
{paramExplanation[param]}
</p>
</TableCell> </TableCell>
<TableCell> <TableCell>
<TextInput <TextInput
name={param} name={param}
defaultValue={ defaultValue={
typeof value === 'object' ? JSON.stringify(value, null, 2) : value.toString() typeof value === "object"
? JSON.stringify(value, null, 2)
: value.toString()
} }
/> />
</TableCell> </TableCell>
@ -129,8 +178,9 @@ export const AccordionHero: React.FC<AccordionHeroProps> = ({ selectedStrategy,
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
: <Text>No specific settings</Text> ) : (
} <Text>No specific settings</Text>
)}
</AccordionBody> </AccordionBody>
</Accordion> </Accordion>
); );
@ -139,26 +189,32 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
accessToken, accessToken,
userRole, userRole,
userID, userID,
modelData modelData,
}) => { }) => {
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({}); const [routerSettings, setRouterSettings] = useState<{ [key: string]: 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 [selectedStrategy, setSelectedStrategy] = useState<string | null>(null) const [selectedStrategy, setSelectedStrategy] = useState<string | null>(null);
const [strategySettings, setStrategySettings] = useState<routingStrategyArgs | null>(null); const [strategySettings, setStrategySettings] =
useState<routingStrategyArgs | null>(null);
let paramExplanation: { [key: string]: string } = { let paramExplanation: { [key: string]: string } = {
"routing_strategy_args": "(dict) Arguments to pass to the routing strategy", routing_strategy_args: "(dict) Arguments to pass to the routing strategy",
"routing_strategy": "(string) Routing strategy to use", routing_strategy: "(string) Routing strategy to use",
"allowed_fails": "(int) Number of times a deployment can fail before being added to cooldown", allowed_fails:
"cooldown_time": "(int) time in seconds to cooldown a deployment after failure", "(int) Number of times a deployment can fail before being added to cooldown",
"num_retries": "(int) Number of retries for failed requests. Defaults to 0.", cooldown_time:
"timeout": "(float) Timeout for requests. Defaults to None.", "(int) time in seconds to cooldown a deployment after failure",
"retry_after": "(int) Minimum time to wait before retrying a failed request", num_retries: "(int) Number of retries for failed requests. Defaults to 0.",
"ttl": "(int) Sliding window to look back over when calculating the average latency of a deployment. Default - 1 hour (in seconds).", timeout: "(float) Timeout for requests. Defaults to None.",
"lowest_latency_buffer": "(float) Shuffle between deployments within this % of the lowest latency. Default - 0 (i.e. always pick lowest latency)." retry_after: "(int) Minimum time to wait before retrying a failed request",
} ttl: "(int) Sliding window to look back over when calculating the average latency of a deployment. Default - 1 hour (in seconds).",
lowest_latency_buffer:
"(float) Shuffle between deployments within this % of the lowest latency. Default - 0 (i.e. always pick lowest latency).",
};
useEffect(() => { useEffect(() => {
if (!accessToken || !userRole || !userID) { if (!accessToken || !userRole || !userID) {
@ -190,8 +246,8 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
return; return;
} }
console.log(`received key: ${key}`) console.log(`received key: ${key}`);
console.log(`routerSettings['fallbacks']: ${routerSettings['fallbacks']}`) console.log(`routerSettings['fallbacks']: ${routerSettings["fallbacks"]}`);
routerSettings["fallbacks"].map((dict: { [key: string]: any }) => { routerSettings["fallbacks"].map((dict: { [key: string]: any }) => {
// Check if the dictionary has the specified key and delete it if present // Check if the dictionary has the specified key and delete it if present
@ -202,18 +258,18 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
}); });
const payload = { const payload = {
router_settings: routerSettings router_settings: routerSettings,
}; };
try { try {
await setCallbacksCall(accessToken, payload); await setCallbacksCall(accessToken, payload);
setRouterSettings({ ...routerSettings }); setRouterSettings({ ...routerSettings });
setSelectedStrategy(routerSettings["routing_strategy"]) setSelectedStrategy(routerSettings["routing_strategy"]);
message.success("Router settings updated successfully"); message.success("Router settings updated successfully");
} catch (error) { } catch (error) {
message.error("Failed to update router settings: " + error, 20); message.error("Failed to update router settings: " + error, 20);
} }
} };
const handleSaveChanges = (router_settings: any) => { const handleSaveChanges = (router_settings: any) => {
if (!accessToken) { if (!accessToken) {
@ -223,39 +279,55 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
console.log("router_settings", router_settings); console.log("router_settings", router_settings);
const updatedVariables = Object.fromEntries( const updatedVariables = Object.fromEntries(
Object.entries(router_settings).map(([key, value]) => { Object.entries(router_settings)
if (key !== 'routing_strategy_args' && key !== "routing_strategy") { .map(([key, value]) => {
return [key, (document.querySelector(`input[name="${key}"]`) as HTMLInputElement)?.value || value]; if (key !== "routing_strategy_args" && key !== "routing_strategy") {
} return [
else if (key == "routing_strategy") { key,
return [key, selectedStrategy] (
} document.querySelector(
else if (key == "routing_strategy_args" && selectedStrategy == "latency-based-routing") { `input[name="${key}"]`
let setRoutingStrategyArgs: routingStrategyArgs = {} ) as HTMLInputElement
)?.value || value,
];
} else if (key == "routing_strategy") {
return [key, selectedStrategy];
} else if (
key == "routing_strategy_args" &&
selectedStrategy == "latency-based-routing"
) {
let setRoutingStrategyArgs: routingStrategyArgs = {};
const lowestLatencyBufferElement = document.querySelector(`input[name="lowest_latency_buffer"]`) as HTMLInputElement; const lowestLatencyBufferElement = document.querySelector(
const ttlElement = document.querySelector(`input[name="ttl"]`) as HTMLInputElement; `input[name="lowest_latency_buffer"]`
) as HTMLInputElement;
const ttlElement = document.querySelector(
`input[name="ttl"]`
) as HTMLInputElement;
if (lowestLatencyBufferElement?.value) { if (lowestLatencyBufferElement?.value) {
setRoutingStrategyArgs["lowest_latency_buffer"] = Number(lowestLatencyBufferElement.value) setRoutingStrategyArgs["lowest_latency_buffer"] = Number(
lowestLatencyBufferElement.value
);
} }
if (ttlElement?.value) { if (ttlElement?.value) {
setRoutingStrategyArgs["ttl"] = Number(ttlElement.value) setRoutingStrategyArgs["ttl"] = Number(ttlElement.value);
} }
console.log(`setRoutingStrategyArgs: ${setRoutingStrategyArgs}`) console.log(`setRoutingStrategyArgs: ${setRoutingStrategyArgs}`);
return [ return ["routing_strategy_args", setRoutingStrategyArgs];
"routing_strategy_args", setRoutingStrategyArgs
]
} }
return null; return null;
}).filter(entry => entry !== null && entry !== undefined) as Iterable<[string, unknown]> })
.filter((entry) => entry !== null && entry !== undefined) as Iterable<
[string, unknown]
>
); );
console.log("updatedVariables", updatedVariables); console.log("updatedVariables", updatedVariables);
const payload = { const payload = {
router_settings: updatedVariables router_settings: updatedVariables,
}; };
try { try {
@ -267,19 +339,17 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
message.success("router settings updated successfully"); message.success("router settings updated successfully");
}; };
if (!accessToken) { if (!accessToken) {
return null; return null;
} }
return ( return (
<div className="w-full mx-4"> <div className="w-full mx-4">
<TabGroup className="gap-2 p-8 h-[75vh] w-full mt-2"> <TabGroup className="gap-2 p-8 h-[75vh] w-full mt-2">
<TabList variant="line" defaultValue="1"> <TabList variant="line" defaultValue="1">
<Tab value="1">General Settings</Tab> <Tab value="1">Loadbalancing</Tab>
<Tab value="2">Fallbacks</Tab> <Tab value="2">Fallbacks</Tab>
<Tab value="3">General</Tab>
</TabList> </TabList>
<TabPanels> <TabPanels>
<TabPanel> <TabPanel>
@ -294,27 +364,55 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{Object.entries(routerSettings).filter(([param, value]) => param != "fallbacks" && param != "context_window_fallbacks" && param != "routing_strategy_args").map(([param, value]) => ( {Object.entries(routerSettings)
.filter(
([param, value]) =>
param != "fallbacks" &&
param != "context_window_fallbacks" &&
param != "routing_strategy_args"
)
.map(([param, value]) => (
<TableRow key={param}> <TableRow key={param}>
<TableCell> <TableCell>
<Text>{param}</Text> <Text>{param}</Text>
<p style={{fontSize: '0.65rem', color: '#808080', fontStyle: 'italic'}} className="mt-1">{paramExplanation[param]}</p> <p
style={{
fontSize: "0.65rem",
color: "#808080",
fontStyle: "italic",
}}
className="mt-1"
>
{paramExplanation[param]}
</p>
</TableCell> </TableCell>
<TableCell> <TableCell>
{ {param == "routing_strategy" ? (
param == "routing_strategy" ? <Select2
<Select2 defaultValue={value} className="w-full max-w-md" onValueChange={setSelectedStrategy}> defaultValue={value}
<SelectItem value="usage-based-routing">usage-based-routing</SelectItem> className="w-full max-w-md"
<SelectItem value="latency-based-routing">latency-based-routing</SelectItem> onValueChange={setSelectedStrategy}
<SelectItem value="simple-shuffle">simple-shuffle</SelectItem> >
</Select2> : <SelectItem value="usage-based-routing">
usage-based-routing
</SelectItem>
<SelectItem value="latency-based-routing">
latency-based-routing
</SelectItem>
<SelectItem value="simple-shuffle">
simple-shuffle
</SelectItem>
</Select2>
) : (
<TextInput <TextInput
name={param} name={param}
defaultValue={ defaultValue={
typeof value === 'object' ? JSON.stringify(value, null, 2) : value.toString() typeof value === "object"
? JSON.stringify(value, null, 2)
: value.toString()
} }
/> />
} )}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -323,15 +421,21 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
<AccordionHero <AccordionHero
selectedStrategy={selectedStrategy} selectedStrategy={selectedStrategy}
strategyArgs={ strategyArgs={
routerSettings && routerSettings['routing_strategy_args'] && Object.keys(routerSettings['routing_strategy_args']).length > 0 routerSettings &&
? routerSettings['routing_strategy_args'] routerSettings["routing_strategy_args"] &&
Object.keys(routerSettings["routing_strategy_args"])
.length > 0
? routerSettings["routing_strategy_args"]
: defaultLowestLatencyArgs // default value when keys length is 0 : defaultLowestLatencyArgs // default value when keys length is 0
} }
paramExplanation={paramExplanation} paramExplanation={paramExplanation}
/> />
</Card> </Card>
<Col> <Col>
<Button className="mt-2" onClick={() => handleSaveChanges(routerSettings)}> <Button
className="mt-2"
onClick={() => handleSaveChanges(routerSettings)}
>
Save Changes Save Changes
</Button> </Button>
</Col> </Col>
@ -347,15 +451,21 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
</TableHead> </TableHead>
<TableBody> <TableBody>
{ {routerSettings["fallbacks"] &&
routerSettings["fallbacks"] && routerSettings["fallbacks"].map(
routerSettings["fallbacks"].map((item: Object, index: number) => (item: Object, index: number) =>
Object.entries(item).map(([key, value]) => ( Object.entries(item).map(([key, value]) => (
<TableRow key={index.toString() + key}> <TableRow key={index.toString() + key}>
<TableCell>{key}</TableCell> <TableCell>{key}</TableCell>
<TableCell>{Array.isArray(value) ? value.join(', ') : value}</TableCell>
<TableCell> <TableCell>
<Button onClick={() => testFallbackModelResponse(key, accessToken)}> {Array.isArray(value) ? value.join(", ") : value}
</TableCell>
<TableCell>
<Button
onClick={() =>
testFallbackModelResponse(key, accessToken)
}
>
Test Fallback Test Fallback
</Button> </Button>
</TableCell> </TableCell>
@ -368,11 +478,22 @@ const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))
) )}
}
</TableBody> </TableBody>
</Table> </Table>
<AddFallbacks models={modelData?.data ? modelData.data.map((data: any) => data.model_name) : []} accessToken={accessToken} routerSettings={routerSettings} setRouterSettings={setRouterSettings}/> <AddFallbacks
models={
modelData?.data
? modelData.data.map((data: any) => data.model_name)
: []
}
accessToken={accessToken}
routerSettings={routerSettings}
setRouterSettings={setRouterSettings}
/>
</TabPanel>
<TabPanel>
<h1>General settings for litellm proxy</h1>
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
</TabGroup> </TabGroup>