Spaces:
Sleeping
Sleeping
import React, { useState, useEffect } from "react"; | |
import { | |
Card, | |
Title, | |
Subtitle, | |
Table, | |
TableHead, | |
TableRow, | |
Badge, | |
TableHeaderCell, | |
TableCell, | |
TableBody, | |
Metric, | |
Text, | |
Grid, | |
Button, | |
TextInput, | |
Select as Select2, | |
SelectItem, | |
Col, | |
Accordion, | |
AccordionBody, | |
AccordionHeader, | |
AccordionList, | |
} from "@tremor/react"; | |
import { | |
TabPanel, | |
TabPanels, | |
TabGroup, | |
TabList, | |
Tab, | |
Icon, | |
} from "@tremor/react"; | |
import { | |
getCallbacksCall, | |
setCallbacksCall, | |
getGeneralSettingsCall, | |
serviceHealthCheck, | |
updateConfigFieldSetting, | |
deleteConfigFieldSetting, | |
} from "./networking"; | |
import { | |
Modal, | |
Form, | |
Input, | |
Select, | |
Button as Button2, | |
message, | |
InputNumber, | |
} from "antd"; | |
import { | |
InformationCircleIcon, | |
PencilAltIcon, | |
PencilIcon, | |
StatusOnlineIcon, | |
TrashIcon, | |
RefreshIcon, | |
CheckCircleIcon, | |
XCircleIcon, | |
QuestionMarkCircleIcon, | |
} from "@heroicons/react/outline"; | |
import AddFallbacks from "./add_fallbacks"; | |
import openai from "openai"; | |
import Paragraph from "antd/es/skeleton/Paragraph"; | |
interface GeneralSettingsPageProps { | |
accessToken: string | null; | |
userRole: string | null; | |
userID: string | null; | |
modelData: any; | |
} | |
async function testFallbackModelResponse( | |
selectedModel: string, | |
accessToken: string | |
) { | |
// base url should be the current base_url | |
const isLocal = process.env.NODE_ENV === "development"; | |
if (isLocal != true) { | |
console.log = function() {}; | |
} | |
console.log("isLocal:", isLocal); | |
const proxyBaseUrl = isLocal | |
? "http://localhost:4000" | |
: window.location.origin; | |
const client = new openai.OpenAI({ | |
apiKey: accessToken, // Replace with your OpenAI API key | |
baseURL: proxyBaseUrl, // Replace with your OpenAI API base URL | |
dangerouslyAllowBrowser: true, // using a temporary litellm proxy key | |
}); | |
try { | |
const response = await client.chat.completions.create({ | |
model: selectedModel, | |
messages: [ | |
{ | |
role: "user", | |
content: "Hi, this is a test message", | |
}, | |
], | |
// @ts-ignore | |
mock_testing_fallbacks: true, | |
}); | |
message.success( | |
<span> | |
Test model=<strong>{selectedModel}</strong>, received model= | |
<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> | |
); | |
} catch (error) { | |
message.error( | |
`Error occurred while generating model response. Please try again. Error: ${error}`, | |
20 | |
); | |
} | |
} | |
interface AccordionHeroProps { | |
selectedStrategy: string | null; | |
strategyArgs: routingStrategyArgs; | |
paramExplanation: { [key: string]: string }; | |
} | |
interface routingStrategyArgs { | |
ttl?: number; | |
lowest_latency_buffer?: number; | |
} | |
interface generalSettingsItem { | |
field_name: string; | |
field_type: string; | |
field_value: any; | |
field_description: string; | |
stored_in_db: boolean | null; | |
} | |
const defaultLowestLatencyArgs: routingStrategyArgs = { | |
ttl: 3600, | |
lowest_latency_buffer: 0, | |
}; | |
export const AccordionHero: React.FC<AccordionHeroProps> = ({ | |
selectedStrategy, | |
strategyArgs, | |
paramExplanation, | |
}) => ( | |
<Accordion> | |
<AccordionHeader className="text-sm font-medium text-tremor-content-strong dark:text-dark-tremor-content-strong"> | |
Routing Strategy Specific Args | |
</AccordionHeader> | |
<AccordionBody> | |
{selectedStrategy == "latency-based-routing" ? ( | |
<Card> | |
<Table> | |
<TableHead> | |
<TableRow> | |
<TableHeaderCell>Setting</TableHeaderCell> | |
<TableHeaderCell>Value</TableHeaderCell> | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
{Object.entries(strategyArgs).map(([param, value]) => ( | |
<TableRow key={param}> | |
<TableCell> | |
<Text>{param}</Text> | |
<p | |
style={{ | |
fontSize: "0.65rem", | |
color: "#808080", | |
fontStyle: "italic", | |
}} | |
className="mt-1" | |
> | |
{paramExplanation[param]} | |
</p> | |
</TableCell> | |
<TableCell> | |
<TextInput | |
name={param} | |
defaultValue={ | |
typeof value === "object" | |
? JSON.stringify(value, null, 2) | |
: value.toString() | |
} | |
/> | |
</TableCell> | |
</TableRow> | |
))} | |
</TableBody> | |
</Table> | |
</Card> | |
) : ( | |
<Text>No specific settings</Text> | |
)} | |
</AccordionBody> | |
</Accordion> | |
); | |
const GeneralSettings: React.FC<GeneralSettingsPageProps> = ({ | |
accessToken, | |
userRole, | |
userID, | |
modelData, | |
}) => { | |
const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>( | |
{} | |
); | |
const [generalSettingsDict, setGeneralSettingsDict] = useState<{ | |
[key: string]: any; | |
}>({}); | |
const [generalSettings, setGeneralSettings] = useState<generalSettingsItem[]>( | |
[] | |
); | |
const [isModalVisible, setIsModalVisible] = useState(false); | |
const [form] = Form.useForm(); | |
const [selectedCallback, setSelectedCallback] = useState<string | null>(null); | |
const [selectedStrategy, setSelectedStrategy] = useState<string | null>(null); | |
const [strategySettings, setStrategySettings] = | |
useState<routingStrategyArgs | null>(null); | |
let paramExplanation: { [key: string]: string } = { | |
routing_strategy_args: "(dict) Arguments to pass to the routing strategy", | |
routing_strategy: "(string) Routing strategy to use", | |
allowed_fails: | |
"(int) Number of times a deployment can fail before being added to cooldown", | |
cooldown_time: | |
"(int) time in seconds to cooldown a deployment after failure", | |
num_retries: "(int) Number of retries for failed requests. Defaults to 0.", | |
timeout: "(float) Timeout for requests. Defaults to None.", | |
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(() => { | |
if (!accessToken || !userRole || !userID) { | |
return; | |
} | |
getCallbacksCall(accessToken, userID, userRole).then((data) => { | |
console.log("callbacks", data); | |
let router_settings = data.router_settings; | |
// remove "model_group_retry_policy" from general_settings if exists | |
if ("model_group_retry_policy" in router_settings) { | |
delete router_settings["model_group_retry_policy"]; | |
} | |
setRouterSettings(router_settings); | |
}); | |
getGeneralSettingsCall(accessToken).then((data) => { | |
let general_settings = data; | |
setGeneralSettings(general_settings); | |
}); | |
}, [accessToken, userRole, userID]); | |
const handleAddCallback = () => { | |
console.log("Add callback clicked"); | |
setIsModalVisible(true); | |
}; | |
const handleCancel = () => { | |
setIsModalVisible(false); | |
form.resetFields(); | |
setSelectedCallback(null); | |
}; | |
const deleteFallbacks = async (key: string) => { | |
/** | |
* pop the key from the Object, if it exists | |
*/ | |
if (!accessToken) { | |
return; | |
} | |
console.log(`received key: ${key}`); | |
console.log(`routerSettings['fallbacks']: ${routerSettings["fallbacks"]}`); | |
const updatedFallbacks = routerSettings["fallbacks"] | |
.map((dict: { [key: string]: any }) => { | |
if (key in dict) { | |
delete dict[key]; | |
} | |
return dict; | |
}) | |
.filter((dict: { [key: string]: any }) => Object.keys(dict).length > 0); | |
const updatedSettings = { | |
...routerSettings, | |
fallbacks: updatedFallbacks | |
}; | |
const payload = { | |
router_settings: updatedSettings, | |
}; | |
try { | |
await setCallbacksCall(accessToken, payload); | |
setRouterSettings(updatedSettings); | |
message.success("Router settings updated successfully"); | |
} catch (error) { | |
message.error("Failed to update router settings: " + error, 20); | |
} | |
}; | |
const handleInputChange = (fieldName: string, newValue: any) => { | |
// Update the value in the state | |
const updatedSettings = generalSettings.map((setting) => | |
setting.field_name === fieldName | |
? { ...setting, field_value: newValue } | |
: setting | |
); | |
setGeneralSettings(updatedSettings); | |
}; | |
const handleUpdateField = (fieldName: string, idx: number) => { | |
if (!accessToken) { | |
return; | |
} | |
let fieldValue = generalSettings[idx].field_value; | |
if (fieldValue == null || fieldValue == undefined) { | |
return; | |
} | |
try { | |
updateConfigFieldSetting(accessToken, fieldName, fieldValue); | |
// update value in state | |
const updatedSettings = generalSettings.map((setting) => | |
setting.field_name === fieldName | |
? { ...setting, stored_in_db: true } | |
: setting | |
); | |
setGeneralSettings(updatedSettings); | |
} catch (error) { | |
// do something | |
} | |
}; | |
const handleResetField = (fieldName: string, idx: number) => { | |
if (!accessToken) { | |
return; | |
} | |
try { | |
deleteConfigFieldSetting(accessToken, fieldName); | |
// update value in state | |
const updatedSettings = generalSettings.map((setting) => | |
setting.field_name === fieldName | |
? { ...setting, stored_in_db: null, field_value: null } | |
: setting | |
); | |
setGeneralSettings(updatedSettings); | |
} catch (error) { | |
// do something | |
} | |
}; | |
const handleSaveChanges = (router_settings: any) => { | |
if (!accessToken) { | |
return; | |
} | |
console.log("router_settings", router_settings); | |
const updatedVariables = Object.fromEntries( | |
Object.entries(router_settings) | |
.map(([key, value]) => { | |
if (key !== "routing_strategy_args" && key !== "routing_strategy") { | |
return [ | |
key, | |
( | |
document.querySelector( | |
`input[name="${key}"]` | |
) 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 ttlElement = document.querySelector( | |
`input[name="ttl"]` | |
) as HTMLInputElement; | |
if (lowestLatencyBufferElement?.value) { | |
setRoutingStrategyArgs["lowest_latency_buffer"] = Number( | |
lowestLatencyBufferElement.value | |
); | |
} | |
if (ttlElement?.value) { | |
setRoutingStrategyArgs["ttl"] = Number(ttlElement.value); | |
} | |
console.log(`setRoutingStrategyArgs: ${setRoutingStrategyArgs}`); | |
return ["routing_strategy_args", setRoutingStrategyArgs]; | |
} | |
return null; | |
}) | |
.filter((entry) => entry !== null && entry !== undefined) as Iterable< | |
[string, unknown] | |
> | |
); | |
console.log("updatedVariables", updatedVariables); | |
const payload = { | |
router_settings: updatedVariables, | |
}; | |
try { | |
setCallbacksCall(accessToken, payload); | |
} catch (error) { | |
message.error("Failed to update router settings: " + error, 20); | |
} | |
message.success("router settings updated successfully"); | |
}; | |
if (!accessToken) { | |
return null; | |
} | |
return ( | |
<div className="w-full mx-4"> | |
<TabGroup className="gap-2 p-8 h-[75vh] w-full mt-2"> | |
<TabList variant="line" defaultValue="1"> | |
<Tab value="1">Loadbalancing</Tab> | |
<Tab value="2">Fallbacks</Tab> | |
<Tab value="3">General</Tab> | |
</TabList> | |
<TabPanels> | |
<TabPanel> | |
<Grid numItems={1} className="gap-2 p-8 w-full mt-2"> | |
<Title>Router Settings</Title> | |
<Card> | |
<Table> | |
<TableHead> | |
<TableRow> | |
<TableHeaderCell>Setting</TableHeaderCell> | |
<TableHeaderCell>Value</TableHeaderCell> | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
{Object.entries(routerSettings) | |
.filter( | |
([param, value]) => | |
param != "fallbacks" && | |
param != "context_window_fallbacks" && | |
param != "routing_strategy_args" | |
) | |
.map(([param, value]) => ( | |
<TableRow key={param}> | |
<TableCell> | |
<Text>{param}</Text> | |
<p | |
style={{ | |
fontSize: "0.65rem", | |
color: "#808080", | |
fontStyle: "italic", | |
}} | |
className="mt-1" | |
> | |
{paramExplanation[param]} | |
</p> | |
</TableCell> | |
<TableCell> | |
{param == "routing_strategy" ? ( | |
<Select2 | |
defaultValue={value} | |
className="w-full max-w-md" | |
onValueChange={setSelectedStrategy} | |
> | |
<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 | |
name={param} | |
defaultValue={ | |
typeof value === "object" | |
? JSON.stringify(value, null, 2) | |
: value.toString() | |
} | |
/> | |
)} | |
</TableCell> | |
</TableRow> | |
))} | |
</TableBody> | |
</Table> | |
<AccordionHero | |
selectedStrategy={selectedStrategy} | |
strategyArgs={ | |
routerSettings && | |
routerSettings["routing_strategy_args"] && | |
Object.keys(routerSettings["routing_strategy_args"]) | |
.length > 0 | |
? routerSettings["routing_strategy_args"] | |
: defaultLowestLatencyArgs // default value when keys length is 0 | |
} | |
paramExplanation={paramExplanation} | |
/> | |
</Card> | |
<Col> | |
<Button | |
className="mt-2" | |
onClick={() => handleSaveChanges(routerSettings)} | |
> | |
Save Changes | |
</Button> | |
</Col> | |
</Grid> | |
</TabPanel> | |
<TabPanel> | |
<Table> | |
<TableHead> | |
<TableRow> | |
<TableHeaderCell>Model Name</TableHeaderCell> | |
<TableHeaderCell>Fallbacks</TableHeaderCell> | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
{routerSettings["fallbacks"] && | |
routerSettings["fallbacks"].map( | |
(item: Object, index: number) => | |
Object.entries(item).map(([key, value]) => ( | |
<TableRow key={index.toString() + key}> | |
<TableCell>{key}</TableCell> | |
<TableCell> | |
{Array.isArray(value) ? value.join(", ") : value} | |
</TableCell> | |
<TableCell> | |
<Button | |
onClick={() => | |
testFallbackModelResponse(key, accessToken) | |
} | |
> | |
Test Fallback | |
</Button> | |
</TableCell> | |
<TableCell> | |
<Icon | |
icon={TrashIcon} | |
size="sm" | |
onClick={() => deleteFallbacks(key)} | |
/> | |
</TableCell> | |
</TableRow> | |
)) | |
)} | |
</TableBody> | |
</Table> | |
<AddFallbacks | |
models={ | |
modelData?.data | |
? modelData.data.map((data: any) => data.model_name) | |
: [] | |
} | |
accessToken={accessToken} | |
routerSettings={routerSettings} | |
setRouterSettings={setRouterSettings} | |
/> | |
</TabPanel> | |
<TabPanel> | |
<Card> | |
<Table> | |
<TableHead> | |
<TableRow> | |
<TableHeaderCell>Setting</TableHeaderCell> | |
<TableHeaderCell>Value</TableHeaderCell> | |
<TableHeaderCell>Status</TableHeaderCell> | |
<TableHeaderCell>Action</TableHeaderCell> | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
{generalSettings.filter((value) => value.field_type !== "TypedDictionary").map((value, index) => ( | |
<TableRow key={index}> | |
<TableCell> | |
<Text>{value.field_name}</Text> | |
<p | |
style={{ | |
fontSize: "0.65rem", | |
color: "#808080", | |
fontStyle: "italic", | |
}} | |
className="mt-1" | |
> | |
{value.field_description} | |
</p> | |
</TableCell> | |
<TableCell> | |
{value.field_type == "Integer" ? ( | |
<InputNumber | |
step={1} | |
value={value.field_value} | |
onChange={(newValue) => | |
handleInputChange(value.field_name, newValue) | |
} // Handle value change | |
/> | |
) : null} | |
</TableCell> | |
<TableCell> | |
{value.stored_in_db == true ? ( | |
<Badge icon={CheckCircleIcon} className="text-white"> | |
In DB | |
</Badge> | |
) : value.stored_in_db == false ? ( | |
<Badge className="text-gray bg-white outline"> | |
In Config | |
</Badge> | |
) : ( | |
<Badge className="text-gray bg-white outline"> | |
Not Set | |
</Badge> | |
)} | |
</TableCell> | |
<TableCell> | |
<Button | |
onClick={() => | |
handleUpdateField(value.field_name, index) | |
} | |
> | |
Update | |
</Button> | |
<Icon | |
icon={TrashIcon} | |
color="red" | |
onClick={() => | |
handleResetField(value.field_name, index) | |
} | |
> | |
Reset | |
</Icon> | |
</TableCell> | |
</TableRow> | |
))} | |
</TableBody> | |
</Table> | |
</Card> | |
</TabPanel> | |
</TabPanels> | |
</TabGroup> | |
</div> | |
); | |
}; | |
export default GeneralSettings; | |