Skip to content

Commit be4152c

Browse files
UI - fix edit azure public model name + fix editing model name post create
* test(test_router.py): add unit test confirming fallbacks with tag based routing works as expected * test: update testing * test: update test to not use gemini-pro google removed it * fix(conditional_public_model_name.tsx): edit azure public model name Fixes #10093 * fix(model_info_view.tsx): migrate to patch model updates Enables changing model name easily
1 parent acd2c17 commit be4152c

File tree

5 files changed

+136
-21
lines changed

5 files changed

+136
-21
lines changed

tests/litellm/test_router.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,43 @@ def test_router_with_model_info_and_model_group():
7878
model_group="gpt-3.5-turbo",
7979
user_facing_model_group_name="gpt-3.5-turbo",
8080
)
81+
82+
83+
@pytest.mark.asyncio
84+
async def test_router_with_tags_and_fallbacks():
85+
"""
86+
If fallback model missing tag, raise error
87+
"""
88+
from litellm import Router
89+
90+
router = Router(
91+
model_list=[
92+
{
93+
"model_name": "gpt-3.5-turbo",
94+
"litellm_params": {
95+
"model": "gpt-3.5-turbo",
96+
"mock_response": "Hello, world!",
97+
"tags": ["test"],
98+
},
99+
},
100+
{
101+
"model_name": "anthropic-claude-3-5-sonnet",
102+
"litellm_params": {
103+
"model": "claude-3-5-sonnet-latest",
104+
"mock_response": "Hello, world 2!",
105+
},
106+
},
107+
],
108+
fallbacks=[
109+
{"gpt-3.5-turbo": ["anthropic-claude-3-5-sonnet"]},
110+
],
111+
enable_tag_filtering=True,
112+
)
113+
114+
with pytest.raises(Exception):
115+
response = await router.acompletion(
116+
model="gpt-3.5-turbo",
117+
messages=[{"role": "user", "content": "Hello, world!"}],
118+
mock_testing_fallbacks=True,
119+
metadata={"tags": ["test"]},
120+
)

