diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx index eb75c836b2b..33aee764fbb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx @@ -15,15 +15,15 @@ import SystemOneUI, { NOUL_HELP } from "./SystemOneUI"; import type { SystemOneResponse } from "./lib/schemas"; import type { OpenAIDecisionsResponse } from "./lib/openAIDecisions"; -interface ModelGroupInfo { - model_group: string; +interface Deployment { + model_name: string; mode: string; } -const DECISION_AND_CHAT_MODELS: ModelGroupInfo[] = [ - { model_group: "gpt-5.5", mode: "chat" }, - { model_group: "jev-latest", mode: "evaluation" }, - { model_group: "pplx-decider", mode: "evaluation" }, +const DECISION_AND_CHAT_MODELS: Deployment[] = [ + { model_name: "gpt-5.5", mode: "chat" }, + { model_name: "jev-latest", mode: "evaluation" }, + { model_name: "pplx-decider", mode: "evaluation" }, ]; const responseBody: SystemOneResponse = { @@ -45,8 +45,19 @@ const render = (ui: ReactElement) => , ); -const modelGroupInfoResponse = (groups: ModelGroupInfo[]) => - ({ ok: true, status: 200, text: async () => JSON.stringify({ data: groups }) }) as Response; +const modelInfoResponse = (deployments: Deployment[]) => + ({ + ok: true, + status: 200, + text: async () => + JSON.stringify({ + data: deployments.map(({ model_name, mode }) => ({ + model_name, + litellm_params: { model: `openai/${model_name}` }, + model_info: { id: model_name, mode }, + })), + }), + }) as Response; const EXPIRED_KEY_RESPONSE = { ok: false, @@ -55,7 +66,7 @@ const EXPIRED_KEY_RESPONSE = { JSON.stringify({ error: { message: "Authentication Error - Expired Key.", type: "expired_key", code: "401" } }), } as Response; -const isModelLookup = (input: RequestInfo | URL) => String(input).endsWith("/model_group/info"); +const isModelLookup = (input: RequestInfo | URL) => String(input).endsWith("/model/info"); const lookupAuthHeaders = (calls: Parameters[]) => calls.map(([, init]) => Object.values((init?.headers ?? {}) as Record)); @@ -96,7 +107,7 @@ describe("SystemOneUI integration", () => { isModelLookup(input) ? mockModelLookup(input, init) : mockFetch(input, init), ); mockFetch.mockResolvedValue(createResponse(responseBody)); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse([])); + mockModelLookup.mockResolvedValue(modelInfoResponse([])); }); afterEach(() => { @@ -143,7 +154,7 @@ describe("SystemOneUI integration", () => { }); it("keeps the preset label after picking a model and leaves an omitted model out when clearing", async () => { - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); const picker = screen.getByRole("combobox", { name: "Example" }); @@ -210,7 +221,7 @@ describe("SystemOneUI integration", () => { it("opens on /v1/systemone with the first decision model the key can call", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); @@ -228,7 +239,7 @@ describe("SystemOneUI integration", () => { it("sends each endpoint's real shape and switches existing responses between cards and JSON without resending", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); const systemOneEditor = await openJsonEditor(); @@ -519,7 +530,7 @@ describe("SystemOneUI integration", () => { it("offers only decision models and writes the picked one into the request", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); const picker = await screen.findByRole("combobox", { name: "Decision model" }); @@ -542,9 +553,7 @@ describe("SystemOneUI integration", () => { it("routes a configured TypeSafe model through the same proxy endpoint", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue( - modelGroupInfoResponse([{ model_group: "typesafe/jev-latest", mode: "evaluation" }]), - ); + mockModelLookup.mockResolvedValue(modelInfoResponse([{ model_name: "typesafe/jev-latest", mode: "evaluation" }])); render(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("typesafe/jev-latest"), @@ -573,7 +582,7 @@ describe("SystemOneUI integration", () => { it("looks up a typed virtual key's decision models once, after typing settles", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); await user.type(screen.getByLabelText("Virtual Key", { exact: true }), "sk-typed-key"); @@ -606,7 +615,7 @@ describe("SystemOneUI integration", () => { fireEvent.change(editor, { target: { value: draft } }); await act(async () => { - models.resolve(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + models.resolve(modelInfoResponse(DECISION_AND_CHAT_MODELS)); await new Promise((resolve) => setTimeout(resolve, 0)); }); @@ -635,7 +644,7 @@ describe("SystemOneUI integration", () => { expect(mockFetch.mock.calls[0]?.[0]).toMatch(new RegExp(`${endpoint}$`)); expect(JSON.parse(mockFetch.mock.calls[0]?.[1]?.body as string)).not.toHaveProperty("model"); await act(async () => { - models.resolve(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + models.resolve(modelInfoResponse(DECISION_AND_CHAT_MODELS)); await models.promise; }); expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue(""); @@ -676,7 +685,7 @@ describe("SystemOneUI integration", () => { it("uses the requested model when the response omits it", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); mockFetch.mockResolvedValueOnce( createResponse({ answers: responseBody.answers, @@ -984,7 +993,7 @@ describe("SystemOneUI integration", () => { it("edits score levels and keeps instructions when a question changes type", async () => { const user = userEvent.setup(); - mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + mockModelLookup.mockResolvedValue(modelInfoResponse(DECISION_AND_CHAT_MODELS)); render(); await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest"));