From a282e3c78ca5d8b9b3df32bce637f92255cb90a0 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Thu, 17 Sep 2026 16:24:55 -0700 Subject: [PATCH] refactor(ui): build transformed model rows without in-place mutation Greptile flagged the per-second fields for extending the transformer's mutate-in-a-loop pattern. Map each raw model to a new object instead so no field is assigned onto a shared reference, and drop the now unused prefer-const suppression for the file --- ui/litellm-dashboard/eslint-suppressions.json | 5 - .../utils/modelDataTransformer.ts | 135 ++++++++---------- 2 files changed, 60 insertions(+), 80 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 7de7373b20b..257f6de1a82 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -656,11 +656,6 @@ "count": 1 } }, - "src/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer.ts": { - "prefer-const": { - "count": 6 - } - }, "src/app/(dashboard)/old-usage/_components/usage.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer.ts b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer.ts index 438bbc379c9..5dce4cb8b65 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer.ts @@ -11,82 +11,67 @@ export const perSecondCostTiers = (modelInfo: Record | null | u export const formatPerSecondCost = (cost: number): string => `$${cost.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 })}/s`; -/** - * Utility function to transform raw model data into the format expected by UI components - * This creates a new transformed data object without mutating the original - */ +interface RawLitellmParams { + model?: string; + custom_llm_provider?: string; + api_base?: string; + output_cost_per_second?: number; + [key: string]: unknown; +} + +interface RawModelInfo { + input_cost_per_token?: number | null; + output_cost_per_token?: number | null; + output_cost_per_second?: number; + max_tokens?: number; + max_input_tokens?: number; + [key: string]: unknown; +} + +interface RawModel { + litellm_params?: RawLitellmParams; + model_info?: RawModelInfo; + [key: string]: unknown; +} + +const costPerMillionTokens = (costPerToken: number | null | undefined) => + costPerToken == null ? costPerToken : (Number(costPerToken) * 1000000).toFixed(2); + +const resolveProvider = ( + litellmModelName: string | null | undefined, + customLlmProvider: string | null | undefined, + getProviderFromModel: (model: string) => string, +): string => { + if (!litellmModelName) return "-"; + if (customLlmProvider) return customLlmProvider; + const splitModel = litellmModelName.split("/"); + return splitModel.length === 1 ? getProviderFromModel(litellmModelName) : splitModel[0]; +}; + +const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string) => string) => { + const model: RawModel = JSON.parse(JSON.stringify(rawModel)); + const litellmParams = model?.litellm_params; + const modelInfo = model?.model_info; + + return { + ...model, + provider: resolveProvider(litellmParams?.model, litellmParams?.custom_llm_provider, getProviderFromModel), + input_cost: modelInfo ? costPerMillionTokens(modelInfo.input_cost_per_token) : null, + output_cost: modelInfo ? costPerMillionTokens(modelInfo.output_cost_per_token) : null, + output_cost_per_second: litellmParams?.output_cost_per_second ?? modelInfo?.output_cost_per_second ?? null, + output_cost_per_second_tiers: perSecondCostTiers(modelInfo), + litellm_model_name: litellmParams?.model, + max_tokens: modelInfo ? modelInfo.max_tokens : "Undefined", + max_input_tokens: modelInfo ? modelInfo.max_input_tokens : "Undefined", + api_base: litellmParams?.api_base, + cleanedLitellmParams: Object.fromEntries( + Object.entries(litellmParams ?? {}).filter(([key]) => key !== "model" && key !== "api_base"), + ), + }; +}; + export const transformModelData = (rawModelData: any, getProviderFromModel: (model: string) => string) => { if (!rawModelData?.data) return { data: [] }; - // Deep copy the data to avoid mutating the original - const transformedData = JSON.parse(JSON.stringify(rawModelData.data)); - - for (let i = 0; i < transformedData.length; i++) { - let curr_model = transformedData[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 provider = ""; - let input_cost: any = null; - let output_cost: any = null; - let max_tokens = "Undefined"; - let max_input_tokens = "Undefined"; - let cleanedLitellmParams = {}; - - // Check if litellm_model_name is null or undefined - if (litellm_model_name) { - // Split litellm_model_name based on "/" - let splitModel = litellm_model_name.split("/"); - - // Get the first element in the split - let firstElement = splitModel[0]; - - // If there is only one element, default provider to openai - provider = custom_llm_provider; - if (!provider) { - provider = splitModel.length === 1 ? getProviderFromModel(litellm_model_name) : firstElement; - } - } else { - // litellm_model_name is null or undefined, default provider to openai - 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"), - ); - } - - transformedData[i].provider = provider; - transformedData[i].input_cost = input_cost; - transformedData[i].output_cost = output_cost; - transformedData[i].output_cost_per_second = - curr_model?.litellm_params?.output_cost_per_second ?? model_info?.output_cost_per_second ?? null; - transformedData[i].output_cost_per_second_tiers = perSecondCostTiers(model_info); - transformedData[i].litellm_model_name = litellm_model_name; - - // Convert Cost in terms of Cost per 1M tokens - if (transformedData[i].input_cost != null) { - transformedData[i].input_cost = (Number(transformedData[i].input_cost) * 1000000).toFixed(2); - } - - if (transformedData[i].output_cost != null) { - transformedData[i].output_cost = (Number(transformedData[i].output_cost) * 1000000).toFixed(2); - } - - transformedData[i].max_tokens = max_tokens; - transformedData[i].max_input_tokens = max_input_tokens; - transformedData[i].api_base = curr_model?.litellm_params?.api_base; - transformedData[i].cleanedLitellmParams = cleanedLitellmParams; - } - - return { data: transformedData }; + return { data: rawModelData.data.map((rawModel: RawModel) => transformModel(rawModel, getProviderFromModel)) }; };