ui/litellm-dashboard/src/components/add_model/conditional_public_model_name.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const ConditionalPublicModelName: React.FC = () => {
1414
const customModelName = Form.useWatch('custom_model_name', form);
1515
const showPublicModelName = !selectedModels.includes('all-wildcard');
1616

17+
1718
// Force table to re-render when custom model name changes
1819
useEffect(() => {
1920
if (customModelName && selectedModels.includes('custom')) {
@@ -35,20 +36,33 @@ const ConditionalPublicModelName: React.FC = () => {
3536
// Initial setup of model mappings when models are selected
3637
useEffect(() => {
3738
if (selectedModels.length > 0 && !selectedModels.includes('all-wildcard')) {
38-
const mappings = selectedModels.map((model: string) => {
39-
if (model === 'custom' && customModelName) {
39+
// Check if we already have mappings that match the selected models
40+
const currentMappings = form.getFieldValue('model_mappings') || [];
41+
42+
// Only update if the mappings don't exist or don't match the selected models
43+
const shouldUpdateMappings = currentMappings.length !== selectedModels.length ||
44+
!selectedModels.every(model =>
45+
currentMappings.some((mapping: { public_name: string; litellm_model: string }) =>
46+
mapping.public_name === model ||
47+
(model === 'custom' && mapping.public_name === customModelName)));
48+
49+
if (shouldUpdateMappings) {
50+
const mappings = selectedModels.map((model: string) => {
51+
if (model === 'custom' && customModelName) {
52+
return {
53+
public_name: customModelName,
54+
litellm_model: customModelName
55+
};
56+
}
4057
return {
41-
public_name: customModelName,
42-
litellm_model: customModelName
58+
public_name: model,
59+
litellm_model: model
4360
};
44-
}
45-
return {
46-
public_name: model,
47-
litellm_model: model
48-
};
49-
});
50-
form.setFieldValue('model_mappings', mappings);
51-
setTableKey(prev => prev + 1); // Force table re-render
61+
});
62+
63+
form.setFieldValue('model_mappings', mappings);
64+
setTableKey(prev => prev + 1); // Force table re-render
65+
}
5266
}
5367
}, [selectedModels, customModelName, form]);
5468

ui/litellm-dashboard/src/components/add_model/litellm_model_name.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,34 @@ const LiteLLMModelNameField: React.FC<LiteLLMModelNameFieldProps> = ({
2323

2424
// If "all-wildcard" is selected, clear the model_name field
2525
if (values.includes("all-wildcard")) {
26-
form.setFieldsValue({ model_name: undefined, model_mappings: [] });
26+
form.setFieldsValue({ model: undefined, model_mappings: [] });
2727
} else {
28-
// Update model mappings immediately for each selected model
29-
const mappings = values
30-
.map(model => ({
28+
// Get current model value to check if we need to update
29+
const currentModel = form.getFieldValue('model');
30+
31+
// Only update if the value has actually changed
32+
if (JSON.stringify(currentModel) !== JSON.stringify(values)) {
33+
34+
// Create mappings first
35+
const mappings = values.map(model => ({
3136
public_name: model,
3237
litellm_model: model
3338
}));
34-
form.setFieldsValue({ model_mappings: mappings });
39+
40+
// Update both fields in one call to reduce re-renders
41+
form.setFieldsValue({
42+
model: values,
43+
model_mappings: mappings
44+
});
45+
46+
}
3547
}
3648
};
3749

3850
// Handle custom model name changes
3951
const handleCustomModelNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
4052
const customName = e.target.value;
41-
53+
4254
// Immediately update the model mappings
4355
const currentMappings = form.getFieldValue('model_mappings') || [];
4456
const updatedMappings = currentMappings.map((mapping: any) => {
@@ -69,7 +81,11 @@ const LiteLLMModelNameField: React.FC<LiteLLMModelNameFieldProps> = ({
6981
{(selectedProvider === Providers.Azure) ||
7082
(selectedProvider === Providers.OpenAI_Compatible) ||
7183
(selectedProvider === Providers.Ollama) ? (
72-
<TextInput placeholder={getPlaceholder(selectedProvider)} />
84+
<>
85+
<TextInput
86+
placeholder={getPlaceholder(selectedProvider)}
87+
/>
88+
</>
7389
) : providerModels.length > 0 ? (
7490
<AntSelect
7591
mode="multiple"

ui/litellm-dashboard/src/components/model_info_view.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from "@tremor/react";
1616
import NumericalInput from "./shared/numerical_input";
1717
import { ArrowLeftIcon, TrashIcon, KeyIcon } from "@heroicons/react/outline";
18-
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall, modelInfoCall, modelInfoV1Call } from "./networking";
18+
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall, modelInfoCall, modelInfoV1Call, modelPatchUpdateCall } from "./networking";
1919
import { Button, Form, Input, InputNumber, message, Select, Modal } from "antd";
2020
import EditModelModal from "./edit_model/edit_model_modal";
2121
import { handleEditModelSubmit } from "./edit_model/edit_model_modal";
@@ -118,6 +118,8 @@ export default function ModelInfoView({
118118
try {
119119
if (!accessToken) return;
120120
setIsSaving(true);
121+
122+
console.log("values.model_name, ", values.model_name);
121123

122124
let updatedLitellmParams = {
123125
...localModelData.litellm_params,
@@ -149,7 +151,7 @@ export default function ModelInfoView({
149151
}
150152
};
151153

152-
await modelUpdateCall(accessToken, updateData);
154+
await modelPatchUpdateCall(accessToken, updateData, modelId);
153155

154156
const updatedModelData = {
155157
...localModelData,

ui/litellm-dashboard/src/components/networking.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3152,6 +3152,49 @@ export const teamUpdateCall = async (
31523152
}
31533153
};
31543154

3155+
/**
3156+
* Patch update a model
3157+
*
3158+
* @param accessToken
3159+
* @param formValues
3160+
* @returns
3161+
*/
3162+
export const modelPatchUpdateCall = async (
3163+
accessToken: string,
3164+
formValues: Record<string, any>, // Assuming formValues is an object
3165+
modelId: string
3166+
) => {
3167+
try {
3168+
console.log("Form Values in modelUpateCall:", formValues); // Log the form values before making the API call
3169+
3170+
const url = proxyBaseUrl ? `${proxyBaseUrl}/model/${modelId}/update` : `/model/${modelId}/update`;
3171+
const response = await fetch(url, {
3172+
method: "PATCH",
3173+
headers: {
3174+
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
3175+
"Content-Type": "application/json",
3176+
},
3177+
body: JSON.stringify({
3178+
...formValues, // Include formValues in the request body
3179+
}),
3180+
});
3181+
3182+
if (!response.ok) {
3183+
const errorData = await response.text();
3184+
handleError(errorData);
3185+
console.error("Error update from the server:", errorData);
3186+
throw new Error("Network response was not ok");
3187+
}
3188+
const data = await response.json();
3189+
console.log("Update model Response:", data);
3190+
return data;
3191+
// Handle success - you might want to update some state or UI based on the created key
3192+
} catch (error) {
3193+
console.error("Failed to update model:", error);
3194+
throw error;
3195+
}
3196+
};
3197+
31553198
export const modelUpdateCall = async (
31563199
accessToken: string,
31573200
formValues: Record<string, any> // Assuming formValues is an object

0 commit comments

Comments
 (0)