mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
feat(ui): configure logging_only_scope on guardrails
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
1d0db0fbc7
commit
fa5f7904a9
9 changed files with 302 additions and 18 deletions
|
|
@ -6,6 +6,7 @@ import { useController, type Control, type ControllerRenderProps, type RegisterO
|
|||
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { modeIncludesLoggingOnly, type LoggingOnlyScopeChoice } from "./guardrail_info_helpers";
|
||||
|
||||
export interface GuardrailCriterion {
|
||||
name: string;
|
||||
|
|
@ -15,6 +16,7 @@ export interface GuardrailCriterion {
|
|||
|
||||
export interface GuardrailFormValues extends Record<string, unknown> {
|
||||
criteria?: GuardrailCriterion[];
|
||||
logging_only_scope_choice?: LoggingOnlyScopeChoice;
|
||||
}
|
||||
export type GuardrailFormControl = Control<GuardrailFormValues>;
|
||||
export type GuardrailFieldRules = Pick<RegisterOptions<GuardrailFormValues, string>, "validate">;
|
||||
|
|
@ -123,3 +125,49 @@ export const SkipMessageSelect: React.FC<{ control: GuardrailFieldControlProps }
|
|||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
const LOGGING_ONLY_SCOPE_ITEMS: Array<{ label: string; value: LoggingOnlyScopeChoice }> = [
|
||||
{ label: "Default (request and response)", value: "default" },
|
||||
{ label: "Input only (request)", value: "input" },
|
||||
{ label: "Output only (response)", value: "output" },
|
||||
{ label: "Both (request and response)", value: "both" },
|
||||
];
|
||||
|
||||
export const LoggingOnlyScopeSelect: React.FC<{ control: GuardrailFieldControlProps }> = ({ control }) => {
|
||||
const { id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy } = control;
|
||||
|
||||
return (
|
||||
<Select items={LOGGING_ONLY_SCOPE_ITEMS} value={asText(value) || "default"} onValueChange={onChange}>
|
||||
<SelectTrigger id={id} aria-invalid={ariaInvalid} aria-describedby={ariaDescribedBy} className="w-full">
|
||||
<SelectValue placeholder="Select an option" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LOGGING_ONLY_SCOPE_ITEMS.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoggingOnlyScopeField: React.FC<{ control: GuardrailFormControl; mode: unknown }> = ({
|
||||
control,
|
||||
mode,
|
||||
}) => {
|
||||
if (!modeIncludesLoggingOnly(mode)) return null;
|
||||
|
||||
return (
|
||||
<GuardrailField
|
||||
control={control}
|
||||
name="logging_only_scope_choice"
|
||||
label={labelWithHint(
|
||||
"Logging only scope",
|
||||
"Which direction a logging_only scan observes. Observe-only scans never block; pre_call and post_call on this guardrail still block.",
|
||||
)}
|
||||
>
|
||||
{(fieldControl) => <LoggingOnlyScopeSelect control={fieldControl} />}
|
||||
</GuardrailField>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
formatGuardrailMode,
|
||||
formatLoggingOnlyScope,
|
||||
modeIncludesLoggingOnly,
|
||||
} from "./guardrail_info_helpers";
|
||||
|
||||
type GuardrailModeParams = {
|
||||
mode?: unknown;
|
||||
default_on?: boolean;
|
||||
logging_only_scope?: string | null;
|
||||
};
|
||||
|
||||
export const GuardrailModeCard: React.FC<{ litellmParams: GuardrailModeParams }> = ({ litellmParams }) => (
|
||||
<Card className="block p-6">
|
||||
<p>Mode</p>
|
||||
<div className="mt-2">
|
||||
<h3 className="text-lg font-medium">{formatGuardrailMode(litellmParams.mode) || "-"}</h3>
|
||||
<Badge variant={litellmParams.default_on ? "secondary" : "outline"}>
|
||||
{litellmParams.default_on ? "Default On" : "Default Off"}
|
||||
</Badge>
|
||||
</div>
|
||||
{modeIncludesLoggingOnly(litellmParams.mode) && (
|
||||
<div className="mt-4">
|
||||
<p>Logging only scope</p>
|
||||
<h3 className="text-lg font-medium">{formatLoggingOnlyScope(litellmParams.logging_only_scope)}</h3>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
export const GuardrailModeRows: React.FC<{ litellmParams: GuardrailModeParams }> = ({ litellmParams }) => (
|
||||
<>
|
||||
<div>
|
||||
<p className="font-medium">Mode</p>
|
||||
<div>{formatGuardrailMode(litellmParams.mode) || "-"}</div>
|
||||
</div>
|
||||
{modeIncludesLoggingOnly(litellmParams.mode) && (
|
||||
<div>
|
||||
<p className="font-medium">Logging only scope</p>
|
||||
<div>{formatLoggingOnlyScope(litellmParams.logging_only_scope)}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
@ -105,6 +105,48 @@ describe("AddGuardrailForm create payload characterization", () => {
|
|||
expect(payload()).toMatchObject({ litellm_params: { mode: ["pre_call", "post_call"] } });
|
||||
});
|
||||
|
||||
it("sends the selected output logging-only scope", async () => {
|
||||
vi.mocked(networking.getGuardrailUISettings).mockResolvedValue({
|
||||
...uiSettings,
|
||||
supported_modes: ["pre_call", "logging_only"],
|
||||
});
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderForm();
|
||||
|
||||
await user.type(await screen.findByLabelText("Guardrail Name"), "my-bedrock");
|
||||
await pickProvider(user, "Bedrock Guardrail");
|
||||
await user.click(screen.getByLabelText("Mode"));
|
||||
await user.click((await screen.findAllByText("logging_only")).at(-1) as HTMLElement);
|
||||
await chooseSelectOption(
|
||||
user,
|
||||
await screen.findByLabelText("Logging only scope"),
|
||||
"Output only (response)",
|
||||
);
|
||||
|
||||
await user.type(await screen.findByPlaceholderText("The guardrail id on Bedrock"), "gr-123");
|
||||
await user.click(screen.getByRole("button", { name: "Next" }));
|
||||
await user.click(await screen.findByRole("button", { name: "Create Guardrail" }));
|
||||
|
||||
await waitFor(() => expect(networking.createGuardrailCall).toHaveBeenCalledTimes(1));
|
||||
expect(payload()?.litellm_params.logging_only_scope).toBe("output");
|
||||
});
|
||||
|
||||
it("hides logging-only scope and omits it from a pre-call payload", async () => {
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderForm();
|
||||
|
||||
await user.type(await screen.findByLabelText("Guardrail Name"), "my-bedrock");
|
||||
await pickProvider(user, "Bedrock Guardrail");
|
||||
expect(screen.queryByLabelText("Logging only scope")).not.toBeInTheDocument();
|
||||
|
||||
await user.type(await screen.findByPlaceholderText("The guardrail id on Bedrock"), "gr-123");
|
||||
await user.click(screen.getByRole("button", { name: "Next" }));
|
||||
await user.click(await screen.findByRole("button", { name: "Create Guardrail" }));
|
||||
|
||||
await waitFor(() => expect(networking.createGuardrailCall).toHaveBeenCalledTimes(1));
|
||||
expect(payload()?.litellm_params).not.toHaveProperty("logging_only_scope");
|
||||
});
|
||||
|
||||
it("blocks Next when the user deselects every mode", async () => {
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderForm();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useForm, type UseFormReturn } from "react-hook-form";
|
||||
import { useForm, useWatch, type UseFormReturn } from "react-hook-form";
|
||||
import { toast } from "@/lib/toast";
|
||||
import {
|
||||
createGuardrailCall,
|
||||
|
|
@ -10,18 +10,22 @@ import {
|
|||
import ContentFilterConfiguration from "./content_filter/ContentFilterConfiguration";
|
||||
import { type CompetitorIntentConfig } from "./content_filter/CompetitorIntentConfiguration";
|
||||
import {
|
||||
choiceToLoggingOnlyScope,
|
||||
choiceToSkipSystemForCreate,
|
||||
choiceToSkipToolForCreate,
|
||||
getGuardrailLogo,
|
||||
getGuardrailProviders,
|
||||
getSupportedModesForProvider,
|
||||
guardrail_provider_map,
|
||||
modeIncludesLoggingOnly,
|
||||
populateGuardrailProviderMap,
|
||||
populateGuardrailProviders,
|
||||
shouldRenderContentFilterConfigSettings,
|
||||
shouldRenderLLMJudgeFields,
|
||||
shouldRenderPIIConfigSettings,
|
||||
toModeArray,
|
||||
type LoggingOnlyScope,
|
||||
type LoggingOnlyScopeChoice,
|
||||
} from "./guardrail_info_helpers";
|
||||
import { Logo } from "@/components/molecules/logo/Logo";
|
||||
import { MultiSelect } from "@/components/shared/MultiSelect";
|
||||
|
|
@ -49,6 +53,7 @@ import {
|
|||
requiredRule,
|
||||
type GuardrailCriterion,
|
||||
type GuardrailFormValues,
|
||||
LoggingOnlyScopeField,
|
||||
SkipMessageSelect,
|
||||
} from "./GuardrailFormField";
|
||||
import GuardrailOptionalParams from "./guardrail_optional_params";
|
||||
|
|
@ -160,6 +165,7 @@ type SkipMessageChoice = "inherit" | "yes" | "no";
|
|||
const INITIAL_VALUES: GuardrailFormValues = {
|
||||
mode: "pre_call",
|
||||
default_on: false,
|
||||
logging_only_scope_choice: "default",
|
||||
skip_system_message_choice: "inherit",
|
||||
skip_tool_message_choice: "inherit",
|
||||
};
|
||||
|
|
@ -199,6 +205,7 @@ interface ProviderParamsResponse {
|
|||
|
||||
const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, accessToken, onSuccess, preset }) => {
|
||||
const form = useForm<GuardrailFormValues>({ defaultValues: INITIAL_VALUES });
|
||||
const watchedMode = useWatch({ control: form.control, name: "mode" });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
||||
const [guardrailSettings, setGuardrailSettings] = useState<GuardrailSettings | null>(null);
|
||||
|
|
@ -277,6 +284,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
guardrail_name: preset.guardrailNameSuggestion,
|
||||
mode: preset.mode,
|
||||
default_on: preset.defaultOn,
|
||||
logging_only_scope_choice: "default",
|
||||
skip_system_message_choice: "inherit",
|
||||
skip_tool_message_choice: "inherit",
|
||||
};
|
||||
|
|
@ -439,6 +447,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
guardrail_name: string;
|
||||
litellm_params: {
|
||||
guardrail: string;
|
||||
logging_only_scope?: LoggingOnlyScope | null;
|
||||
[key: string]: unknown; // Allow dynamic properties
|
||||
};
|
||||
guardrail_info: Record<string, unknown>;
|
||||
|
|
@ -462,6 +471,13 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
guardrailData.litellm_params.skip_tool_message_in_guardrail = skipToolForCreate;
|
||||
}
|
||||
|
||||
const loggingOnlyScope = choiceToLoggingOnlyScope(
|
||||
values.logging_only_scope_choice as LoggingOnlyScopeChoice | undefined,
|
||||
);
|
||||
if (modeIncludesLoggingOnly(values.mode) && loggingOnlyScope !== null) {
|
||||
guardrailData.litellm_params.logging_only_scope = loggingOnlyScope;
|
||||
}
|
||||
|
||||
// For Presidio PII, add the entity and action configurations
|
||||
if (providerKey === "PresidioPII" && selectedEntities.length > 0) {
|
||||
const piiEntitiesConfig: { [key: string]: string } = {};
|
||||
|
|
@ -796,6 +812,8 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
{(fieldControl) => <SkipMessageSelect control={fieldControl} />}
|
||||
</GuardrailField>
|
||||
|
||||
<LoggingOnlyScopeField control={form.control} mode={watchedMode} />
|
||||
|
||||
{/* Use the GuardrailProviderFields component to render provider-specific fields */}
|
||||
{showProviderFields && (
|
||||
<GuardrailProviderFields
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from "react";
|
|||
import { CheckCircle2, ChevronRight, Code, ExternalLink, PlayCircle, Save, Users, XCircle } from "lucide-react";
|
||||
import { createGuardrailCall, updateGuardrailCall, testCustomCodeGuardrail } from "@/components/networking";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { type LoggingOnlyScope } from "../guardrail_info_helpers";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import {
|
||||
|
|
@ -178,6 +179,7 @@ export interface EditGuardrailData {
|
|||
mode?: string | string[];
|
||||
default_on?: boolean;
|
||||
custom_code?: string;
|
||||
logging_only_scope?: LoggingOnlyScope | null;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,51 @@ describe("GuardrailInfoView update payload characterization", () => {
|
|||
expect(lastPayload()).toEqual({ litellm_params: { skip_system_message_in_guardrail: true } });
|
||||
});
|
||||
|
||||
it("shows and updates the logging-only scope", async () => {
|
||||
vi.mocked(networking.getGuardrailInfo).mockResolvedValue(
|
||||
guardrail({
|
||||
guardrailIdentifier: "gr-abc",
|
||||
api_key: "sk-old",
|
||||
mode: "logging_only",
|
||||
logging_only_scope: "input",
|
||||
}),
|
||||
);
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderView();
|
||||
|
||||
expect(await screen.findAllByText("Input only (request)")).toHaveLength(2);
|
||||
await openEditor(user);
|
||||
await chooseSelectOption(
|
||||
user,
|
||||
screen.getByLabelText("Logging only scope"),
|
||||
"Output only (response)",
|
||||
);
|
||||
await saveChanges(user);
|
||||
|
||||
await waitFor(() => expect(networking.updateGuardrailCall).toHaveBeenCalledTimes(1));
|
||||
expect(lastPayload()).toEqual({ litellm_params: { logging_only_scope: "output" } });
|
||||
});
|
||||
|
||||
it("clears the logging-only scope when the edit choice returns to default", async () => {
|
||||
vi.mocked(networking.getGuardrailInfo).mockResolvedValue(
|
||||
guardrail({
|
||||
guardrailIdentifier: "gr-abc",
|
||||
api_key: "sk-old",
|
||||
mode: "logging_only",
|
||||
logging_only_scope: "input",
|
||||
}),
|
||||
);
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderView();
|
||||
await openEditor(user);
|
||||
|
||||
await chooseSelectOption(user, screen.getByLabelText("Logging only scope"), "Default (request and response)");
|
||||
await saveChanges(user);
|
||||
|
||||
await waitFor(() => expect(networking.updateGuardrailCall).toHaveBeenCalledTimes(1));
|
||||
expect(lastPayload()).toEqual({ litellm_params: { logging_only_scope: null } });
|
||||
});
|
||||
|
||||
it("parses the guardrail information textarea into an object", async () => {
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderView();
|
||||
|
|
|
|||
|
|
@ -28,14 +28,17 @@ import {
|
|||
readRecord,
|
||||
requiredRule,
|
||||
type GuardrailFormValues,
|
||||
LoggingOnlyScopeField,
|
||||
SkipMessageSelect,
|
||||
} from "./GuardrailFormField";
|
||||
import ContentFilterManager, { formatContentFilterDataForAPI } from "./content_filter/ContentFilterManager";
|
||||
import CustomCodeModal, { EditGuardrailData } from "./custom_code/CustomCodeModal";
|
||||
import { GuardrailModeCard, GuardrailModeRows } from "./GuardrailModeDisplay";
|
||||
import {
|
||||
formatGuardrailMode,
|
||||
getLoggingOnlyScopeUpdate,
|
||||
getGuardrailLogoAndName,
|
||||
guardrail_provider_map,
|
||||
loggingOnlyScopeToChoice,
|
||||
skipSystemMessageToChoice,
|
||||
skipToolMessageToChoice,
|
||||
type SkipSystemMessageChoice,
|
||||
|
|
@ -219,6 +222,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
if (!guardrailData) return;
|
||||
form.setValue("guardrail_name", guardrailData.guardrail_name);
|
||||
form.setValue("default_on", guardrailData.litellm_params?.default_on);
|
||||
form.setValue("logging_only_scope_choice", loggingOnlyScopeToChoice(guardrailData.litellm_params?.logging_only_scope));
|
||||
form.setValue(
|
||||
"skip_system_message_choice",
|
||||
skipSystemMessageToChoice(guardrailData.litellm_params?.skip_system_message_in_guardrail),
|
||||
|
|
@ -282,7 +286,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
|
||||
// Prepare update data object - only include changed fields
|
||||
const updateData: any = {
|
||||
litellm_params: {},
|
||||
litellm_params: getLoggingOnlyScopeUpdate(guardrailData.litellm_params, values.logging_only_scope_choice),
|
||||
};
|
||||
|
||||
// Only include guardrail_name if it has changed
|
||||
|
|
@ -556,17 +560,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="block p-6">
|
||||
<p>Mode</p>
|
||||
<div className="mt-2">
|
||||
<h3 className="text-lg font-medium">
|
||||
{formatGuardrailMode(guardrailData.litellm_params?.mode) || "-"}
|
||||
</h3>
|
||||
<Badge variant={guardrailData.litellm_params?.default_on ? "secondary" : "outline"}>
|
||||
{guardrailData.litellm_params?.default_on ? "Default On" : "Default Off"}
|
||||
</Badge>
|
||||
</div>
|
||||
</Card>
|
||||
<GuardrailModeCard litellmParams={guardrailData.litellm_params} />
|
||||
|
||||
<Card className="block p-6">
|
||||
<p>Created At</p>
|
||||
|
|
@ -745,6 +739,10 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
>
|
||||
{(fieldControl) => <SkipMessageSelect control={fieldControl} />}
|
||||
</GuardrailField>
|
||||
<LoggingOnlyScopeField
|
||||
control={form.control}
|
||||
mode={guardrailData.litellm_params?.mode}
|
||||
/>
|
||||
{guardrailData.litellm_params?.guardrail === "presidio" && (
|
||||
<>
|
||||
<SectionHeading>PII Protection</SectionHeading>
|
||||
|
|
@ -856,10 +854,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
<p className="font-medium">Provider</p>
|
||||
<div>{displayName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">Mode</p>
|
||||
<div>{formatGuardrailMode(guardrailData.litellm_params?.mode) || "-"}</div>
|
||||
</div>
|
||||
<GuardrailModeRows litellmParams={guardrailData.litellm_params} />
|
||||
<div>
|
||||
<p className="font-medium">Default On</p>
|
||||
<Badge variant={guardrailData.litellm_params?.default_on ? "secondary" : "outline"}>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ import {
|
|||
skipToolMessageToChoice,
|
||||
choiceToSkipToolForCreate,
|
||||
formatGuardrailMode,
|
||||
loggingOnlyScopeToChoice,
|
||||
choiceToLoggingOnlyScope,
|
||||
getLoggingOnlyScopeUpdate,
|
||||
formatLoggingOnlyScope,
|
||||
modeIncludesLoggingOnly,
|
||||
} from "./guardrail_info_helpers";
|
||||
|
||||
describe("guardrail_info_helpers", () => {
|
||||
|
|
@ -239,6 +244,52 @@ describe("guardrail_info_helpers", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("logging-only scope helpers", () => {
|
||||
it("maps API scope values to choices and back", () => {
|
||||
expect(loggingOnlyScopeToChoice("input")).toBe("input");
|
||||
expect(loggingOnlyScopeToChoice("output")).toBe("output");
|
||||
expect(loggingOnlyScopeToChoice("both")).toBe("both");
|
||||
expect(loggingOnlyScopeToChoice(undefined)).toBe("default");
|
||||
expect(loggingOnlyScopeToChoice(null)).toBe("default");
|
||||
expect(loggingOnlyScopeToChoice("invalid")).toBe("default");
|
||||
|
||||
expect(choiceToLoggingOnlyScope("default")).toBeNull();
|
||||
expect(choiceToLoggingOnlyScope(undefined)).toBeNull();
|
||||
expect(choiceToLoggingOnlyScope("input")).toBe("input");
|
||||
expect(choiceToLoggingOnlyScope("output")).toBe("output");
|
||||
expect(choiceToLoggingOnlyScope("both")).toBe("both");
|
||||
|
||||
expect(getLoggingOnlyScopeUpdate({ logging_only_scope: "input" }, "input")).toEqual({});
|
||||
expect(getLoggingOnlyScopeUpdate({ logging_only_scope: "input" }, "output")).toEqual({
|
||||
logging_only_scope: "output",
|
||||
});
|
||||
expect(getLoggingOnlyScopeUpdate({ logging_only_scope: "input" }, "default")).toEqual({
|
||||
logging_only_scope: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("formats every scope and falls back to default for missing or unknown values", () => {
|
||||
expect(formatLoggingOnlyScope("input")).toBe("Input only (request)");
|
||||
expect(formatLoggingOnlyScope("output")).toBe("Output only (response)");
|
||||
expect(formatLoggingOnlyScope("both")).toBe("Both (request and response)");
|
||||
expect(formatLoggingOnlyScope(undefined)).toBe("Default (request and response)");
|
||||
expect(formatLoggingOnlyScope(null)).toBe("Default (request and response)");
|
||||
expect(formatLoggingOnlyScope("invalid")).toBe("Default (request and response)");
|
||||
});
|
||||
|
||||
it("detects logging_only in string, array, and tagged mode values", () => {
|
||||
expect(modeIncludesLoggingOnly("logging_only")).toBe(true);
|
||||
expect(modeIncludesLoggingOnly(["pre_call", "logging_only"])).toBe(true);
|
||||
expect(
|
||||
modeIncludesLoggingOnly({
|
||||
tags: { "Service-Type: internal-service": "logging_only" },
|
||||
default: "pre_call",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(modeIncludesLoggingOnly("pre_call")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("skipSystemMessageToChoice / choiceToSkipSystemForCreate", () => {
|
||||
it("maps API values to form choices and back for create", () => {
|
||||
expect(skipSystemMessageToChoice(undefined)).toBe("inherit");
|
||||
|
|
|
|||
|
|
@ -114,6 +114,42 @@ export const toModeArray = (raw: unknown): string[] => {
|
|||
return [];
|
||||
};
|
||||
|
||||
export type LoggingOnlyScope = "input" | "output" | "both";
|
||||
export type LoggingOnlyScopeChoice = "default" | LoggingOnlyScope;
|
||||
|
||||
export const loggingOnlyScopeToChoice = (v: string | null | undefined): LoggingOnlyScopeChoice =>
|
||||
v === "input" || v === "output" || v === "both" ? v : "default";
|
||||
|
||||
export const choiceToLoggingOnlyScope = (choice: LoggingOnlyScopeChoice | undefined): LoggingOnlyScope | null =>
|
||||
choice === "input" || choice === "output" || choice === "both" ? choice : null;
|
||||
|
||||
export const getLoggingOnlyScopeUpdate = (
|
||||
litellmParams: { logging_only_scope?: string | null } | null | undefined,
|
||||
choice: LoggingOnlyScopeChoice | undefined,
|
||||
): { logging_only_scope?: LoggingOnlyScope | null } => {
|
||||
if (choice === undefined || choice === loggingOnlyScopeToChoice(litellmParams?.logging_only_scope)) return {};
|
||||
return { logging_only_scope: choiceToLoggingOnlyScope(choice) };
|
||||
};
|
||||
|
||||
export const formatLoggingOnlyScope = (v: string | null | undefined): string => {
|
||||
if (v === "input") return "Input only (request)";
|
||||
if (v === "output") return "Output only (response)";
|
||||
if (v === "both") return "Both (request and response)";
|
||||
return "Default (request and response)";
|
||||
};
|
||||
|
||||
export const modeIncludesLoggingOnly = (raw: unknown): boolean => {
|
||||
if (toModeArray(raw).includes("logging_only")) return true;
|
||||
if (raw === null || typeof raw !== "object") return false;
|
||||
|
||||
const { tags, default: fallback } = raw as { tags?: Record<string, unknown>; default?: unknown };
|
||||
const taggedModes =
|
||||
tags && typeof tags === "object"
|
||||
? Object.values(tags).some((mode) => toModeArray(mode).includes("logging_only"))
|
||||
: false;
|
||||
return toModeArray(fallback).includes("logging_only") || taggedModes;
|
||||
};
|
||||
|
||||
export const formatGuardrailMode = (raw: unknown): string => {
|
||||
const flat: string[] = toModeArray(raw);
|
||||
if (flat.length > 0) return flat.join(", ");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue