Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ const selectEndpoint = async (endpoint: "/v1/decisions" | "/v1/systemone") => {
await userEvent.click(await screen.findByRole("option", { name: endpoint, exact: true }));
};

const selectPreset = async (label: string) => {
screen.getByRole("combobox", { name: "Example" }).focus();
await userEvent.keyboard("{ArrowDown}");
await userEvent.click(await screen.findByRole("option", { name: label }));
};

const createResponse = (body: SystemOneResponse | OpenAIDecisionsResponse, status = 200, errorText = "") =>
({
ok: status >= 200 && status < 300,
Expand Down Expand Up @@ -99,20 +105,81 @@ describe("SystemOneUI integration", () => {
document.cookie = "token=; path=/; max-age=0";
});

it("restores the example after the request is edited", async () => {
const user = userEvent.setup();
it("switches between example presets and reads Custom once the request is edited", async () => {
render(<SystemOneUI accessToken="session-key" />);
const editor = await openJsonEditor();
const example = (editor as HTMLTextAreaElement).value;
const resetButton = screen.getByRole("button", { name: "Reset example" });
expect(resetButton).toBeDisabled();
const picker = screen.getByRole("combobox", { name: "Example" });
expect(picker).toHaveTextContent("Triage a bug report");

fireEvent.change(editor, { target: { value: "{}" } });
expect(resetButton).toBeEnabled();
await user.click(resetButton);

expect(picker).toHaveTextContent("Custom");
await selectPreset("Triage a bug report");
expect(editor).toHaveValue(example);
expect(resetButton).toBeDisabled();
expect(picker).toHaveTextContent("Triage a bug report");

await selectPreset("Score a sales lead");
expect(picker).toHaveTextContent("Score a sales lead");
expect(JSON.parse((editor as HTMLTextAreaElement).value)).toMatchObject({ questions: { fit: { type: "score" } } });
});

it("clears the whole request and lets a preset be picked from nothing", async () => {
const user = userEvent.setup();
render(<SystemOneUI accessToken="session-key" />);
const clear = screen.getByRole("button", { name: "Clear" });
expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/streaming responses/);

await user.click(clear);
expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue("");
expect(screen.queryAllByRole("group", { name: /Question / })).toHaveLength(0);
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
expect(clear).toBeDisabled();
expect(screen.getByRole("combobox", { name: "Example" })).toHaveTextContent("Custom");

await selectPreset("Route a support ticket");
expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/charged twice/);
expect(screen.getAllByRole("group", { name: /Question / })).toHaveLength(1);
expect(screen.getByRole("button", { name: "Send" })).toBeEnabled();
});

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));
render(<SystemOneUI accessToken="session-key" />);
await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest"));
const picker = screen.getByRole("combobox", { name: "Example" });
const editor = await openJsonEditor();
const example = JSON.parse((editor as HTMLTextAreaElement).value) as Record<string, unknown>;
const { model: _model, ...withoutModel } = example;

fireEvent.change(editor, {
target: { value: JSON.stringify({ ...withoutModel, model: "pplx-decider" }, null, 2) },
});
expect(picker).toHaveTextContent("Triage a bug report");

fireEvent.change(editor, { target: { value: JSON.stringify(withoutModel, null, 2) } });
expect(picker).toHaveTextContent("Triage a bug report");
await userEvent.click(screen.getByRole("button", { name: "Clear" }));
expect(JSON.parse((editor as HTMLTextAreaElement).value)).toEqual({ state: "", questions: {} });
await selectPreset("Score a sales lead");
expect(JSON.parse((editor as HTMLTextAreaElement).value)).not.toHaveProperty("model");
});

it("offers the same presets on /v1/decisions in the OpenAI shape and clears to an empty input", async () => {
const user = userEvent.setup();
render(<SystemOneUI accessToken="session-key" />);
await selectEndpoint("/v1/decisions");
await selectPreset("Moderate a forum comment");
await user.click(screen.getByRole("tab", { name: "JSON" }));
const editor = screen.getByRole("textbox", { name: "Decisions JSON payload" }) as HTMLTextAreaElement;
const payload = JSON.parse(editor.value);
expect(payload.input).toMatch(/stream=false/);
expect(payload.questions).toEqual([
{ type: "predicate", name: "breaks_rules", instructions: "Does this comment break the community guidelines?" },
]);

await user.click(screen.getByRole("button", { name: "Clear" }));
expect(JSON.parse(editor.value)).toEqual({ input: "", questions: [] });
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
});

