Merge branch 'litellm_lakera_v2_gemini_contents' into litellm_passthrough_applied_guardrails_header

This commit is contained in:
yucheng 2026-10-31 10:10:00 -07:00
commit 3f4972e905
3 changed files with 21 additions and 6 deletions

View file

@ -55,6 +55,7 @@ from litellm.constants import (
)
from litellm.integrations.custom_guardrail import (
CustomGuardrail,
guardrail_entry_names,
guardrail_request_data_with_streaming,
without_server_streaming_classification,
)
@ -966,14 +967,14 @@ async def _inherit_auth_guardrails(
user_api_key_dict=user_api_key_dict,
)
merged: Final = (as_str_mapping(parsed_body["metadata"]) or {}).get("guardrails")
if not isinstance(merged, list) or not merged or not _named_guardrail_runs_post_call(parsed_body):
return None
return _GUARDRAIL_ENTRIES.validate_python(merged)
entries: Final = _GUARDRAIL_ENTRIES.validate_python(merged) if isinstance(merged, list) else []
return entries if entries and _named_guardrail_runs_post_call(parsed_body, entries) else None
def _named_guardrail_runs_post_call(data: Mapping[str, object]) -> bool:
def _named_guardrail_runs_post_call(data: Mapping[str, object], entries: Sequence[object]) -> bool:
names: Final = frozenset(chain.from_iterable(map(guardrail_entry_names, entries)))
return any(
guardrail.default_on is not True
guardrail.guardrail_name in names
and guardrail.should_run_guardrail( # pyright: ignore[reportUnknownMemberType] # takes the untyped request dict
data=data, event_type=GuardrailEventHooks.post_call
)

View file

@ -1746,7 +1746,7 @@ async def _run_gemini_pre_call(
guardrail: LakeraAIGuardrail, lakera_response: Mapping[str, object], data: dict[str, object]
) -> Sequence[object]:
"""Run the pre-call hook against a Lakera double and return the messages each guard request carried."""
sent: Final = list[object]()
sent: Final = list[object]() # mutable-ok: the Lakera double records each guard request it receives
def respond(request: httpx.Request) -> httpx.Response:
sent.append(json.loads(request.content)["messages"])

View file

@ -11904,6 +11904,20 @@ class TestInheritAuthGuardrails:
post_call.assert_not_awaited()
assert response.body == b'{\n "id":"chatcmpl-1"\n}'
@pytest.mark.asyncio
async def test_a_named_default_on_post_call_guardrail_still_checks_the_response(self):
auth: Final = UserAPIKeyAuth(api_key="sk-test", metadata={"guardrails": ["global-post"]})
_, post_call = await self._run(
self._chat_body(),
inherit=True,
user_api_key_dict=auth,
guardrails=(self._guardrail("global-post", GuardrailEventHooks.post_call, default_on=True),),
)
post_call.assert_awaited_once()
assert post_call.call_args.kwargs["data"]["metadata"]["guardrails"] == ["global-post"]
@pytest.mark.asyncio
async def test_request_body_guardrails_are_added_to_the_key_guardrails(self):
auth: Final = UserAPIKeyAuth(api_key="sk-test", metadata={"guardrails": ["key-guard"]})