Spaces:
Paused
Paused
| 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; | |