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 29eb8e0019b..87726c9a800 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -579,6 +579,34 @@ describe("ModelInfoView", () => { expect(updatePayload.litellm_params).not.toHaveProperty("vector_store_ids"); }); + it("should not include guardrails in update payload when model has none and user does not touch the field", async () => { + // Regression: editing a model without guardrails used to inject + // guardrails: [] into litellm_params on every save, because the + // submit handler used `if (values.guardrails)` and an empty array + // is truthy in JavaScript. + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + await user.click(screen.getByRole("button", { name: /edit settings/i })); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save changes/i })).toBeInTheDocument(); + }); + + await user.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(mockModelPatchUpdateCall).toHaveBeenCalled(); + }); + + const updatePayload = mockModelPatchUpdateCall.mock.calls[0][1]; + expect(updatePayload.litellm_params).not.toHaveProperty("guardrails"); + }); + it("should not include input_cost_per_token or output_cost_per_token in update payload when user does not touch cost fields", async () => { // Regression: editing a model without touching cost fields used to inject // input_cost_per_token: 0 and output_cost_per_token: 0 into litellm_params, diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index ea8af2fcd62..d9d9b0a198b 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -268,8 +268,13 @@ export default function ModelInfoView({ } else { delete updatedLitellmParams.litellm_credential_name; } - if (values.guardrails) { + if (values.guardrails?.length > 0) { updatedLitellmParams.guardrails = values.guardrails; + } else if (values.guardrails !== undefined) { + // User explicitly cleared previously-set guardrails — send [] to clear on backend + updatedLitellmParams.guardrails = []; + } else { + delete updatedLitellmParams.guardrails; } if (values.vector_store_ids?.length > 0) { updatedLitellmParams.vector_store_ids = values.vector_store_ids; @@ -637,9 +642,11 @@ export default function ModelInfoView({ model_access_group: Array.isArray(localModelData.model_info?.access_groups) ? localModelData.model_info.access_groups : [], - guardrails: Array.isArray(localModelData.litellm_params?.guardrails) - ? localModelData.litellm_params.guardrails - : [], + guardrails: + Array.isArray(localModelData.litellm_params?.guardrails) && + localModelData.litellm_params.guardrails.length > 0 + ? localModelData.litellm_params.guardrails + : undefined, vector_store_ids: Array.isArray(localModelData.litellm_params?.vector_store_ids) && localModelData.litellm_params.vector_store_ids.length > 0