diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index b2cbbda34aa..e7186162b81 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -526,17 +526,21 @@ describe("ModelInfoView", () => { await user.click(screen.getByRole("button", { name: /edit settings/i })); + // Find the LiteLLM Params textarea by placeholder const litellmParamsInput = screen .getAllByRole("textbox") .find( (input) => input.tagName === "TEXTAREA" && - (input as HTMLTextAreaElement).value.includes('"custom_llm_provider"'), + input.getAttribute("placeholder")?.includes("rpm"), ); expect(litellmParamsInput).toBeDefined(); if (!litellmParamsInput) { return; } + // Known params like custom_llm_provider should NOT be in the textarea + expect((litellmParamsInput as HTMLTextAreaElement).value).not.toContain("custom_llm_provider"); + // litellm_credential_name should also NOT be in the textarea expect((litellmParamsInput as HTMLTextAreaElement).value).not.toContain("litellm_credential_name"); await user.clear(litellmParamsInput); await user.paste(`{"litellm_credential_name":"from-json","timeout":42}`); @@ -636,4 +640,64 @@ describe("ModelInfoView", () => { expect(screen.getByText(/Created By/)).toBeInTheDocument(); }); }); + + it("should allow removing custom params from LiteLLM Params json", async () => { + const user = userEvent.setup(); + const modelWithCustomParams = { + ...defaultModelData, + litellm_params: { + ...defaultModelData.litellm_params, + litellm_image_enabled: true, + custom_param: "value", + }, + }; + + mockUseModelsInfo.mockReturnValue({ + data: { + data: [modelWithCustomParams], + }, + isLoading: false, + error: null, + }); + + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + await user.click(screen.getByRole("button", { name: /edit settings/i })); + + // Find the LiteLLM Params textarea + const litellmParamsInput = screen + .getAllByRole("textbox") + .find( + (input) => + input.tagName === "TEXTAREA" && + input.getAttribute("placeholder")?.includes("rpm"), + ); + expect(litellmParamsInput).toBeDefined(); + if (!litellmParamsInput) { + return; + } + + // Verify custom params are in the textarea + expect((litellmParamsInput as HTMLTextAreaElement).value).toContain("litellm_image_enabled"); + expect((litellmParamsInput as HTMLTextAreaElement).value).toContain("custom_param"); + + // Remove the custom params + await user.clear(litellmParamsInput); + await user.paste(`{}`); + + await user.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(mockModelPatchUpdateCall).toHaveBeenCalled(); + }); + + // Verify the payload does not include the removed custom params + const updatePayload = mockModelPatchUpdateCall.mock.calls[0][1]; + expect(updatePayload.litellm_params.litellm_image_enabled).toBeUndefined(); + expect(updatePayload.litellm_params.custom_param).toBeUndefined(); + }); }); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 9e846c83ff3..4bad63c3446 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -44,6 +44,26 @@ import NumericalInput from "./shared/numerical_input"; import { Tag } from "./tag_management/types"; import { getDisplayModelName } from "./view_model/model_name_display"; +// Known LiteLLM params that have dedicated form fields - these should not appear in litellm_extra_params +const KNOWN_LITELLM_PARAMS = [ + "model", + "api_base", + "custom_llm_provider", + "organization", + "tpm", + "rpm", + "max_retries", + "timeout", + "stream_timeout", + "input_cost_per_token", + "output_cost_per_token", + "tags", + "guardrails", + "vector_store_ids", + "cache_control_injection_points", + "litellm_credential_name", +]; + interface ModelInfoViewProps { modelId: string; onClose: () => void; @@ -640,7 +660,7 @@ export default function ModelInfoView({ litellm_extra_params: JSON.stringify( Object.fromEntries( Object.entries(localModelData.litellm_params || {}).filter( - ([key]) => key !== "litellm_credential_name", + ([key]) => !KNOWN_LITELLM_PARAMS.includes(key), ), ), null,