(UI) Allow adding custom pricing when adding new model (#8165)

* ui custom pricing

* fix validateJSON

* ui allow entering custom pricing

* ui fix add model form

* working edit model modal

* working edit custom pricing

* ui add custom pricing on ui
This commit is contained in:
Ishaan Jaff
2025-01-31 17:54:29 -08:00
committed by GitHub
parent 2cf0daa31c
commit 29a8a613a7
4 changed files with 333 additions and 209 deletions
@@ -1,6 +1,6 @@
import React from "react";
import { Form, Switch } from "antd";
import { Text, Button, Accordion, AccordionHeader, AccordionBody } from "@tremor/react";
import { Form, Switch, Select, Input } from "antd";
import { Text, Button, Accordion, AccordionHeader, AccordionBody, TextInput } from "@tremor/react";
import { Row, Col, Typography, Card } from "antd";
import TextArea from "antd/es/input/TextArea";
const { Link } = Typography;
@@ -15,8 +15,20 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
setShowAdvancedSettings,
}) => {
const [form] = Form.useForm();
const [customPricing, setCustomPricing] = React.useState(false);
const [pricingModel, setPricingModel] = React.useState<'per_token' | 'per_second'>('per_token');
// Add validation function for numbers
const validateNumber = (_: any, value: string) => {
if (!value) {
return Promise.resolve();
}
if (isNaN(Number(value)) || Number(value) < 0) {
return Promise.reject('Please enter a valid positive number');
}
return Promise.resolve();
};
// Add validation function
const validateJSON = (_: any, value: string) => {
if (!value) {
return Promise.resolve();
@@ -29,6 +41,19 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
}
};
// Handle custom pricing changes
const handleCustomPricingChange = (checked: boolean) => {
setCustomPricing(checked);
if (!checked) {
// Clear pricing fields when disabled
form.setFieldsValue({
input_cost_per_token: undefined,
output_cost_per_token: undefined,
input_cost_per_second: undefined,
});
}
};
const handlePassThroughChange = (checked: boolean) => {
const currentParams = form.getFieldValue('litellm_extra_params');
try {
@@ -56,81 +81,136 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
return (
<>
<Accordion className="mt-2 mb-4">
<AccordionHeader>
<b>Advanced Settings</b>
</AccordionHeader>
<AccordionBody>
<div className="bg-white rounded-lg">
<Form.Item
label="Custom Pricing"
name="custom_pricing"
valuePropName="checked"
className="mb-4"
>
<Switch onChange={handleCustomPricingChange} className="bg-gray-600" />
</Form.Item>
<Accordion className="mt-2 mb-4">
<AccordionHeader>
<b>Advanced Settings</b>
</AccordionHeader>
<AccordionBody>
<div className="bg-white rounded-lg">
<Form.Item
label="Use in pass through routes"
name="use_in_pass_through"
valuePropName="checked"
className="mb-4 mt-4"
tooltip={
<span>
Allow using these credentials in pass through routes.{" "}
<Link href="https://docs.litellm.ai/docs/pass_through/vertex_ai" target="_blank">
Learn more
</Link>
</span>
}
>
<Switch
onChange={handlePassThroughChange}
className="bg-gray-600"
/>
</Form.Item>
<Form.Item
label="LiteLLM Params"
name="litellm_extra_params"
tooltip="Optional litellm params used for making a litellm.completion() call."
className="mb-4 mt-4"
rules={[{ validator: validateJSON }]}
>
<TextArea
rows={4}
placeholder='{
"rpm": 100,
"timeout": 0,
"stream_timeout": 0
}'
/>
</Form.Item>
<Row className="mb-4">
<Col span={10}></Col>
<Col span={10}>
<Text className="text-gray-600 text-sm">
Pass JSON of litellm supported params{" "}
<Link
href="https://docs.litellm.ai/docs/completion/input"
target="_blank"
{customPricing && (
<div className="ml-6 pl-4 border-l-2 border-gray-200">
<Form.Item
label="Pricing Model"
name="pricing_model"
className="mb-4"
>
<Select
defaultValue="per_token"
onChange={(value) => setPricingModel(value)}
options={[
{ value: 'per_token', label: 'Per Million Tokens' },
{ value: 'per_second', label: 'Per Second' },
]}
/>
</Form.Item>
{pricingModel === 'per_token' ? (
<>
<Form.Item
label="Input Cost (per 1M tokens)"
name="input_cost_per_token"
rules={[{ validator: validateNumber }]}
className="mb-4"
>
litellm.completion() call
</Link>
</Text>
</Col>
</Row>
<Form.Item
label="Model Info"
name="model_info_params"
tooltip="Optional model info params. Returned when calling `/model/info` endpoint."
className="mb-0"
rules={[{ validator: validateJSON }]}
>
<TextArea
rows={4}
placeholder='{
"mode": "chat"
}'
/>
</Form.Item>
</div>
</AccordionBody>
</Accordion>
<TextInput />
</Form.Item>
<Form.Item
label="Output Cost (per 1M tokens)"
name="output_cost_per_token"
rules={[{ validator: validateNumber }]}
className="mb-4"
>
<TextInput />
</Form.Item>
</>
) : (
<Form.Item
label="Cost Per Second"
name="input_cost_per_second"
rules={[{ validator: validateNumber }]}
className="mb-4"
>
<TextInput />
</Form.Item>
)}
</div>
)}
<Form.Item
label="Use in pass through routes"
name="use_in_pass_through"
valuePropName="checked"
className="mb-4 mt-4"
tooltip={
<span>
Allow using these credentials in pass through routes.{" "}
<Link href="https://docs.litellm.ai/docs/pass_through/vertex_ai" target="_blank">
Learn more
</Link>
</span>
}
>
<Switch
onChange={handlePassThroughChange}
className="bg-gray-600"
/>
</Form.Item>
<Form.Item
label="LiteLLM Params"
name="litellm_extra_params"
tooltip="Optional litellm params used for making a litellm.completion() call."
className="mb-4 mt-4"
rules={[{ validator: validateJSON }]}
>
<TextArea
rows={4}
placeholder='{
"rpm": 100,
"timeout": 0,
"stream_timeout": 0
}'
/>
</Form.Item>
<Row className="mb-4">
<Col span={10}></Col>
<Col span={10}>
<Text className="text-gray-600 text-sm">
Pass JSON of litellm supported params{" "}
<Link
href="https://docs.litellm.ai/docs/completion/input"
target="_blank"
>
litellm.completion() call
</Link>
</Text>
</Col>
</Row>
<Form.Item
label="Model Info"
name="model_info_params"
tooltip="Optional model info params. Returned when calling `/model/info` endpoint."
className="mb-0"
rules={[{ validator: validateJSON }]}
>
<TextArea
rows={4}
placeholder='{
"mode": "chat"
}'
/>
</Form.Item>
</div>
</AccordionBody>
</Accordion>
</>
);
};
@@ -35,6 +35,16 @@ export const handleAddModelSubmit = async (
console.log(`litellm_model: ${litellm_model}`);
const litellmParamsObj: Record<string, any> = {};
const modelInfoObj: Record<string, any> = {};
// Handle pricing conversion before processing other fields
if (formValues.input_cost_per_token) {
formValues.input_cost_per_token = Number(formValues.input_cost_per_token) / 1000000;
}
if (formValues.output_cost_per_token) {
formValues.output_cost_per_token = Number(formValues.output_cost_per_token) / 1000000;
}
// Keep input_cost_per_second as is, no conversion needed
// Iterate through the key-value pairs in formValues
litellmParamsObj["model"] = litellm_model;
let modelName: string = "";
@@ -43,6 +53,10 @@ export const handleAddModelSubmit = async (
if (value === "") {
continue;
}
// Skip the custom_pricing and pricing_model fields as they're only used for UI control
if (key === 'custom_pricing' || key === 'pricing_model') {
continue;
}
if (key == "model_name") {
modelName = modelName + value;
} else if (key == "custom_llm_provider") {
@@ -97,6 +111,16 @@ export const handleAddModelSubmit = async (
}
}
// Handle the pricing fields
else if (key === "input_cost_per_token" ||
key === "output_cost_per_token" ||
key === "input_cost_per_second") {
if (value) {
litellmParamsObj[key] = Number(value);
}
continue;
}
// Check if key is any of the specified API related keys
else {
// Add key-value pair to litellm_params dictionary
@@ -0,0 +1,146 @@
import React from "react";
import { Modal, Form, InputNumber, message } from "antd";
import { TextInput } from "@tremor/react";
import { Button as Button2 } from "antd";
interface EditModelModalProps {
visible: boolean;
onCancel: () => void;
model: any;
onSubmit: (data: FormData) => void;
}
const EditModelModal: React.FC<EditModelModalProps> = ({
visible,
onCancel,
model,
onSubmit,
}) => {
const [form] = Form.useForm();
let litellm_params_to_edit: Record<string, any> = {};
let model_name = "";
let model_id = "";
if (model) {
litellm_params_to_edit = {
...model.litellm_params,
input_cost_per_token: model.litellm_params?.input_cost_per_token ? (model.litellm_params.input_cost_per_token * 1_000_000) : undefined,
output_cost_per_token: model.litellm_params?.output_cost_per_token ? (model.litellm_params.output_cost_per_token * 1_000_000) : undefined,
};
model_name = model.model_name;
let model_info = model.model_info;
if (model_info) {
model_id = model_info.id;
console.log(`model_id: ${model_id}`);
litellm_params_to_edit.model_id = model_id;
}
}
const handleOk = () => {
form
.validateFields()
.then((values) => {
const submissionValues = {
...values,
input_cost_per_token: values.input_cost_per_token ? Number(values.input_cost_per_token) / 1_000_000 : undefined,
output_cost_per_token: values.output_cost_per_token ? Number(values.output_cost_per_token) / 1_000_000 : undefined,
};
onSubmit(submissionValues);
form.resetFields();
})
.catch((error) => {
console.error("Validation failed:", error);
});
};
return (
<Modal
title={"Edit Model " + model_name}
visible={visible}
width={800}
footer={null}
onOk={handleOk}
onCancel={onCancel}
>
<Form
form={form}
onFinish={onSubmit}
initialValues={litellm_params_to_edit}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<Form.Item
label="Input Cost (per 1M tokens)"
name="input_cost_per_token"
tooltip="float (optional) - Input cost per 1 million tokens"
>
<TextInput />
</Form.Item>
<Form.Item
label="Output Cost (per 1M tokens)"
name="output_cost_per_token"
tooltip="float (optional) - Output cost per 1 million tokens"
>
<TextInput />
</Form.Item>
<Form.Item className="mt-8" label="api_base" name="api_base">
<TextInput />
</Form.Item>
<Form.Item
label="organization"
name="organization"
tooltip="OpenAI Organization ID"
>
<TextInput />
</Form.Item>
<Form.Item
label="tpm"
name="tpm"
tooltip="int (optional) - Tokens limit for this deployment: in tokens per minute (tpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="rpm"
name="rpm"
tooltip="int (optional) - Rate limit for this deployment: in requests per minute (rpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item label="max_retries" name="max_retries">
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="timeout"
name="timeout"
tooltip="int (optional) - Timeout in seconds for LLM requests (Defaults to 600 seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="stream_timeout"
name="stream_timeout"
tooltip="int (optional) - Timeout for stream requests (seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item label="model_id" name="model_id" hidden={true}></Form.Item>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2>
</div>
</Form>
</Modal>
);
};
export default EditModelModal;
@@ -20,6 +20,7 @@ import ConditionalPublicModelName from "./add_model/conditional_public_model_nam
import LiteLLMModelNameField from "./add_model/litellm_model_name";
import AdvancedSettings from "./add_model/advanced_settings";
import { handleAddModelSubmit } from "./add_model/handle_add_model_submit";
import EditModelModal from "./edit_model/edit_model_modal";
import {
TabPanel,
TabPanels,
@@ -367,134 +368,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
return null;
}
const EditModelModal: React.FC<EditModelModalProps> = ({
visible,
onCancel,
model,
onSubmit,
}) => {
const [form] = Form.useForm();
let litellm_params_to_edit: Record<string, any> = {};
let model_name = "";
let model_id = "";
if (model) {
litellm_params_to_edit = model.litellm_params;
model_name = model.model_name;
let model_info = model.model_info;
if (model_info) {
model_id = model_info.id;
console.log(`model_id: ${model_id}`);
litellm_params_to_edit.model_id = model_id;
}
}
const handleOk = () => {
form
.validateFields()
.then((values) => {
onSubmit(values);
form.resetFields();
})
.catch((error) => {
console.error("Validation failed:", error);
});
};
return (
<Modal
title={"Edit Model " + model_name}
visible={visible}
width={800}
footer={null}
onOk={handleOk}
onCancel={onCancel}
>
<Form
form={form}
onFinish={handleEditSubmit}
initialValues={litellm_params_to_edit} // Pass initial values here
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<Form.Item className="mt-8" label="api_base" name="api_base">
<TextInput />
</Form.Item>
<Form.Item
label="organization"
name="organization"
tooltip="OpenAI Organization ID"
>
<TextInput />
</Form.Item>
<Form.Item
label="tpm"
name="tpm"
tooltip="int (optional) - Tokens limit for this deployment: in tokens per minute (tpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="rpm"
name="rpm"
tooltip="int (optional) - Rate limit for this deployment: in requests per minute (rpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item label="max_retries" name="max_retries">
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="timeout"
name="timeout"
tooltip="int (optional) - Timeout in seconds for LLM requests (Defaults to 600 seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="stream_timeout"
name="stream_timeout"
tooltip="int (optional) - Timeout for stream requests (seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="Input Cost per 1M Tokens"
name="input_cost_per_million_tokens"
tooltip="float (optional) - Input cost per 1 million tokens"
>
<InputNumber min={0} step={0.01} />
</Form.Item>
<Form.Item
label="Output Cost per 1M Tokens"
name="output_cost_per_million_tokens"
tooltip="float (optional) - Output cost per 1 million tokens"
>
<InputNumber min={0} step={0.01} />
</Form.Item>
<Form.Item
label="model_id"
name="model_id"
hidden={true}
></Form.Item>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2>
</div>
</Form>
</Modal>
);
};
const handleEditClick = (model: any) => {
setSelectedModel(model);
setEditModalVisible(true);
@@ -526,14 +399,15 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
let newLiteLLMParams: Record<string, any> = {};
let model_info_model_id = null;
if (formValues.input_cost_per_million_tokens) {
formValues.input_cost_per_token = formValues.input_cost_per_million_tokens / 1000000;
delete formValues.input_cost_per_million_tokens;
if (formValues.input_cost_per_token) {
// Convert from per 1M tokens to per token
formValues.input_cost_per_token = Number(formValues.input_cost_per_token) / 1_000_000;
}
if (formValues.output_cost_per_million_tokens) {
formValues.output_cost_per_token = formValues.output_cost_per_million_tokens / 1000000;
delete formValues.output_cost_per_million_tokens;
if (formValues.output_cost_per_token) {
// Convert from per 1M tokens to per token
formValues.output_cost_per_token = Number(formValues.output_cost_per_token) / 1_000_000;
}
for (const [key, value] of Object.entries(formValues)) {
if (key !== "model_id") {