it("offers both literal proxy endpoints without TypeSafe or product prefixes", async () => {
Expand Down Expand Up @@ -227,7 +294,7 @@ describe("SystemOneUI integration", () => {

await selectEndpoint("/v1/decisions");
expect(screen.getByRole("textbox", { name: "Decisions JSON payload" })).toHaveValue(JSON.stringify(payload));
await user.click(screen.getByRole("button", { name: "Reset example" }));
await selectPreset("Triage a bug report");
expect(screen.getByRole("textbox", { name: "Decisions JSON payload" })).toHaveValue(nativeExample);
await selectEndpoint("/v1/systemone");
expect(screen.getByRole("textbox", { name: "System One JSON payload" })).toHaveValue(systemOneDraft);
Expand Down Expand Up @@ -403,7 +470,7 @@ describe("SystemOneUI integration", () => {
);
expect(next).toEqual({ ...payload, questions: [{ ...payload.questions[0], instructions: "Is it urgent?" }] });
await user.click(screen.getByRole("tab", { name: "Form" }));
await user.click(screen.getByRole("button", { name: "Reset example" }));
await selectPreset("Triage a bug report");
expect(screen.getByRole("textbox", { name: "Input" })).toBeInTheDocument();
expect(screen.getAllByRole("group", { name: /Question \d/ })).toHaveLength(3);
await selectEndpoint("/v1/systemone");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import { DECISIONS_DOCS_URL } from "@/lib/decisionModels";
import { uiHref } from "@/utils/uiHref";
import { useMutation } from "@tanstack/react-query";
import { Code, Info, LoaderCircle, RotateCcw, Send } from "lucide-react";
import { Code, Eraser, Info, LoaderCircle, Send } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { makePlaygroundDecisionRequest, type PlaygroundDecisionRequest } from "../../llm_calls/system_one";
import { decisionsExample, openAIDecisionsExample } from "./lib/example";
import { DECISION_PRESETS, emptyPayload, matchPreset, presetPayload, type DecisionPreset } from "./lib/example";
import { payloadModel, withPayloadModel } from "./lib/payloadModel";
import JsonEditor from "./JsonEditor";
import QuestionBreakdown from "./QuestionBreakdown";
Expand All @@ -35,13 +35,41 @@
signal: AbortSignal;
}

const CUSTOM_PRESET = "custom";
const DECISION_MODELS_DISCUSSION_URL = "https://github.com/BerriAI/litellm/discussions/44231";

interface PresetPickerProps {
activePreset: DecisionPreset | undefined;
onPick: (id: string | null) => void;
}

function PresetPicker({ activePreset, onPick }: PresetPickerProps) {
return (
<Select value={activePreset?.id ?? CUSTOM_PRESET} onValueChange={onPick}>
<SelectTrigger className="w-56" aria-label="Example">
<SelectValue>{activePreset?.label ?? "Custom"}</SelectValue>
</SelectTrigger>
<SelectContent>
{activePreset === undefined && (
<SelectItem value={CUSTOM_PRESET} disabled>
Custom
</SelectItem>
)}
{DECISION_PRESETS.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}

function getCustomProxyBaseUrl(): string | undefined {
return typeof window === "undefined" ? undefined : window.sessionStorage.getItem("customProxyBaseUrl") || undefined;
}

export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = false }: SystemOneUIProps) {

Check warning on line 72 in ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx

View workflow job for this annotation

GitHub Actions / ui

Function 'SystemOneUI' has a complexity of 28. Maximum allowed is 20
const [apiKeySource, setApiKeySource] = useState<ApiKeySource>(disabledPersonalKeyCreation ? "custom" : "session");
const [customApiKey, setCustomApiKey] = useState("");
const effectiveApiKey = apiKeySource === "session" ? accessToken || "" : customApiKey.trim();
Expand All @@ -53,9 +81,10 @@
const [endpoint, setEndpoint] = useState<PlaygroundDecisionRequest["endpoint"]>("/v1/systemone");
const [drafts, setDrafts] = useState<Partial<Record<PlaygroundDecisionRequest["endpoint"], string>>>({});
const [view, setView] = useState<EditorView>("form");
const example = endpoint === "/v1/decisions" ? openAIDecisionsExample : decisionsExample;
const examplePayload = JSON.stringify(example(decisionModels[0]), null, 2);
const rawPayload = drafts[endpoint] ?? examplePayload;
const rawPayload = drafts[endpoint] ?? presetPayload(DECISION_PRESETS[0], endpoint, decisionModels[0]);
const draftModel = drafts[endpoint] === undefined ? decisionModels[0] : payloadModel(rawPayload);
const activePreset = matchPreset(endpoint, rawPayload);
const clearedPayload = emptyPayload(endpoint, draftModel);
const activeController = useRef<AbortController | null>(null);
const validated = useMemo(
() =>
Expand Down Expand Up @@ -95,9 +124,11 @@
}
}

function handleResetExample() {
clearRequestState();
setDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== endpoint)));
function handlePresetPick(id: string | null) {
const preset = DECISION_PRESETS.find((candidate) => candidate.id === id);
if (preset !== undefined) {
handlePayloadChange(presetPayload(preset, endpoint, draftModel));
}
}

function handleModelPick(model: string | null) {
Expand Down Expand Up @@ -204,9 +235,14 @@
)}
</div>
<div className="ml-auto flex items-center gap-2">
<Button variant="outline" onClick={handleResetExample} disabled={rawPayload === examplePayload}>
<RotateCcw />
Reset example
<PresetPicker activePreset={activePreset} onPick={handlePresetPick} />
<Button
variant="outline"
onClick={() => handlePayloadChange(clearedPayload)}
disabled={rawPayload === clearedPayload}
>
<Eraser />
Clear
</Button>
{view === "json" && (
<Button variant="outline" onClick={handleFormatJson} disabled={!rawPayload.trim() || hasSyntaxError}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import {
DECISION_PRESETS,
emptyPayload,
openAIDecisionsExample,
presetPayload,
toOpenAIDecisionsRequest,
matchPreset,
} from "./example";
import { validateOpenAIDecisionsPayload, validateSystemOnePayload } from "./validatePayload";

describe("decision presets", () => {
it.each(DECISION_PRESETS.map((preset) => [preset.label, preset] as const))(
"%s is a valid request on both endpoints",
(_label, preset) => {
expect(
validateSystemOnePayload(presetPayload(preset, "/v1/systemone", "jev-latest"), "/v1/systemone"),
).toMatchObject({ isValid: true, issues: [] });
expect(validateOpenAIDecisionsPayload(presetPayload(preset, "/v1/decisions", "jev-latest"))).toMatchObject({
isValid: true,
issues: [],
});
},
);

it("covers every question type across the presets", () => {
const types = new Set(
DECISION_PRESETS.flatMap((preset) => Object.values(preset.request.questions).map((question) => question.type)),
);
expect([...types].sort()).toEqual(["choice", "noul", "score"]);
});

it("maps System One questions onto the OpenAI shape by name", () => {
const request = toOpenAIDecisionsRequest(DECISION_PRESETS[0].request, "jev-latest");
expect(request.questions.map((question) => [question.type, question.name])).toEqual([
["choice", "area"],
["predicate", "has_repro_steps"],
["score", "severity"],
]);
expect(request).toEqual(openAIDecisionsExample("jev-latest"));
});

it("recognises a preset whatever the model key holds or where it sits", () => {
const lead = DECISION_PRESETS.find((preset) => preset.id === "lead-scoring");
const parsed = JSON.parse(presetPayload(lead!, "/v1/systemone", "jev-latest")) as Record<string, unknown>;
const { model: _model, ...rest } = parsed;
expect(matchPreset("/v1/systemone", JSON.stringify({ ...rest, model: "pplx-decider" }))).toBe(lead);
expect(matchPreset("/v1/systemone", JSON.stringify(rest))).toBe(lead);
expect(matchPreset("/v1/decisions", presetPayload(lead!, "/v1/decisions"))).toBe(lead);
expect(matchPreset("/v1/decisions", presetPayload(lead!, "/v1/systemone"))).toBeUndefined();
expect(matchPreset("/v1/systemone", JSON.stringify({ ...rest, state: "edited" }))).toBeUndefined();
expect(matchPreset("/v1/systemone", "{")).toBeUndefined();
});

it("omits the model from payloads when none is known", () => {
expect(JSON.parse(presetPayload(DECISION_PRESETS[1], "/v1/systemone"))).not.toHaveProperty("model");
expect(JSON.parse(emptyPayload("/v1/decisions"))).toEqual({ input: "", questions: [] });
expect(validateSystemOnePayload(emptyPayload("/v1/systemone"), "/v1/systemone").isValid).toBe(false);
});
});
Loading
Loading