mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
Revert "fix(guardrails): keep legacy not_run neutral and stop labelling image-only input as skipped"
This reverts commit f78dd921c9.
This commit is contained in:
parent
f78dd921c9
commit
bd9a87ea76
10 changed files with 26 additions and 61 deletions
|
|
@ -210,7 +210,7 @@ class OpenAIChatCompletionsHandler(BaseTranslation):
|
|||
task_mappings=tool_call_task_mappings,
|
||||
)
|
||||
|
||||
elif not images_to_check and not guardrail_to_apply.records_own_guardrail_information:
|
||||
elif not guardrail_to_apply.records_own_guardrail_information:
|
||||
guardrail_to_apply.add_standard_logging_guardrail_information_to_request_data(
|
||||
guardrail_json_response="no scannable content after message scoping",
|
||||
request_data=data,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from litellm.types.proxy.compliance_endpoints import (
|
|||
ComplianceCheckRequest,
|
||||
ComplianceCheckResult,
|
||||
)
|
||||
from litellm.types.utils import UNEVALUATED_GUARDRAIL_STATUSES
|
||||
|
||||
|
||||
class ComplianceChecker:
|
||||
|
|
@ -27,11 +26,7 @@ class ComplianceChecker:
|
|||
|
||||
def __init__(self, data: ComplianceCheckRequest):
|
||||
self.data = data
|
||||
self.guardrails = [
|
||||
g
|
||||
for g in (data.guardrail_information or [])
|
||||
if g.get("guardrail_status") not in UNEVALUATED_GUARDRAIL_STATUSES
|
||||
]
|
||||
self.guardrails = [g for g in (data.guardrail_information or []) if g.get("guardrail_status") != "skipped"]
|
||||
|
||||
def _get_guardrails_by_mode(self, mode: str) -> list[dict]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ from litellm.repositories.table_repositories import (
|
|||
DailyGuardrailUsageUnitsRepository,
|
||||
SpendLogGuardrailIndexRepository,
|
||||
)
|
||||
from litellm.types.utils import UNEVALUATED_GUARDRAIL_STATUSES
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from prisma import types as prisma_types
|
||||
|
|
@ -198,7 +197,7 @@ def guardrail_status_to_action(status: str | None) -> str:
|
|||
if not status:
|
||||
return "passed"
|
||||
s: Final = (status or "").lower()
|
||||
if s in UNEVALUATED_GUARDRAIL_STATUSES:
|
||||
if s == "skipped":
|
||||
return "skipped"
|
||||
if "intervened" in s or "block" in s:
|
||||
return "blocked"
|
||||
|
|
|
|||
|
|
@ -3123,8 +3123,6 @@ GuardrailStatus = Literal[
|
|||
"success", "guardrail_flagged", "guardrail_intervened", "guardrail_failed_to_respond", "not_run", "skipped"
|
||||
]
|
||||
|
||||
UNEVALUATED_GUARDRAIL_STATUSES: Final[frozenset[GuardrailStatus]] = frozenset({"not_run", "skipped"})
|
||||
|
||||
# Fields on a guardrail record whose values can quote the caller's prompt: the payload sent to the
|
||||
# guardrail, the provider response that echoes it back, and the two first-party hooks that inline
|
||||
# prompt substrings (``block_code_execution`` and ``litellm_content_filter``). Every other field
|
||||
|
|
|
|||
|
|
@ -1942,27 +1942,6 @@ class TestNoScannableContentRecordsSkipped:
|
|||
assert guardrail.last_inputs is not None
|
||||
assert all(e.get("guardrail_status") != "skipped" for e in self._recorded_entries(data))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_image_only_content_is_not_reported_as_skipped(self):
|
||||
"""Images are only scanned alongside text, so an image-only request is a
|
||||
pre-existing scan gap, not a message-scoping skip, and must not be labelled one"""
|
||||
handler = OpenAIChatCompletionsHandler()
|
||||
guardrail = MockGuardrail(guardrail_name="image-guardrail")
|
||||
guardrail.skip_system_message_in_guardrail = True
|
||||
data = {
|
||||
"messages": [
|
||||
{"role": "system", "content": "SYSTEM-PROMPT"},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "image_url", "image_url": {"url": "https://example.com/cat.png"}}],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
await handler.process_input_messages(data=data, guardrail_to_apply=guardrail)
|
||||
|
||||
assert self._recorded_entries(data) == []
|
||||
|
||||
|
||||
class TestBuildBlockSseChunks:
|
||||
"""build_block_sse_chunks turns a streaming ModifyResponseException into 200 SSE chunks"""
|
||||
|
|
|
|||
|
|
@ -350,17 +350,15 @@ async def test_zero_and_non_int_usage_counters_are_skipped():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("status", ["skipped", "not_run"])
|
||||
async def test_skipped_entries_are_indexed_but_not_counted_as_evaluations(status: str):
|
||||
async def test_skipped_entries_are_indexed_but_not_counted_as_evaluations():
|
||||
"""
|
||||
LIT-6314 records a skipped entry when message scoping leaves a guardrail
|
||||
nothing to scan (older spend logs spell it not_run). The guardrail never
|
||||
evaluated the request, so counting it as a passed evaluation would inflate
|
||||
daily pass rates; it still gets an index row so per-request drill-down
|
||||
finds the spend log.
|
||||
nothing to scan. The guardrail never evaluated the request, so counting it
|
||||
as a passed evaluation would inflate daily pass rates; it still gets an
|
||||
index row so per-request drill-down finds the spend log.
|
||||
"""
|
||||
prisma = _prisma()
|
||||
logs = [_payload("r1", guardrail_status=status), _payload("r2")]
|
||||
logs = [_payload("r1", guardrail_status="skipped"), _payload("r2")]
|
||||
|
||||
await process_spend_logs_guardrail_usage(prisma, logs)
|
||||
|
||||
|
|
|
|||
|
|
@ -594,15 +594,14 @@ class TestModeMatching:
|
|||
class TestSkippedGuardrails:
|
||||
"""LIT-6314 logs a skipped entry for a guardrail that message scoping left nothing to scan."""
|
||||
|
||||
@pytest.mark.parametrize("status", ["skipped", "not_run"])
|
||||
def test_skipped_alone_never_evidences_compliance(self, status: str):
|
||||
def test_skipped_alone_never_evidences_compliance(self):
|
||||
data = ComplianceCheckRequest(
|
||||
request_id="req-601",
|
||||
user_id="user-1",
|
||||
model="gpt-4",
|
||||
timestamp="2026-02-17T00:00:00Z",
|
||||
guardrail_information=[
|
||||
{"guardrail_name": "pii_detection", "guardrail_status": status, "guardrail_mode": "pre_call"},
|
||||
{"guardrail_name": "pii_detection", "guardrail_status": "skipped", "guardrail_mode": "pre_call"},
|
||||
],
|
||||
)
|
||||
results = {c.check_name: c.passed for c in ComplianceChecker(data).check_eu_ai_act()}
|
||||
|
|
|
|||
|
|
@ -68,25 +68,22 @@ describe("GuardrailViewer", () => {
|
|||
expect(screen.queryByText("FAILED")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(["skipped", "not_run"])(
|
||||
"renders %s as SKIPPED (muted) and keeps it out of the evaluated and passed counts",
|
||||
async (guardrail_status) => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation({ ...skippedPreCall, guardrail_status });
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
it("renders skipped as SKIPPED (muted) and keeps it out of the evaluated and passed counts", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation(skippedPreCall);
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
expect(screen.getByText(/0 guardrails evaluated/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/0 Passed/)).toHaveClass("text-muted-foreground");
|
||||
expect(screen.getByText(/1 Skipped/)).toBeInTheDocument();
|
||||
const badge = screen.getByText("SKIPPED");
|
||||
expect(badge).toHaveClass("text-muted-foreground");
|
||||
expect(screen.queryByText("FAILED")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/^T\+/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/0 guardrails evaluated/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/0 Passed/)).toHaveClass("text-muted-foreground");
|
||||
expect(screen.getByText(/1 Skipped/)).toBeInTheDocument();
|
||||
const badge = screen.getByText("SKIPPED");
|
||||
expect(badge).toHaveClass("text-muted-foreground");
|
||||
expect(screen.queryByText("FAILED")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/^T\+/)).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByText("no scannable content after message scoping")).toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByText("no scannable content after message scoping")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("anchors the lifecycle timeline on timed entries when an untimed skipped entry sorts first", () => {
|
||||
const skipped = makeGuardrailInformation({ ...skippedPreCall, guardrail_name: "skipped-rail" });
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ const getEntryOutcome = (entry: GuardrailInformation): EntryOutcome => {
|
|||
const status = (entry.guardrail_status ?? "").toLowerCase();
|
||||
if (status === "success") return "passed";
|
||||
if (status === "guardrail_flagged") return "flagged";
|
||||
if (status === "skipped" || status === "not_run") return "skipped";
|
||||
if (status === "skipped") return "skipped";
|
||||
return "failed";
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -705,7 +705,7 @@ const GUARDRAIL_JUMP_LINK_STYLE = {
|
|||
|
||||
const isPassedStatus = (status: unknown) => status === "pass" || status === "passed" || status === "success";
|
||||
const isFlaggedStatus = (status: unknown) => status === "flagged" || status === "guardrail_flagged";
|
||||
const isSkippedStatus = (status: unknown) => status === "skipped" || status === "not_run";
|
||||
const isSkippedStatus = (status: unknown) => status === "skipped";
|
||||
|
||||
const guardrailJumpLinkOutcome = (evaluated: unknown[]): keyof typeof GUARDRAIL_JUMP_LINK_STYLE => {
|
||||
if (evaluated.length === 0) return "skipped";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue