From 7a2c5005215a46f4a67fdf304091e860490437dd Mon Sep 17 00:00:00 2001 From: GaetanVDB07 <86427581+GaetanVDB07@users.noreply.github.com> Date: Fri, 12 Jun 2026 13:54:40 +0200 Subject: [PATCH] fix(ui): infer Azure API version from API base (#30204) * fix(ui): infer Azure API version from API base * fix(ui): address Azure API version feedback --- .../provider_specific_fields.test.tsx | 132 +++++++++++++++++- .../add_model/provider_specific_fields.tsx | 37 +++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx index 4590121acf..c2b730bf46 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { Form } from "antd"; import { beforeAll, describe, expect, it, vi } from "vitest"; import { Providers } from "../provider_info_helpers"; @@ -215,4 +215,134 @@ describe("ProviderSpecificFields", () => { expect(baseModelInput).toBeInTheDocument(); }); }); + + it("sets Azure API version from the API base query parameter", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, + ); + + const apiBaseInput = await screen.findByPlaceholderText("https://..."); + const apiVersionInput = await screen.findByPlaceholderText("2023-07-01-preview"); + + fireEvent.change(apiBaseInput, { + target: { + value: + "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions?api_version=2024-10-21", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2024-10-21"); + }); + }); + + it("sets Azure API version from the hyphenated API base query parameter", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, + ); + + const apiBaseInput = await screen.findByPlaceholderText("https://..."); + const apiVersionInput = await screen.findByPlaceholderText("2023-07-01-preview"); + + fireEvent.change(apiBaseInput, { + target: { + value: + "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions?api-version=2024-10-21", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2024-10-21"); + }); + }); + + it("clears an inferred Azure API version when the API base has no version parameter", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, + ); + + const apiBaseInput = await screen.findByPlaceholderText("https://..."); + const apiVersionInput = await screen.findByPlaceholderText("2023-07-01-preview"); + + fireEvent.change(apiBaseInput, { + target: { + value: + "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions?api-version=2024-10-21", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2024-10-21"); + }); + + fireEvent.change(apiBaseInput, { + target: { + value: "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue(""); + }); + }); + + it("preserves a manually edited Azure API version when the API base has no version parameter", async () => { + const queryClient = createQueryClient(); + render( + +
+ + +
, + ); + + const apiBaseInput = await screen.findByPlaceholderText("https://..."); + const apiVersionInput = await screen.findByPlaceholderText("2023-07-01-preview"); + + fireEvent.change(apiBaseInput, { + target: { + value: + "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions?api-version=2024-10-21", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2024-10-21"); + }); + + fireEvent.change(apiVersionInput, { + target: { + value: "2025-01-01-preview", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2025-01-01-preview"); + }); + + fireEvent.change(apiBaseInput, { + target: { + value: "https://test-resource.openai.azure.com/openai/deployments/gpt-4/chat/completions", + }, + }); + + await waitFor(() => { + expect(apiVersionInput).toHaveValue("2025-01-01-preview"); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx index 24df0ac21e..045a9b0c1b 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx @@ -28,6 +28,18 @@ export interface CredentialValues { value: string; } +const getApiVersionFromApiBase = (apiBase: string): string | null => { + const queryStartIndex = apiBase.indexOf("?"); + if (queryStartIndex === -1) { + return null; + } + + const queryString = apiBase.slice(queryStartIndex + 1).split("#")[0]; + const searchParams = new URLSearchParams(queryString); + + return searchParams.get("api_version") || searchParams.get("api-version"); +}; + const mapFieldMetadataToUiField = (field: ProviderCredentialFieldMetadata): ProviderCredentialField => { const type: ProviderCredentialField["type"] = field.field_type === "password" @@ -167,6 +179,30 @@ const ProviderSpecificFields: React.FC = ({ selecte return mapped; }, [selectedProviderEnum, selectedProvider, providerMetadata]); + const hasApiVersionField = React.useMemo(() => allFields.some((field) => field.key === "api_version"), [allFields]); + const lastInferredApiVersionRef = React.useRef(null); + + const handleApiBaseChange = React.useCallback( + (event: React.ChangeEvent) => { + if (!hasApiVersionField) { + return; + } + + const apiVersion = getApiVersionFromApiBase(event.target.value); + if (apiVersion) { + lastInferredApiVersionRef.current = apiVersion; + form.setFieldsValue({ api_version: apiVersion }); + return; + } + + if (form.getFieldValue("api_version") === lastInferredApiVersionRef.current) { + form.setFieldsValue({ api_version: "" }); + } + lastInferredApiVersionRef.current = null; + }, + [form, hasApiVersionField], + ); + const handleUpload = { name: "file", accept: ".json", @@ -261,6 +297,7 @@ const ProviderSpecificFields: React.FC = ({ selecte placeholder={field.placeholder} type={field.type === "password" ? "password" : "text"} defaultValue={field.defaultValue} + onChange={field.key === "api_base" ? handleApiBaseChange : undefined} /> )}