fix(ui): filter known params from litellm_extra_params to allow re-editing

Fixes #23998

The LiteLLM Params JSON textarea was showing all params including those
with dedicated form fields (api_base, model, etc.). This caused confusion
when users tried to remove custom params - they would reappear because
the individual form fields would still have those values.

Changes:
- Added KNOWN_LITELLM_PARAMS constant listing all params with dedicated form fields
- Filter out known params from litellm_extra_params initialization
- Updated tests to verify known params don't appear in the textarea
- Added test to verify custom params can be removed successfully
This commit is contained in:
BillionClaw 2026-03-18 20:10:46 +08:00
parent cec3e9e7d4
commit 11fb7f687d
2 changed files with 86 additions and 2 deletions

View file

@ -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(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { 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();
});
});

View file

@ -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,