mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(guardrails): normalize logging-only scope and sanitize warning logs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
bc423af734
commit
d3bb9a6a4a
6 changed files with 43 additions and 48 deletions
|
|
@ -472,7 +472,7 @@ def _configure_callback_scoping(
|
|||
raise ValueError(logging_only_scope_error)
|
||||
verbose_proxy_logger.error(
|
||||
"%s Ignoring logging_only_scope; the guardrail keeps its configured mode.",
|
||||
logging_only_scope_error,
|
||||
logging_only_scope_error.replace("\r", "").replace("\n", ""),
|
||||
)
|
||||
custom_guardrail_callback.logging_only_scope = None
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { chooseSelectOption, renderWithProviders } from "@/../tests/test-utils";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
|
@ -151,34 +151,6 @@ describe("AddGuardrailForm create payload characterization", () => {
|
|||
expect(screen.getByRole("option", { name: "Both (request and response)" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clears a selected directional scope when switching to an unsupported provider", async () => {
|
||||
vi.mocked(networking.getGuardrailUISettings).mockResolvedValue({
|
||||
...uiSettings,
|
||||
supported_modes: ["pre_call", "logging_only"],
|
||||
providers_without_directional_logging_only_scope: ["xecguard"],
|
||||
});
|
||||
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({
|
||||
...providerParams,
|
||||
xecguard: { ui_friendly_name: "XecGuard" },
|
||||
});
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderForm();
|
||||
|
||||
fireEvent.change(await screen.findByLabelText("Guardrail Name"), { target: { value: "switch-scope" } });
|
||||
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)");
|
||||
expect(screen.getByLabelText("Logging only scope")).toHaveTextContent("Output only (response)");
|
||||
await pickProvider(user, "XecGuard");
|
||||
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.mode).toContain("logging_only");
|
||||
expect(payload()?.litellm_params).not.toHaveProperty("logging_only_scope");
|
||||
});
|
||||
|
||||
it("hides logging-only scope and omits it from a pre-call payload", async () => {
|
||||
const user = userEvent.setup({ delay: null });
|
||||
renderForm();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
getSupportedModesForProvider,
|
||||
guardrail_provider_map,
|
||||
modeIncludesLoggingOnly,
|
||||
normalizeLoggingOnlyScopeChoice,
|
||||
populateGuardrailProviderMap,
|
||||
populateGuardrailProviders,
|
||||
shouldRenderContentFilterConfigSettings,
|
||||
|
|
@ -247,9 +248,8 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
|
||||
useEffect(() => {
|
||||
const scopeChoice = form.getValues("logging_only_scope_choice");
|
||||
if (!directionalScopeSupported && (scopeChoice === "input" || scopeChoice === "output")) {
|
||||
form.setValue("logging_only_scope_choice", "default");
|
||||
}
|
||||
const normalizedScopeChoice = normalizeLoggingOnlyScopeChoice(scopeChoice, directionalScopeSupported);
|
||||
if (normalizedScopeChoice !== scopeChoice) form.setValue("logging_only_scope_choice", normalizedScopeChoice);
|
||||
}, [directionalScopeSupported, form]);
|
||||
|
||||
// Fetch guardrail UI settings + provider params on mount / accessToken change
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ import {
|
|||
getLoggingOnlyScopeUpdate,
|
||||
getGuardrailLogoAndName,
|
||||
guardrail_provider_map,
|
||||
choiceToLoggingOnlyScope,
|
||||
loggingOnlyScopeToChoice,
|
||||
normalizeLoggingOnlyScopeChoice,
|
||||
skipSystemMessageToChoice,
|
||||
skipToolMessageToChoice,
|
||||
supportsDirectionalLoggingOnlyScope,
|
||||
|
|
@ -225,25 +225,31 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
// binds are seeded: an unbound key would otherwise be submitted as if the user had set it.
|
||||
useEffect(() => {
|
||||
if (!guardrailData) return;
|
||||
const litellmParams = guardrailData.litellm_params;
|
||||
form.setValue("guardrail_name", guardrailData.guardrail_name);
|
||||
form.setValue("default_on", litellmParams?.default_on);
|
||||
form.setValue("logging_only_scope_choice", loggingOnlyScopeToChoice(litellmParams?.logging_only_scope));
|
||||
const skipSystemMessageChoice = skipSystemMessageToChoice(litellmParams?.skip_system_message_in_guardrail);
|
||||
form.setValue("skip_system_message_choice", skipSystemMessageChoice);
|
||||
form.setValue("skip_tool_message_choice", skipToolMessageToChoice(litellmParams?.skip_tool_message_in_guardrail));
|
||||
const guardrailInfo = guardrailData.guardrail_info ? JSON.stringify(guardrailData.guardrail_info, null, 2) : "";
|
||||
form.setValue("guardrail_info", guardrailInfo);
|
||||
if (litellmParams?.optional_params) {
|
||||
form.setValue("optional_params", litellmParams.optional_params);
|
||||
form.setValue("default_on", guardrailData.litellm_params?.default_on);
|
||||
const storedLoggingOnlyScope = guardrailData.litellm_params?.logging_only_scope;
|
||||
form.setValue("logging_only_scope_choice", loggingOnlyScopeToChoice(storedLoggingOnlyScope));
|
||||
form.setValue(
|
||||
"skip_system_message_choice",
|
||||
skipSystemMessageToChoice(guardrailData.litellm_params?.skip_system_message_in_guardrail),
|
||||
);
|
||||
form.setValue(
|
||||
"skip_tool_message_choice",
|
||||
skipToolMessageToChoice(guardrailData.litellm_params?.skip_tool_message_in_guardrail),
|
||||
);
|
||||
form.setValue(
|
||||
"guardrail_info",
|
||||
guardrailData.guardrail_info ? JSON.stringify(guardrailData.guardrail_info, null, 2) : "",
|
||||
);
|
||||
if (guardrailData.litellm_params?.optional_params) {
|
||||
form.setValue("optional_params", guardrailData.litellm_params.optional_params);
|
||||
}
|
||||
}, [guardrailData, guardrailProviderSpecificParams, form]);
|
||||
|
||||
useEffect(() => {
|
||||
const scope = choiceToLoggingOnlyScope(form.getValues("logging_only_scope_choice"));
|
||||
if (!directionalScopeSupported && scope !== null && scope !== "both") {
|
||||
form.setValue("logging_only_scope_choice", "default");
|
||||
}
|
||||
const scopeChoice = form.getValues("logging_only_scope_choice");
|
||||
const normalizedScopeChoice = normalizeLoggingOnlyScopeChoice(scopeChoice, directionalScopeSupported);
|
||||
if (normalizedScopeChoice !== scopeChoice) form.setValue("logging_only_scope_choice", normalizedScopeChoice);
|
||||
}, [directionalScopeSupported, form, guardrailData]);
|
||||
|
||||
const resetToolPermissionEditor = useCallback(() => {
|
||||
|
|
@ -519,6 +525,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
|
|||
|
||||
const isConfigGuardrail = guardrailData.guardrail_definition_location === "config";
|
||||
|
||||
/* eslint-disable max-lines -- keep edit-form scope normalization with its owning view */
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
getLoggingOnlyScopeOptions,
|
||||
formatLoggingOnlyScope,
|
||||
modeIncludesLoggingOnly,
|
||||
normalizeLoggingOnlyScopeChoice,
|
||||
supportsDirectionalLoggingOnlyScope,
|
||||
} from "./guardrail_info_helpers";
|
||||
|
||||
|
|
@ -248,6 +249,15 @@ describe("guardrail_info_helpers", () => {
|
|||
});
|
||||
|
||||
describe("logging-only scope helpers", () => {
|
||||
it("normalizes directional choices only when the provider does not support them", () => {
|
||||
expect(normalizeLoggingOnlyScopeChoice("input", false)).toBe("default");
|
||||
expect(normalizeLoggingOnlyScopeChoice("output", false)).toBe("default");
|
||||
expect(normalizeLoggingOnlyScopeChoice("both", false)).toBe("both");
|
||||
expect(normalizeLoggingOnlyScopeChoice("default", false)).toBe("default");
|
||||
expect(normalizeLoggingOnlyScopeChoice("input", true)).toBe("input");
|
||||
expect(normalizeLoggingOnlyScopeChoice("output", true)).toBe("output");
|
||||
});
|
||||
|
||||
it("maps API scope values to choices and back", () => {
|
||||
expect(loggingOnlyScopeToChoice("input")).toBe("input");
|
||||
expect(loggingOnlyScopeToChoice("output")).toBe("output");
|
||||
|
|
|
|||
|
|
@ -118,6 +118,12 @@ export type LoggingOnlyScope = "input" | "output" | "both";
|
|||
export type LoggingOnlyScopeChoice = "default" | LoggingOnlyScope;
|
||||
export type LoggingOnlyScopeOption = { label: string; value: LoggingOnlyScopeChoice };
|
||||
|
||||
export const normalizeLoggingOnlyScopeChoice = (
|
||||
choice: LoggingOnlyScopeChoice,
|
||||
directionalScopeSupported: boolean,
|
||||
): LoggingOnlyScopeChoice =>
|
||||
directionalScopeSupported || choice === "default" || choice === "both" ? choice : "default";
|
||||
|
||||
const LOGGING_ONLY_SCOPE_OPTIONS: LoggingOnlyScopeOption[] = [
|
||||
{ label: "Default (request and response)", value: "default" },
|
||||
{ label: "Input only (request)", value: "input" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue