mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-07 00:24:03 +00:00
fix(ui): stop injecting $0 cost into model update payload when cost fields are untouched
Editing a model in the Admin UI (e.g. to change credential) unconditionally included input_cost_per_token: 0 and output_cost_per_token: 0 in the PATCH payload, overriding built-in pricing from model_prices_and_context_window.json. Guard cost fields with form.isFieldTouched so they are only sent when the user explicitly modifies them. Intentional $0 cost (for budget bypass) still works because the guard checks touched + non-null, not non-zero.
This commit is contained in:
@@ -579,6 +579,34 @@ describe("ModelInfoView", () => {
|
||||
expect(updatePayload.litellm_params).not.toHaveProperty("vector_store_ids");
|
||||
});
|
||||
|
||||
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,
|
||||
// overriding the built-in pricing table from model_prices_and_context_window.json.
|
||||
const user = userEvent.setup();
|
||||
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 }));
|
||||
|
||||
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("input_cost_per_token");
|
||||
expect(updatePayload.litellm_params).not.toHaveProperty("output_cost_per_token");
|
||||
});
|
||||
|
||||
it("should display health check model field for wildcard models", async () => {
|
||||
const wildcardModelData = {
|
||||
...defaultModelData,
|
||||
|
||||
@@ -253,10 +253,16 @@ export default function ModelInfoView({
|
||||
max_retries: values.max_retries,
|
||||
timeout: values.timeout,
|
||||
stream_timeout: values.stream_timeout,
|
||||
input_cost_per_token: values.input_cost / 1_000_000,
|
||||
output_cost_per_token: values.output_cost / 1_000_000,
|
||||
tags: values.tags,
|
||||
};
|
||||
|
||||
if (form.isFieldTouched("input_cost") && values.input_cost !== undefined && values.input_cost !== null) {
|
||||
updatedLitellmParams.input_cost_per_token = Number(values.input_cost) / 1_000_000;
|
||||
}
|
||||
if (form.isFieldTouched("output_cost") && values.output_cost !== undefined && values.output_cost !== null) {
|
||||
updatedLitellmParams.output_cost_per_token = Number(values.output_cost) / 1_000_000;
|
||||
}
|
||||
|
||||
if (values.litellm_credential_name) {
|
||||
updatedLitellmParams.litellm_credential_name = values.litellm_credential_name;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user