mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-03 07:23:33 +00:00
fix(guardrails): keep reading Gemini contents when a messages or input field sits next to them
This commit is contained in:
parent
91507ecc76
commit
ca0213ae52
2 changed files with 15 additions and 19 deletions
|
|
@ -237,11 +237,8 @@ def _gemini_text(content: object) -> str:
|
|||
|
||||
def _has_gemini_contents(data: object) -> bool:
|
||||
"""True if ``data`` is a Gemini ``generateContent`` body, which carries its prompt in
|
||||
``contents`` and ``systemInstruction`` instead of ``messages``. A body that also carries
|
||||
``messages`` or ``input`` is a chat or Responses body and is read as one."""
|
||||
``contents`` and ``systemInstruction`` instead of ``messages``."""
|
||||
fields: Final = as_str_mapping(data) or {}
|
||||
if "messages" in fields or "input" in fields:
|
||||
return False
|
||||
return isinstance(fields.get("contents"), list | dict) or isinstance(
|
||||
fields.get("systemInstruction") or fields.get("system_instruction"), dict
|
||||
)
|
||||
|
|
@ -251,8 +248,6 @@ def _gemini_inspection_messages(data: Mapping[str, object]) -> Sequence[Mapping[
|
|||
"""Chat-style messages for a Gemini ``generateContent`` body: ``systemInstruction`` first as a
|
||||
system message, then each ``contents`` turn with its text parts joined and ``model`` read as
|
||||
``assistant``. Turns with no text are dropped."""
|
||||
if not _has_gemini_contents(data):
|
||||
return []
|
||||
system_text: Final = _gemini_text(data.get("systemInstruction") or data.get("system_instruction"))
|
||||
turns: Final = (
|
||||
{"role": _GEMINI_ROLES.get(str(content.get("role") or "user"), "user"), "content": _gemini_text(content)}
|
||||
|
|
|
|||
|
|
@ -1787,21 +1787,22 @@ class TestGeminiContents:
|
|||
]
|
||||
|
||||
@pytest.mark.parametrize("chat_field", ["messages", "input"])
|
||||
def test_a_chat_body_with_a_stray_contents_field_is_read_as_chat(self, chat_field: str):
|
||||
data: Final = {
|
||||
chat_field: [{"role": "user", "content": "hello"}],
|
||||
"contents": [{"parts": [{"text": "stray"}]}],
|
||||
"system_instruction": {"parts": [{"text": "stray"}]},
|
||||
}
|
||||
assert _build_lakera_inspection_messages(data) == [{"role": "user", "content": "hello"}]
|
||||
def test_a_chat_field_next_to_contents_does_not_hide_the_contents(self, chat_field: str):
|
||||
data: Final = {chat_field: [], "contents": [{"parts": [{"text": "card"}]}]}
|
||||
assert _build_lakera_inspection_messages(data) == [{"role": "user", "content": "card"}]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_chat_body_with_a_stray_contents_field_is_still_masked(self):
|
||||
data: Final = {"messages": [{"role": "user", "content": "alice@example.com"}], "contents": []}
|
||||
await _run_gemini_pre_call(
|
||||
LakeraAIGuardrail(api_key="test_key", on_flagged="mask"), PII_ONLY_LAKERA_RESPONSE, data
|
||||
)
|
||||
assert data["messages"] == [{"role": "user", "content": "[MASKED EMAIL]@example.com"}]
|
||||
async def test_a_body_with_messages_and_contents_is_blocked_rather_than_masked(self):
|
||||
data: Final = {
|
||||
"messages": [{"role": "user", "content": "alice@example.com"}],
|
||||
"contents": [{"parts": [{"text": "hello"}]}],
|
||||
}
|
||||
with pytest.raises(HTTPException) as blocked:
|
||||
await _run_gemini_pre_call(
|
||||
LakeraAIGuardrail(api_key="test_key", on_flagged="mask"), PII_ONLY_LAKERA_RESPONSE, data
|
||||
)
|
||||
assert blocked.value.status_code == 400
|
||||
assert data["messages"] == [{"role": "user", "content": "alice@example.com"}]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clean_body_reaches_lakera_and_is_forwarded_unchanged(self):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue