|
import React, { useState, useEffect } from "react"; |
|
import { |
|
Card, |
|
Title, |
|
Subtitle, |
|
Table, |
|
TableHead, |
|
TableRow, |
|
TableHeaderCell, |
|
TableCell, |
|
TableBody, |
|
Metric, |
|
Text, |
|
Grid, |
|
Accordion, |
|
AccordionHeader, |
|
AccordionBody, |
|
} from "@tremor/react"; |
|
import ConditionalPublicModelName from "./add_model/conditional_public_model_name"; |
|
import LiteLLMModelNameField from "./add_model/litellm_model_name"; |
|
import AdvancedSettings from "./add_model/advanced_settings"; |
|
import ProviderSpecificFields from "./add_model/provider_specific_fields"; |
|
import { handleAddModelSubmit } from "./add_model/handle_add_model_submit"; |
|
import EditModelModal from "./edit_model/edit_model_modal"; |
|
import { |
|
TabPanel, |
|
TabPanels, |
|
TabGroup, |
|
TabList, |
|
Tab, |
|
TextInput, |
|
Icon, |
|
DateRangePicker, |
|
} from "@tremor/react"; |
|
import { |
|
Select, |
|
SelectItem, |
|
MultiSelect, |
|
MultiSelectItem, |
|
DateRangePickerValue, |
|
} from "@tremor/react"; |
|
import { |
|
modelInfoCall, |
|
userGetRequesedtModelsCall, |
|
modelCreateCall, |
|
Model, |
|
modelCostMap, |
|
modelDeleteCall, |
|
healthCheckCall, |
|
modelUpdateCall, |
|
modelMetricsCall, |
|
streamingModelMetricsCall, |
|
modelExceptionsCall, |
|
modelMetricsSlowResponsesCall, |
|
getCallbacksCall, |
|
setCallbacksCall, |
|
modelSettingsCall, |
|
adminGlobalActivityExceptions, |
|
adminGlobalActivityExceptionsPerDeployment, |
|
allEndUsersCall, |
|
} from "./networking"; |
|
import { BarChart, AreaChart } from "@tremor/react"; |
|
import { |
|
Button as Button2, |
|
Modal, |
|
Popover, |
|
Form, |
|
Input, |
|
Select as AntdSelect, |
|
InputNumber, |
|
message, |
|
Descriptions, |
|
Tooltip, |
|
Space, |
|
Row, |
|
Col, |
|
} from "antd"; |
|
import { Badge, BadgeDelta, Button } from "@tremor/react"; |
|
import RequestAccess from "./request_model_access"; |
|
import { Typography } from "antd"; |
|
import TextArea from "antd/es/input/TextArea"; |
|
import { |
|
InformationCircleIcon, |
|
PencilAltIcon, |
|
PencilIcon, |
|
StatusOnlineIcon, |
|
TrashIcon, |
|
RefreshIcon, |
|
CheckCircleIcon, |
|
XCircleIcon, |
|
FilterIcon, |
|
ChevronUpIcon, |
|
ChevronDownIcon, |
|
} from "@heroicons/react/outline"; |
|
import DeleteModelButton from "./delete_model_button"; |
|
const { Title: Title2, Link } = Typography; |
|
import { UploadOutlined } from "@ant-design/icons"; |
|
import type { UploadProps } from "antd"; |
|
import { Upload } from "antd"; |
|
import TimeToFirstToken from "./model_metrics/time_to_first_token"; |
|
import DynamicFields from "./model_add/dynamic_form"; |
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; |
|
import { Providers, provider_map, providerLogoMap, getProviderLogoAndName, getPlaceholder, getProviderModels } from "./provider_info_helpers"; |
|
|
|
interface ModelDashboardProps { |
|
accessToken: string | null; |
|
token: string | null; |
|
userRole: string | null; |
|
userID: string | null; |
|
modelData: any; |
|
keys: any[] | null; |
|
setModelData: any; |
|
premiumUser: boolean; |
|
} |
|
|
|
interface EditModelModalProps { |
|
visible: boolean; |
|
onCancel: () => void; |
|
model: any; |
|
onSubmit: (data: FormData) => void; |
|
} |
|
|
|
interface RetryPolicyObject { |
|
[key: string]: { [retryPolicyKey: string]: number } | undefined; |
|
} |
|
|
|
|
|
interface GlobalExceptionActivityData { |
|
sum_num_rate_limit_exceptions: number; |
|
daily_data: { date: string; num_rate_limit_exceptions: number; }[]; |
|
} |
|
|
|
|
|
|
|
|
|
interface ProviderFields { |
|
field_name: string; |
|
field_type: string; |
|
field_description: string; |
|
field_value: string; |
|
} |
|
|
|
interface ProviderSettings { |
|
name: string; |
|
fields: ProviderFields[]; |
|
} |
|
|
|
|
|
const retry_policy_map: Record<string, string> = { |
|
"BadRequestError (400)": "BadRequestErrorRetries", |
|
"AuthenticationError (401)": "AuthenticationErrorRetries", |
|
"TimeoutError (408)": "TimeoutErrorRetries", |
|
"RateLimitError (429)": "RateLimitErrorRetries", |
|
"ContentPolicyViolationError (400)": "ContentPolicyViolationErrorRetries", |
|
"InternalServerError (500)": "InternalServerErrorRetries", |
|
}; |
|
|
|
const ModelDashboard: React.FC<ModelDashboardProps> = ({ |
|
accessToken, |
|
token, |
|
userRole, |
|
userID, |
|
modelData = { data: [] }, |
|
keys, |
|
setModelData, |
|
premiumUser, |
|
}) => { |
|
const [pendingRequests, setPendingRequests] = useState<any[]>([]); |
|
const [form] = Form.useForm(); |
|
const [modelMap, setModelMap] = useState<any>(null); |
|
const [lastRefreshed, setLastRefreshed] = useState(""); |
|
|
|
const [providerModels, setProviderModels] = useState<Array<string>>([]); |
|
|
|
const providers = Object.values(Providers).filter((key) => |
|
isNaN(Number(key)) |
|
); |
|
|
|
const [providerSettings, setProviderSettings] = useState<ProviderSettings[]>( |
|
[] |
|
); |
|
const [selectedProvider, setSelectedProvider] = useState<Providers>(Providers.OpenAI); |
|
const [healthCheckResponse, setHealthCheckResponse] = useState<string>(""); |
|
const [editModalVisible, setEditModalVisible] = useState<boolean>(false); |
|
const [infoModalVisible, setInfoModalVisible] = useState<boolean>(false); |
|
|
|
const [selectedModel, setSelectedModel] = useState<any>(null); |
|
const [availableModelGroups, setAvailableModelGroups] = useState< |
|
Array<string> |
|
>([]); |
|
const [availableProviders, setavailableProviders] = useState< |
|
Array<string> |
|
>([]); |
|
const [selectedModelGroup, setSelectedModelGroup] = useState<string | null>( |
|
null |
|
); |
|
const [modelLatencyMetrics, setModelLatencyMetrics] = useState<any[]>([]); |
|
const [modelMetrics, setModelMetrics] = useState<any[]>([]); |
|
const [modelMetricsCategories, setModelMetricsCategories] = useState<any[]>( |
|
[] |
|
); |
|
const [streamingModelMetrics, setStreamingModelMetrics] = useState<any[]>([]); |
|
const [streamingModelMetricsCategories, setStreamingModelMetricsCategories] = |
|
useState<any[]>([]); |
|
const [modelExceptions, setModelExceptions] = useState<any[]>([]); |
|
const [allExceptions, setAllExceptions] = useState<any[]>([]); |
|
const [failureTableData, setFailureTableData] = useState<any[]>([]); |
|
const [slowResponsesData, setSlowResponsesData] = useState<any[]>([]); |
|
const [dateValue, setDateValue] = useState<DateRangePickerValue>({ |
|
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), |
|
to: new Date(), |
|
}); |
|
|
|
const [modelGroupRetryPolicy, setModelGroupRetryPolicy] = |
|
useState<RetryPolicyObject | null>(null); |
|
const [defaultRetry, setDefaultRetry] = useState<number>(0); |
|
|
|
const [globalExceptionData, setGlobalExceptionData] = useState<GlobalExceptionActivityData>({} as GlobalExceptionActivityData); |
|
const [globalExceptionPerDeployment, setGlobalExceptionPerDeployment] = useState<any[]>([]); |
|
|
|
const [showAdvancedFilters, setShowAdvancedFilters] = useState<boolean>(false); |
|
const [selectedAPIKey, setSelectedAPIKey] = useState<any | null>(null); |
|
const [selectedCustomer, setSelectedCustomer] = useState<any | null>(null); |
|
|
|
const [allEndUsers, setAllEndUsers] = useState<any[]>([]); |
|
|
|
|
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState<boolean>(false); |
|
|
|
const setProviderModelsFn = (provider: Providers) => { |
|
const _providerModels = getProviderModels(provider, modelMap); |
|
setProviderModels(_providerModels); |
|
console.log(`providerModels: ${_providerModels}`); |
|
}; |
|
|
|
const updateModelMetrics = async ( |
|
modelGroup: string | null, |
|
startTime: Date | undefined, |
|
endTime: Date | undefined, |
|
) => { |
|
console.log("Updating model metrics for group:", modelGroup); |
|
if (!accessToken || !userID || !userRole || !startTime || !endTime) { |
|
return; |
|
} |
|
console.log( |
|
"inside updateModelMetrics - startTime:", |
|
startTime, |
|
"endTime:", |
|
endTime |
|
); |
|
setSelectedModelGroup(modelGroup); |
|
|
|
let selected_token = selectedAPIKey?.token; |
|
if (selected_token === undefined) { |
|
selected_token = null; |
|
} |
|
|
|
let selected_customer = selectedCustomer; |
|
if (selected_customer === undefined) { |
|
selected_customer = null; |
|
} |
|
|
|
|
|
startTime.setHours(0); |
|
startTime.setMinutes(0); |
|
startTime.setSeconds(0); |
|
|
|
endTime.setHours(23); |
|
endTime.setMinutes(59); |
|
endTime.setSeconds(59); |
|
|
|
|
|
try { |
|
const modelMetricsResponse = await modelMetricsCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
modelGroup, |
|
startTime.toISOString(), |
|
endTime.toISOString(), |
|
selected_token, |
|
selected_customer |
|
); |
|
console.log("Model metrics response:", modelMetricsResponse); |
|
|
|
|
|
setModelMetrics(modelMetricsResponse.data); |
|
setModelMetricsCategories(modelMetricsResponse.all_api_bases); |
|
|
|
const streamingModelMetricsResponse = await streamingModelMetricsCall( |
|
accessToken, |
|
modelGroup, |
|
startTime.toISOString(), |
|
endTime.toISOString() |
|
); |
|
|
|
|
|
setStreamingModelMetrics(streamingModelMetricsResponse.data); |
|
setStreamingModelMetricsCategories( |
|
streamingModelMetricsResponse.all_api_bases |
|
); |
|
|
|
const modelExceptionsResponse = await modelExceptionsCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
modelGroup, |
|
startTime.toISOString(), |
|
endTime.toISOString(), |
|
selected_token, |
|
selected_customer |
|
); |
|
console.log("Model exceptions response:", modelExceptionsResponse); |
|
setModelExceptions(modelExceptionsResponse.data); |
|
setAllExceptions(modelExceptionsResponse.exception_types); |
|
|
|
const slowResponses = await modelMetricsSlowResponsesCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
modelGroup, |
|
startTime.toISOString(), |
|
endTime.toISOString(), |
|
selected_token, |
|
selected_customer |
|
); |
|
|
|
console.log("slowResponses:", slowResponses); |
|
|
|
setSlowResponsesData(slowResponses); |
|
|
|
|
|
if (modelGroup) { |
|
const dailyExceptions = await adminGlobalActivityExceptions( |
|
accessToken, |
|
startTime?.toISOString().split('T')[0], |
|
endTime?.toISOString().split('T')[0], |
|
modelGroup, |
|
); |
|
|
|
setGlobalExceptionData(dailyExceptions); |
|
|
|
const dailyExceptionsPerDeplyment = await adminGlobalActivityExceptionsPerDeployment( |
|
accessToken, |
|
startTime?.toISOString().split('T')[0], |
|
endTime?.toISOString().split('T')[0], |
|
modelGroup, |
|
) |
|
|
|
setGlobalExceptionPerDeployment(dailyExceptionsPerDeplyment); |
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
console.error("Failed to fetch model metrics", error); |
|
} |
|
}; |
|
|
|
|
|
useEffect(() => { |
|
updateModelMetrics( |
|
selectedModelGroup, |
|
dateValue.from, |
|
dateValue.to |
|
); |
|
}, [selectedAPIKey, selectedCustomer]); |
|
|
|
function formatCreatedAt(createdAt: string | null) { |
|
if (createdAt) { |
|
const date = new Date(createdAt); |
|
const options = { month: "long", day: "numeric", year: "numeric" }; |
|
return date.toLocaleDateString("en-US"); |
|
} |
|
return null; |
|
} |
|
|
|
const handleEditClick = (model: any) => { |
|
setSelectedModel(model); |
|
setEditModalVisible(true); |
|
}; |
|
|
|
const handleInfoClick = (model: any) => { |
|
setSelectedModel(model); |
|
setInfoModalVisible(true); |
|
}; |
|
|
|
const handleEditCancel = () => { |
|
setEditModalVisible(false); |
|
setSelectedModel(null); |
|
}; |
|
|
|
const handleInfoCancel = () => { |
|
setInfoModalVisible(false); |
|
setSelectedModel(null); |
|
}; |
|
|
|
const handleEditSubmit = async (formValues: Record<string, any>) => { |
|
|
|
|
|
console.log("handleEditSubmit:", formValues); |
|
if (accessToken == null) { |
|
return; |
|
} |
|
|
|
let newLiteLLMParams: Record<string, any> = {}; |
|
let model_info_model_id = null; |
|
|
|
if (formValues.input_cost_per_token) { |
|
|
|
formValues.input_cost_per_token = Number(formValues.input_cost_per_token) / 1_000_000; |
|
} |
|
if (formValues.output_cost_per_token) { |
|
|
|
formValues.output_cost_per_token = Number(formValues.output_cost_per_token) / 1_000_000; |
|
} |
|
|
|
|
|
for (const [key, value] of Object.entries(formValues)) { |
|
if (key !== "model_id") { |
|
newLiteLLMParams[key] = value; |
|
} else { |
|
model_info_model_id = value; |
|
} |
|
} |
|
|
|
let payload = { |
|
litellm_params: newLiteLLMParams, |
|
model_info: { |
|
id: model_info_model_id, |
|
}, |
|
}; |
|
|
|
console.log("handleEditSubmit payload:", payload); |
|
|
|
try { |
|
let newModelValue = await modelUpdateCall(accessToken, payload); |
|
message.success( |
|
"Model updated successfully, restart server to see updates" |
|
); |
|
|
|
setEditModalVisible(false); |
|
setSelectedModel(null); |
|
} catch (error) { |
|
console.log(`Error occurred`); |
|
} |
|
}; |
|
|
|
const uploadProps: UploadProps = { |
|
name: "file", |
|
accept: ".json", |
|
beforeUpload: (file) => { |
|
if (file.type === "application/json") { |
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
if (e.target) { |
|
const jsonStr = e.target.result as string; |
|
form.setFieldsValue({ vertex_credentials: jsonStr }); |
|
} |
|
}; |
|
reader.readAsText(file); |
|
} |
|
|
|
return false; |
|
}, |
|
onChange(info) { |
|
if (info.file.status !== "uploading") { |
|
console.log(info.file, info.fileList); |
|
} |
|
if (info.file.status === "done") { |
|
message.success(`${info.file.name} file uploaded successfully`); |
|
} else if (info.file.status === "error") { |
|
message.error(`${info.file.name} file upload failed.`); |
|
} |
|
}, |
|
}; |
|
|
|
const handleRefreshClick = () => { |
|
|
|
const currentDate = new Date(); |
|
setLastRefreshed(currentDate.toLocaleString()); |
|
}; |
|
|
|
const handleSaveRetrySettings = async () => { |
|
if (!accessToken) { |
|
console.error("Access token is missing"); |
|
return; |
|
} |
|
|
|
console.log("new modelGroupRetryPolicy:", modelGroupRetryPolicy); |
|
|
|
try { |
|
const payload = { |
|
router_settings: { |
|
model_group_retry_policy: modelGroupRetryPolicy, |
|
}, |
|
}; |
|
|
|
await setCallbacksCall(accessToken, payload); |
|
message.success("Retry settings saved successfully"); |
|
} catch (error) { |
|
console.error("Failed to save retry settings:", error); |
|
message.error("Failed to save retry settings"); |
|
} |
|
}; |
|
|
|
useEffect(() => { |
|
if (!accessToken || !token || !userRole || !userID) { |
|
return; |
|
} |
|
const fetchData = async () => { |
|
try { |
|
const _providerSettings = await modelSettingsCall(accessToken); |
|
setProviderSettings(_providerSettings); |
|
|
|
|
|
const modelDataResponse = await modelInfoCall( |
|
accessToken, |
|
userID, |
|
userRole |
|
); |
|
console.log("Model data response:", modelDataResponse.data); |
|
setModelData(modelDataResponse); |
|
|
|
|
|
let all_model_groups: Set<string> = new Set(); |
|
for (let i = 0; i < modelDataResponse.data.length; i++) { |
|
const model = modelDataResponse.data[i]; |
|
all_model_groups.add(model.model_name); |
|
} |
|
console.log("all_model_groups:", all_model_groups); |
|
let _array_model_groups = Array.from(all_model_groups); |
|
|
|
_array_model_groups = _array_model_groups.sort(); |
|
|
|
setAvailableModelGroups(_array_model_groups); |
|
|
|
console.log("array_model_groups:", _array_model_groups); |
|
let _initial_model_group = "all"; |
|
if (_array_model_groups.length > 0) { |
|
|
|
_initial_model_group = |
|
_array_model_groups[_array_model_groups.length - 1]; |
|
console.log("_initial_model_group:", _initial_model_group); |
|
|
|
} |
|
|
|
console.log("selectedModelGroup:", selectedModelGroup); |
|
|
|
const modelMetricsResponse = await modelMetricsCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
_initial_model_group, |
|
dateValue.from?.toISOString(), |
|
dateValue.to?.toISOString(), |
|
selectedAPIKey?.token, |
|
selectedCustomer |
|
); |
|
|
|
console.log("Model metrics response:", modelMetricsResponse); |
|
|
|
|
|
setModelMetrics(modelMetricsResponse.data); |
|
setModelMetricsCategories(modelMetricsResponse.all_api_bases); |
|
|
|
const streamingModelMetricsResponse = await streamingModelMetricsCall( |
|
accessToken, |
|
_initial_model_group, |
|
dateValue.from?.toISOString(), |
|
dateValue.to?.toISOString() |
|
); |
|
|
|
|
|
setStreamingModelMetrics(streamingModelMetricsResponse.data); |
|
setStreamingModelMetricsCategories( |
|
streamingModelMetricsResponse.all_api_bases |
|
); |
|
|
|
const modelExceptionsResponse = await modelExceptionsCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
_initial_model_group, |
|
dateValue.from?.toISOString(), |
|
dateValue.to?.toISOString(), |
|
selectedAPIKey?.token, |
|
selectedCustomer |
|
); |
|
console.log("Model exceptions response:", modelExceptionsResponse); |
|
setModelExceptions(modelExceptionsResponse.data); |
|
setAllExceptions(modelExceptionsResponse.exception_types); |
|
|
|
const slowResponses = await modelMetricsSlowResponsesCall( |
|
accessToken, |
|
userID, |
|
userRole, |
|
_initial_model_group, |
|
dateValue.from?.toISOString(), |
|
dateValue.to?.toISOString(), |
|
selectedAPIKey?.token, |
|
selectedCustomer |
|
); |
|
|
|
const dailyExceptions = await adminGlobalActivityExceptions( |
|
accessToken, |
|
dateValue.from?.toISOString().split('T')[0], |
|
dateValue.to?.toISOString().split('T')[0], |
|
_initial_model_group, |
|
); |
|
|
|
setGlobalExceptionData(dailyExceptions); |
|
|
|
const dailyExceptionsPerDeplyment = await adminGlobalActivityExceptionsPerDeployment( |
|
accessToken, |
|
dateValue.from?.toISOString().split('T')[0], |
|
dateValue.to?.toISOString().split('T')[0], |
|
_initial_model_group, |
|
) |
|
|
|
setGlobalExceptionPerDeployment(dailyExceptionsPerDeplyment); |
|
|
|
console.log("dailyExceptions:", dailyExceptions); |
|
|
|
console.log("dailyExceptionsPerDeplyment:", dailyExceptionsPerDeplyment); |
|
|
|
|
|
console.log("slowResponses:", slowResponses); |
|
|
|
setSlowResponsesData(slowResponses); |
|
|
|
let all_end_users_data = await allEndUsersCall(accessToken); |
|
|
|
setAllEndUsers(all_end_users_data?.end_users); |
|
|
|
const routerSettingsInfo = await getCallbacksCall( |
|
accessToken, |
|
userID, |
|
userRole |
|
); |
|
|
|
let router_settings = routerSettingsInfo.router_settings; |
|
|
|
console.log("routerSettingsInfo:", router_settings); |
|
|
|
let model_group_retry_policy = router_settings.model_group_retry_policy; |
|
let default_retries = router_settings.num_retries; |
|
|
|
console.log("model_group_retry_policy:", model_group_retry_policy); |
|
console.log("default_retries:", default_retries); |
|
setModelGroupRetryPolicy(model_group_retry_policy); |
|
setDefaultRetry(default_retries); |
|
} catch (error) { |
|
console.error("There was an error fetching the model data", error); |
|
} |
|
}; |
|
|
|
if (accessToken && token && userRole && userID) { |
|
fetchData(); |
|
} |
|
|
|
const fetchModelMap = async () => { |
|
const data = await modelCostMap(accessToken); |
|
console.log(`received model cost map data: ${Object.keys(data)}`); |
|
setModelMap(data); |
|
}; |
|
if (modelMap == null) { |
|
fetchModelMap(); |
|
} |
|
|
|
handleRefreshClick(); |
|
}, [accessToken, token, userRole, userID, modelMap, lastRefreshed]); |
|
|
|
if (!modelData) { |
|
return <div>Loading...</div>; |
|
} |
|
|
|
if (!accessToken || !token || !userRole || !userID) { |
|
return <div>Loading...</div>; |
|
} |
|
let all_models_on_proxy: any[] = []; |
|
let all_providers: string[] = []; |
|
|
|
|
|
for (let i = 0; i < modelData.data.length; i++) { |
|
let curr_model = modelData.data[i]; |
|
let litellm_model_name = curr_model?.litellm_params?.model; |
|
let custom_llm_provider = curr_model?.litellm_params?.custom_llm_provider; |
|
let model_info = curr_model?.model_info; |
|
|
|
let defaultProvider = "openai"; |
|
let provider = ""; |
|
let input_cost = "Undefined"; |
|
let output_cost = "Undefined"; |
|
let max_tokens = "Undefined"; |
|
let max_input_tokens = "Undefined"; |
|
let cleanedLitellmParams = {}; |
|
|
|
const getProviderFromModel = (model: string) => { |
|
|
|
|
|
|
|
|
|
|
|
console.log(`GET PROVIDER CALLED! - ${modelMap}`); |
|
if (modelMap !== null && modelMap !== undefined) { |
|
if (typeof modelMap == "object" && model in modelMap) { |
|
return modelMap[model]["litellm_provider"]; |
|
} |
|
} |
|
return "openai"; |
|
}; |
|
|
|
|
|
if (litellm_model_name) { |
|
|
|
let splitModel = litellm_model_name.split("/"); |
|
|
|
|
|
let firstElement = splitModel[0]; |
|
|
|
|
|
provider = custom_llm_provider; |
|
if (!provider) { |
|
provider = |
|
splitModel.length === 1 |
|
? getProviderFromModel(litellm_model_name) |
|
: firstElement; |
|
|
|
} |
|
|
|
} else { |
|
|
|
provider = "-"; |
|
} |
|
|
|
if (model_info) { |
|
input_cost = model_info?.input_cost_per_token; |
|
output_cost = model_info?.output_cost_per_token; |
|
max_tokens = model_info?.max_tokens; |
|
max_input_tokens = model_info?.max_input_tokens; |
|
} |
|
|
|
if (curr_model?.litellm_params) { |
|
cleanedLitellmParams = Object.fromEntries( |
|
Object.entries(curr_model?.litellm_params).filter( |
|
([key]) => key !== "model" && key !== "api_base" |
|
) |
|
); |
|
} |
|
|
|
modelData.data[i].provider = provider; |
|
modelData.data[i].input_cost = input_cost; |
|
modelData.data[i].output_cost = output_cost; |
|
modelData.data[i].litellm_model_name = litellm_model_name; |
|
all_providers.push(provider); |
|
|
|
|
|
if (modelData.data[i].input_cost) { |
|
modelData.data[i].input_cost = ( |
|
Number(modelData.data[i].input_cost) * 1000000 |
|
).toFixed(2); |
|
} |
|
|
|
if (modelData.data[i].output_cost) { |
|
modelData.data[i].output_cost = ( |
|
Number(modelData.data[i].output_cost) * 1000000 |
|
).toFixed(2); |
|
} |
|
|
|
modelData.data[i].max_tokens = max_tokens; |
|
modelData.data[i].max_input_tokens = max_input_tokens; |
|
modelData.data[i].api_base = curr_model?.litellm_params?.api_base; |
|
modelData.data[i].cleanedLitellmParams = cleanedLitellmParams; |
|
|
|
all_models_on_proxy.push(curr_model.model_name); |
|
|
|
console.log(modelData.data[i]); |
|
} |
|
|
|
|
|
|
|
if (modelData.data && modelData.data.length > 0) { |
|
modelData.data.sort((a: any, b: any) => { |
|
if (a.provider && b.provider) { |
|
return a.provider.localeCompare(b.provider); |
|
} else if (a.provider && !b.provider) { |
|
return -1; |
|
} else if (!a.provider && b.provider) { |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
}); |
|
} |
|
if (userRole && userRole == "Admin Viewer") { |
|
const { Title, Paragraph } = Typography; |
|
return ( |
|
<div> |
|
<Title level={1}>Access Denied</Title> |
|
<Paragraph> |
|
Ask your proxy admin for access to view all models |
|
</Paragraph> |
|
</div> |
|
); |
|
} |
|
|
|
const runHealthCheck = async () => { |
|
try { |
|
message.info("Running health check..."); |
|
setHealthCheckResponse(""); |
|
const response = await healthCheckCall(accessToken); |
|
setHealthCheckResponse(response); |
|
} catch (error) { |
|
console.error("Error running health check:", error); |
|
setHealthCheckResponse("Error running health check"); |
|
} |
|
}; |
|
|
|
const FilterByContent = ( |
|
<div > |
|
<Text className="mb-1">Select API Key Name</Text> |
|
|
|
{ |
|
premiumUser ? ( |
|
<div> |
|
<Select defaultValue="all-keys"> |
|
<SelectItem |
|
key="all-keys" |
|
value="all-keys" |
|
onClick={() => { |
|
setSelectedAPIKey(null); |
|
}} |
|
> |
|
All Keys |
|
</SelectItem> |
|
{keys?.map((key: any, index: number) => { |
|
if ( |
|
key && |
|
key["key_alias"] !== null && |
|
key["key_alias"].length > 0 |
|
) { |
|
return ( |
|
|
|
<SelectItem |
|
key={index} |
|
value={String(index)} |
|
onClick={() => { |
|
setSelectedAPIKey(key); |
|
}} |
|
> |
|
{key["key_alias"]} |
|
</SelectItem> |
|
); |
|
} |
|
return null; // Add this line to handle the case when the condition is not met |
|
})} |
|
</Select> |
|
|
|
|
|
<Text className="mt-1"> |
|
Select Customer Name |
|
</Text> |
|
|
|
<Select defaultValue="all-customers"> |
|
<SelectItem |
|
key="all-customers" |
|
value="all-customers" |
|
onClick={() => { |
|
setSelectedCustomer(null); |
|
}} |
|
> |
|
All Customers |
|
</SelectItem> |
|
{ |
|
allEndUsers?.map((user: any, index: number) => { |
|
return ( |
|
<SelectItem |
|
key={index} |
|
value={user} |
|
onClick={() => { |
|
setSelectedCustomer(user); |
|
}} |
|
> |
|
{user} |
|
</SelectItem> |
|
); |
|
}) |
|
} |
|
</Select> |
|
|
|
</div> |
|
): ( |
|
<div> |
|
|
|
<Select defaultValue="all-keys"> |
|
<SelectItem |
|
key="all-keys" |
|
value="all-keys" |
|
onClick={() => { |
|
setSelectedAPIKey(null); |
|
}} |
|
> |
|
All Keys |
|
</SelectItem> |
|
{keys?.map((key: any, index: number) => { |
|
if ( |
|
key && |
|
key["key_alias"] !== null && |
|
key["key_alias"].length > 0 |
|
) { |
|
return ( |
|
|
|
<SelectItem |
|
key={index} |
|
value={String(index)} |
|
// @ts-ignore |
|
disabled={true} |
|
onClick={() => { |
|
setSelectedAPIKey(key); |
|
}} |
|
> |
|
✨ {key["key_alias"]} (Enterprise only Feature) |
|
</SelectItem> |
|
); |
|
} |
|
return null; // Add this line to handle the case when the condition is not met |
|
})} |
|
</Select> |
|
|
|
|
|
<Text className="mt-1"> |
|
Select Customer Name |
|
</Text> |
|
|
|
<Select defaultValue="all-customers"> |
|
<SelectItem |
|
key="all-customers" |
|
value="all-customers" |
|
onClick={() => { |
|
setSelectedCustomer(null); |
|
}} |
|
> |
|
All Customers |
|
</SelectItem> |
|
{ |
|
allEndUsers?.map((user: any, index: number) => { |
|
return ( |
|
<SelectItem |
|
key={index} |
|
value={user} |
|
// @ts-ignore |
|
disabled={true} |
|
onClick={() => { |
|
setSelectedCustomer(user); |
|
}} |
|
> |
|
✨ {user} (Enterprise only Feature) |
|
</SelectItem> |
|
); |
|
}) |
|
} |
|
</Select> |
|
|
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
</div> |
|
|
|
); |
|
|
|
const customTooltip = (props: any) => { |
|
const { payload, active } = props; |
|
if (!active || !payload) return null; |
|
|
|
|
|
const date = payload[0]?.payload?.date; |
|
|
|
|
|
let sortedPayload = payload.sort((a: any, b: any) => b.value - a.value); |
|
|
|
|
|
if (sortedPayload.length > 5) { |
|
let remainingItems = sortedPayload.length - 5; |
|
sortedPayload = sortedPayload.slice(0, 5); |
|
sortedPayload.push({ |
|
dataKey: `${remainingItems} other deployments`, |
|
value: payload |
|
.slice(5) |
|
.reduce((acc: number, curr: any) => acc + curr.value, 0), |
|
color: "gray", |
|
}); |
|
} |
|
|
|
return ( |
|
<div className="w-150 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown"> |
|
{date && ( |
|
<p className="text-tremor-content-emphasis mb-2">Date: {date}</p> |
|
)} |
|
{sortedPayload.map((category: any, idx: number) => { |
|
const roundedValue = parseFloat(category.value.toFixed(5)); |
|
const displayValue = |
|
roundedValue === 0 && category.value > 0 |
|
? "<0.00001" |
|
: roundedValue.toFixed(5); |
|
return ( |
|
<div key={idx} className="flex justify-between"> |
|
<div className="flex items-center space-x-2"> |
|
<div |
|
className={`w-2 h-2 mt-1 rounded-full bg-${category.color}-500`} |
|
/> |
|
<p className="text-tremor-content">{category.dataKey}</p> |
|
</div> |
|
<p className="font-medium text-tremor-content-emphasis text-righ ml-2"> |
|
{displayValue} |
|
</p> |
|
</div> |
|
); |
|
})} |
|
</div> |
|
); |
|
}; |
|
|
|
|
|
const handleOk = () => { |
|
form |
|
.validateFields() |
|
.then((values) => { |
|
handleAddModelSubmit(values, accessToken, form); |
|
// form.resetFields(); |
|
}) |
|
.catch((error) => { |
|
console.error("Validation failed:", error); |
|
}); |
|
}; |
|
|
|
console.log(`selectedProvider: ${selectedProvider}`); |
|
console.log(`providerModels.length: ${providerModels.length}`); |
|
|
|
const providerKey = Object.keys(Providers).find( |
|
(key) => (Providers as { [index: string]: any })[key] === selectedProvider |
|
); |
|
|
|
let dynamicProviderForm: ProviderSettings | undefined = undefined; |
|
if (providerKey) { |
|
dynamicProviderForm = providerSettings.find( |
|
(provider) => provider.name === provider_map[providerKey] |
|
); |
|
} |
|
|
|
return ( |
|
<div style={{ width: "100%", height: "100%" }}> |
|
<TabGroup className="gap-2 p-8 h-[75vh] w-full mt-2"> |
|
<TabList className="flex justify-between mt-2 w-full items-center"> |
|
<div className="flex"> |
|
<Tab>All Models</Tab> |
|
<Tab>Add Model</Tab> |
|
<Tab> |
|
<pre>/health Models</pre> |
|
</Tab> |
|
<Tab>Model Analytics</Tab> |
|
<Tab>Model Retry Settings</Tab> |
|
</div> |
|
|
|
<div className="flex items-center space-x-2"> |
|
{lastRefreshed && <Text>Last Refreshed: {lastRefreshed}</Text>} |
|
<Icon |
|
icon={RefreshIcon} // Modify as necessary for correct icon name |
|
variant="shadow" |
|
size="xs" |
|
className="self-center" |
|
onClick={handleRefreshClick} |
|
/> |
|
</div> |
|
</TabList> |
|
<TabPanels> |
|
<TabPanel> |
|
<Grid> |
|
<div className="flex items-center"> |
|
<Text>Filter by Public Model Name</Text> |
|
<Select |
|
className="mb-4 mt-2 ml-2 w-50" |
|
defaultValue={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: undefined |
|
} |
|
onValueChange={(value) => |
|
setSelectedModelGroup(value === "all" ? "all" : value) |
|
} |
|
value={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: undefined |
|
} |
|
> |
|
<SelectItem value={"all"}>All Models</SelectItem> |
|
{availableModelGroups.map((group, idx) => ( |
|
<SelectItem |
|
key={idx} |
|
value={group} |
|
onClick={() => setSelectedModelGroup(group)} |
|
> |
|
{group} |
|
</SelectItem> |
|
))} |
|
</Select> |
|
</div> |
|
<Card> |
|
<Table style={{ maxWidth: "1500px", width: "100%" }}> |
|
<TableHead> |
|
<TableRow> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
Public Model Name |
|
</TableHeaderCell> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
Provider |
|
</TableHeaderCell> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
LiteLLM Model |
|
</TableHeaderCell> |
|
{userRole === "Admin" && ( |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
API Base |
|
</TableHeaderCell> |
|
)} |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "85px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
Input Price{" "} |
|
<p style={{ fontSize: "10px", color: "gray" }}> |
|
/1M Tokens ($) |
|
</p> |
|
</TableHeaderCell> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "85px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
Output Price{" "} |
|
<p style={{ fontSize: "10px", color: "gray" }}> |
|
/1M Tokens ($) |
|
</p> |
|
</TableHeaderCell> |
|
|
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
{premiumUser ? ( |
|
"Created At" |
|
) : ( |
|
<a |
|
href="https://forms.gle/W3U4PZpJGFHWtHyA9" |
|
target="_blank" |
|
style={{ color: "#72bcd4" }} |
|
> |
|
{" "} |
|
✨ Created At |
|
</a> |
|
)} |
|
</TableHeaderCell> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
{premiumUser ? ( |
|
"Created By" |
|
) : ( |
|
<a |
|
href="https://forms.gle/W3U4PZpJGFHWtHyA9" |
|
target="_blank" |
|
style={{ color: "#72bcd4" }} |
|
> |
|
{" "} |
|
✨ Created By |
|
</a> |
|
)} |
|
</TableHeaderCell> |
|
<TableHeaderCell |
|
style={{ |
|
maxWidth: "50px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
fontSize: "11px", |
|
}} |
|
> |
|
Status |
|
</TableHeaderCell> |
|
<TableHeaderCell></TableHeaderCell> |
|
</TableRow> |
|
</TableHead> |
|
<TableBody> |
|
{modelData.data |
|
.filter( |
|
(model: any) => |
|
selectedModelGroup === "all" || |
|
model.model_name === selectedModelGroup || |
|
selectedModelGroup === null || |
|
selectedModelGroup === undefined || |
|
selectedModelGroup === "" |
|
) |
|
.map((model: any, index: number) => ( |
|
<TableRow |
|
key={index} |
|
style={{ maxHeight: "1px", minHeight: "1px" }} |
|
> |
|
<TableCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<p className="text-xs">{model.model_name || "-"}</p> |
|
</TableCell> |
|
<TableCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<div className="flex items-center space-x-2"> |
|
{model.provider && ( |
|
<img |
|
src={getProviderLogoAndName(model.provider).logo} |
|
alt={`${model.provider} logo`} |
|
className="w-4 h-4" |
|
onError={(e) => { |
|
// Create a div with provider initial as fallback |
|
const target = e.target as HTMLImageElement; |
|
const parent = target.parentElement; |
|
if (parent) { |
|
const fallbackDiv = document.createElement('div'); |
|
fallbackDiv.className = 'w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs'; |
|
fallbackDiv.textContent = model.provider?.charAt(0) || '-'; |
|
parent.replaceChild(fallbackDiv, target); |
|
} |
|
}} |
|
/> |
|
)} |
|
<p className="text-xs">{model.provider || "-"}</p> |
|
</div> |
|
</TableCell> |
|
|
|
<TableCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<Tooltip title={model && model.litellm_model_name}> |
|
<pre |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
className="text-xs" |
|
title={ |
|
model && model.litellm_model_name |
|
? model.litellm_model_name |
|
: "" |
|
} |
|
> |
|
{model && model.litellm_model_name |
|
? model.litellm_model_name.slice(0, 20) + (model.litellm_model_name.length > 20 ? "..." : "") |
|
: "-"} |
|
</pre> |
|
</Tooltip> |
|
|
|
</TableCell> |
|
{userRole === "Admin" && ( |
|
<TableCell |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<Tooltip title={model && model.api_base}> |
|
<pre |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
className="text-xs" |
|
title={ |
|
model && model.api_base |
|
? model.api_base |
|
: "" |
|
} |
|
> |
|
{model && model.api_base |
|
? model.api_base.slice(0, 20) |
|
: "-"} |
|
</pre> |
|
</Tooltip> |
|
</TableCell> |
|
)} |
|
<TableCell |
|
style={{ |
|
maxWidth: "80px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<pre className="text-xs"> |
|
{model.input_cost |
|
? model.input_cost |
|
: model.litellm_params.input_cost_per_token != null && model.litellm_params.input_cost_per_token != undefined |
|
? ( |
|
Number( |
|
model.litellm_params |
|
.input_cost_per_token |
|
) * 1000000 |
|
).toFixed(2) |
|
: null} |
|
</pre> |
|
</TableCell> |
|
<TableCell |
|
style={{ |
|
maxWidth: "80px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<pre className="text-xs"> |
|
{model.output_cost |
|
? model.output_cost |
|
: model.litellm_params.output_cost_per_token |
|
? ( |
|
Number( |
|
model.litellm_params |
|
.output_cost_per_token |
|
) * 1000000 |
|
).toFixed(2) |
|
: null} |
|
</pre> |
|
</TableCell> |
|
<TableCell> |
|
<p className="text-xs"> |
|
{premiumUser |
|
? formatCreatedAt( |
|
model.model_info.created_at |
|
) || "-" |
|
: "-"} |
|
</p> |
|
</TableCell> |
|
<TableCell> |
|
<p className="text-xs"> |
|
{premiumUser |
|
? model.model_info.created_by || "-" |
|
: "-"} |
|
</p> |
|
</TableCell> |
|
<TableCell |
|
style={{ |
|
maxWidth: "100px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
{model.model_info.db_model ? ( |
|
<Badge size="xs" className="text-white"> |
|
<p className="text-xs">DB Model</p> |
|
</Badge> |
|
) : ( |
|
<Badge size="xs" className="text-black"> |
|
<p className="text-xs">Config Model</p> |
|
</Badge> |
|
)} |
|
</TableCell> |
|
<TableCell |
|
style={{ |
|
maxWidth: "150px", |
|
whiteSpace: "normal", |
|
wordBreak: "break-word", |
|
}} |
|
> |
|
<Grid numItems={3}> |
|
<Col> |
|
<Icon |
|
icon={InformationCircleIcon} |
|
size="sm" |
|
onClick={() => handleInfoClick(model)} |
|
/> |
|
</Col> |
|
<Col> |
|
<Icon |
|
icon={PencilAltIcon} |
|
size="sm" |
|
onClick={() => handleEditClick(model)} |
|
/> |
|
</Col> |
|
|
|
<Col> |
|
<DeleteModelButton |
|
modelID={model.model_info.id} |
|
accessToken={accessToken} |
|
/> |
|
</Col> |
|
</Grid> |
|
</TableCell> |
|
</TableRow> |
|
))} |
|
</TableBody> |
|
</Table> |
|
</Card> |
|
</Grid> |
|
<EditModelModal |
|
visible={editModalVisible} |
|
onCancel={handleEditCancel} |
|
model={selectedModel} |
|
onSubmit={handleEditSubmit} |
|
/> |
|
<Modal |
|
title={selectedModel && selectedModel.model_name} |
|
visible={infoModalVisible} |
|
width={800} |
|
footer={null} |
|
onCancel={handleInfoCancel} |
|
> |
|
<Title>Model Info</Title> |
|
<SyntaxHighlighter language="json"> |
|
{selectedModel && JSON.stringify(selectedModel, null, 2)} |
|
</SyntaxHighlighter> |
|
</Modal> |
|
</TabPanel> |
|
<TabPanel className="h-full"> |
|
<Title2 level={2}>Add new model</Title2> |
|
<Card> |
|
<Form |
|
form={form} |
|
onFinish={handleOk} |
|
labelCol={{ span: 10 }} |
|
wrapperCol={{ span: 16 }} |
|
labelAlign="left" |
|
> |
|
<> |
|
{/* Provider Selection */} |
|
<Form.Item |
|
rules={[{ required: true, message: "Required" }]} |
|
label="Provider:" |
|
name="custom_llm_provider" |
|
tooltip="E.g. OpenAI, Azure OpenAI, Anthropic, Bedrock, etc." |
|
labelCol={{ span: 10 }} |
|
labelAlign="left" |
|
> |
|
<AntdSelect |
|
showSearch={true} |
|
value={selectedProvider} |
|
onChange={(value) => { |
|
setSelectedProvider(value); |
|
setProviderModelsFn(value); |
|
form.setFieldsValue({ |
|
model: [], |
|
model_name: undefined |
|
}); |
|
}} |
|
> |
|
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => ( |
|
<AntdSelect.Option |
|
key={providerEnum} |
|
value={providerEnum} |
|
> |
|
<div className="flex items-center space-x-2"> |
|
<img |
|
src={providerLogoMap[providerDisplayName]} |
|
alt={`${providerEnum} logo`} |
|
className="w-5 h-5" |
|
onError={(e) => { |
|
// Create a div with provider initial as fallback |
|
const target = e.target as HTMLImageElement; |
|
const parent = target.parentElement; |
|
if (parent) { |
|
const fallbackDiv = document.createElement('div'); |
|
fallbackDiv.className = 'w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs'; |
|
fallbackDiv.textContent = providerDisplayName.charAt(0); |
|
parent.replaceChild(fallbackDiv, target); |
|
} |
|
}} |
|
/> |
|
<span>{providerDisplayName}</span> |
|
</div> |
|
</AntdSelect.Option> |
|
))} |
|
</AntdSelect> |
|
</Form.Item> |
|
<LiteLLMModelNameField |
|
selectedProvider={selectedProvider} |
|
providerModels={providerModels} |
|
getPlaceholder={getPlaceholder} |
|
/> |
|
|
|
{/* Conditionally Render "Public Model Name" */} |
|
<ConditionalPublicModelName /> |
|
|
|
<ProviderSpecificFields |
|
selectedProvider={selectedProvider} |
|
uploadProps={uploadProps} |
|
/> |
|
<AdvancedSettings |
|
showAdvancedSettings={showAdvancedSettings} |
|
setShowAdvancedSettings={setShowAdvancedSettings} |
|
/> |
|
|
|
|
|
<div className="flex justify-between items-center mb-4"> |
|
<Tooltip title="Get help on our github"> |
|
<Typography.Link href="https://github.com/BerriAI/litellm/issues"> |
|
Need Help? |
|
</Typography.Link> |
|
</Tooltip> |
|
<Button2 htmlType="submit">Add Model</Button2> |
|
</div> |
|
</> |
|
</Form> |
|
</Card> |
|
</TabPanel> |
|
<TabPanel> |
|
<Card> |
|
<Text> |
|
`/health` will run a very small request through your models |
|
configured on litellm |
|
</Text> |
|
|
|
<Button onClick={runHealthCheck}>Run `/health`</Button> |
|
{healthCheckResponse && ( |
|
<pre>{JSON.stringify(healthCheckResponse, null, 2)}</pre> |
|
)} |
|
</Card> |
|
</TabPanel> |
|
<TabPanel> |
|
<Grid numItems={4} className="mt-2 mb-2"> |
|
<Col> |
|
<Text>Select Time Range</Text> |
|
<DateRangePicker |
|
enableSelect={true} |
|
value={dateValue} |
|
className="mr-2" |
|
onValueChange={(value) => { |
|
setDateValue(value); |
|
updateModelMetrics( |
|
selectedModelGroup, |
|
value.from, |
|
value.to |
|
); // Call updateModelMetrics with the new date range |
|
}} |
|
/> |
|
</Col> |
|
<Col className="ml-2"> |
|
<Text>Select Model Group</Text> |
|
<Select |
|
defaultValue={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: availableModelGroups[0] |
|
} |
|
value={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: availableModelGroups[0] |
|
} |
|
> |
|
{availableModelGroups.map((group, idx) => ( |
|
<SelectItem |
|
key={idx} |
|
value={group} |
|
onClick={() => |
|
updateModelMetrics(group, dateValue.from, dateValue.to) |
|
} |
|
> |
|
{group} |
|
</SelectItem> |
|
))} |
|
</Select> |
|
</Col> |
|
<Col> |
|
<Popover |
|
trigger="click" content={FilterByContent} |
|
overlayStyle={{ |
|
width: "20vw" |
|
}} |
|
> |
|
<Button |
|
icon={FilterIcon} |
|
size="md" |
|
variant="secondary" |
|
className="mt-4 ml-2" |
|
style={{ |
|
border: "none", |
|
}} |
|
onClick={() => setShowAdvancedFilters(true)} |
|
> |
|
</Button> |
|
</Popover> |
|
</Col> |
|
|
|
</Grid> |
|
|
|
|
|
<Grid numItems={2}> |
|
<Col> |
|
<Card className="mr-2 max-h-[400px] min-h-[400px]"> |
|
<TabGroup> |
|
<TabList variant="line" defaultValue="1"> |
|
<Tab value="1">Avg. Latency per Token</Tab> |
|
<Tab value="2">✨ Time to first token</Tab> |
|
</TabList> |
|
<TabPanels> |
|
<TabPanel> |
|
<p className="text-gray-500 italic"> (seconds/token)</p> |
|
<Text className="text-gray-500 italic mt-1 mb-1"> |
|
average Latency for successfull requests divided by |
|
the total tokens |
|
</Text> |
|
{modelMetrics && modelMetricsCategories && ( |
|
<AreaChart |
|
title="Model Latency" |
|
className="h-72" |
|
data={modelMetrics} |
|
showLegend={false} |
|
index="date" |
|
categories={modelMetricsCategories} |
|
connectNulls={true} |
|
customTooltip={customTooltip} |
|
/> |
|
)} |
|
</TabPanel> |
|
<TabPanel> |
|
<TimeToFirstToken |
|
modelMetrics={streamingModelMetrics} |
|
modelMetricsCategories={ |
|
streamingModelMetricsCategories |
|
} |
|
customTooltip={customTooltip} |
|
premiumUser={premiumUser} |
|
/> |
|
</TabPanel> |
|
</TabPanels> |
|
</TabGroup> |
|
</Card> |
|
</Col> |
|
<Col> |
|
<Card className="ml-2 max-h-[400px] min-h-[400px] overflow-y-auto"> |
|
<Table> |
|
<TableHead> |
|
<TableRow> |
|
<TableHeaderCell>Deployment</TableHeaderCell> |
|
<TableHeaderCell>Success Responses</TableHeaderCell> |
|
<TableHeaderCell> |
|
Slow Responses <p>Success Responses taking 600+s</p> |
|
</TableHeaderCell> |
|
</TableRow> |
|
</TableHead> |
|
<TableBody> |
|
{slowResponsesData.map((metric, idx) => ( |
|
<TableRow key={idx}> |
|
<TableCell>{metric.api_base}</TableCell> |
|
<TableCell>{metric.total_count}</TableCell> |
|
<TableCell>{metric.slow_count}</TableCell> |
|
</TableRow> |
|
))} |
|
</TableBody> |
|
</Table> |
|
</Card> |
|
</Col> |
|
</Grid> |
|
<Grid numItems={1} className="gap-2 w-full mt-2"> |
|
<Card> |
|
|
|
<Title>All Exceptions for {selectedModelGroup}</Title> |
|
|
|
<BarChart |
|
className="h-60" |
|
data={modelExceptions} |
|
index="model" |
|
categories={allExceptions} |
|
stack={true} |
|
|
|
yAxisWidth={30} |
|
/> |
|
</Card> |
|
|
|
</Grid> |
|
|
|
|
|
<Grid numItems={1} className="gap-2 w-full mt-2"> |
|
<Card> |
|
<Title>All Up Rate Limit Errors (429) for {selectedModelGroup}</Title> |
|
<Grid numItems={1}> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Num Rate Limit Errors { (globalExceptionData.sum_num_rate_limit_exceptions)}</Subtitle> |
|
<BarChart |
|
className="h-40" |
|
data={globalExceptionData.daily_data} |
|
index="date" |
|
colors={['rose']} |
|
categories={['num_rate_limit_exceptions']} |
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
</Col> |
|
<Col> |
|
|
|
|
|
|
|
</Col> |
|
|
|
</Grid> |
|
|
|
|
|
</Card> |
|
|
|
{ |
|
premiumUser ? ( |
|
<> |
|
{globalExceptionPerDeployment.map((globalActivity, index) => ( |
|
<Card key={index}> |
|
<Title>{globalActivity.api_base ? globalActivity.api_base : "Unknown API Base"}</Title> |
|
<Grid numItems={1}> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Num Rate Limit Errors (429) {(globalActivity.sum_num_rate_limit_exceptions)}</Subtitle> |
|
<BarChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
index="date" |
|
colors={['rose']} |
|
categories={['num_rate_limit_exceptions']} |
|
|
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
|
|
</Col> |
|
</Grid> |
|
</Card> |
|
))} |
|
</> |
|
) : |
|
<> |
|
{globalExceptionPerDeployment && globalExceptionPerDeployment.length > 0 && |
|
globalExceptionPerDeployment.slice(0, 1).map((globalActivity, index) => ( |
|
<Card key={index}> |
|
<Title>✨ Rate Limit Errors by Deployment</Title> |
|
<p className="mb-2 text-gray-500 italic text-[12px]">Upgrade to see exceptions for all deployments</p> |
|
<Button variant="primary" className="mb-2"> |
|
<a href="https://forms.gle/W3U4PZpJGFHWtHyA9" target="_blank"> |
|
Get Free Trial |
|
</a> |
|
</Button> |
|
<Card> |
|
<Title>{globalActivity.api_base}</Title> |
|
<Grid numItems={1}> |
|
<Col> |
|
<Subtitle |
|
style={{ |
|
fontSize: "15px", |
|
fontWeight: "normal", |
|
color: "#535452", |
|
}} |
|
> |
|
Num Rate Limit Errors {(globalActivity.sum_num_rate_limit_exceptions)} |
|
</Subtitle> |
|
<BarChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
index="date" |
|
colors={['rose']} |
|
categories={['num_rate_limit_exceptions']} |
|
|
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
</Col> |
|
|
|
|
|
</Grid> |
|
</Card> |
|
</Card> |
|
))} |
|
</> |
|
} |
|
</Grid> |
|
|
|
</TabPanel> |
|
<TabPanel> |
|
<div className="flex items-center"> |
|
<Text>Filter by Public Model Name</Text> |
|
|
|
<Select |
|
className="mb-4 mt-2 ml-2 w-50" |
|
defaultValue={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: availableModelGroups[0] |
|
} |
|
value={ |
|
selectedModelGroup |
|
? selectedModelGroup |
|
: availableModelGroups[0] |
|
} |
|
onValueChange={(value) => setSelectedModelGroup(value)} |
|
> |
|
{availableModelGroups.map((group, idx) => ( |
|
<SelectItem |
|
key={idx} |
|
value={group} |
|
onClick={() => setSelectedModelGroup(group)} |
|
> |
|
{group} |
|
</SelectItem> |
|
))} |
|
</Select> |
|
</div> |
|
|
|
<Title>Retry Policy for {selectedModelGroup}</Title> |
|
<Text className="mb-6"> |
|
How many retries should be attempted based on the Exception |
|
</Text> |
|
{retry_policy_map && ( |
|
<table> |
|
<tbody> |
|
{Object.entries(retry_policy_map).map( |
|
([exceptionType, retryPolicyKey], idx) => { |
|
let retryCount = |
|
modelGroupRetryPolicy?.[selectedModelGroup!]?.[ |
|
retryPolicyKey |
|
]; |
|
if (retryCount == null) { |
|
retryCount = defaultRetry; |
|
} |
|
|
|
return ( |
|
<tr |
|
key={idx} |
|
className="flex justify-between items-center mt-2" |
|
> |
|
<td> |
|
<Text>{exceptionType}</Text> |
|
</td> |
|
<td> |
|
<InputNumber |
|
className="ml-5" |
|
value={retryCount} |
|
min={0} |
|
step={1} |
|
onChange={(value) => { |
|
setModelGroupRetryPolicy( |
|
(prevModelGroupRetryPolicy) => { |
|
const prevRetryPolicy = |
|
prevModelGroupRetryPolicy?.[ |
|
selectedModelGroup! |
|
] ?? {}; |
|
return { |
|
...(prevModelGroupRetryPolicy ?? {}), |
|
[selectedModelGroup!]: { |
|
...prevRetryPolicy, |
|
[retryPolicyKey!]: value, |
|
}, |
|
} as RetryPolicyObject; |
|
} |
|
); |
|
}} |
|
/> |
|
</td> |
|
</tr> |
|
); |
|
} |
|
)} |
|
</tbody> |
|
</table> |
|
)} |
|
<Button className="mt-6 mr-8" onClick={handleSaveRetrySettings}> |
|
Save |
|
</Button> |
|
</TabPanel> |
|
</TabPanels> |
|
</TabGroup> |
|
</div> |
|
); |
|
}; |
|
|
|
export default ModelDashboard; |
|
|