From b0145da548113a190a3774c1bc1399f98a74e287 Mon Sep 17 00:00:00 2001 From: yucheng Date: Mon, 2 Nov 2026 13:57:27 -0800 Subject: [PATCH] fix(guardrails): send Lakera Gemini function results without the logging sanitizer The logging sanitizer rewrites keys and truncates deep values, so a result could reach the provider with text Lakera never saw. Results are now encoded as plain JSON. --- litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py | 4 ++-- .../guardrails/guardrail_hooks/test_lakera_ai_v2.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py index c21fe7a23fb..4183f218dd6 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py +++ b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai_v2.py @@ -1,4 +1,5 @@ import copy +import json import os from collections.abc import Mapping, Sequence from datetime import datetime @@ -17,7 +18,6 @@ from litellm.integrations.custom_guardrail import ( CustomGuardrail, ) from litellm.litellm_core_utils.core_helpers import as_str_mapping -from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.llms.base_llm.guardrail_translation.utils import ( effective_skip_system_message_for_guardrail, effective_skip_tool_message_for_guardrail, @@ -253,7 +253,7 @@ def _gemini_tool_results(content: object) -> str: part_fields: Final = (as_str_mapping(part) or {} for part in parts) responses: Final = (fields.get("functionResponse") or fields.get("function_response") for fields in part_fields) results: Final = ((as_str_mapping(response) or {}).get("response") for response in responses) - return "\n".join(safe_dumps(result) for result in results if result) + return "\n".join(json.dumps(result, default=str, ensure_ascii=False) for result in results if result) def _has_gemini_contents(data: object, call_type: str | None) -> bool: diff --git a/tests/unit/proxy/guardrails/guardrail_hooks/test_lakera_ai_v2.py b/tests/unit/proxy/guardrails/guardrail_hooks/test_lakera_ai_v2.py index 435bc18d4d1..25ce614fe8c 100644 --- a/tests/unit/proxy/guardrails/guardrail_hooks/test_lakera_ai_v2.py +++ b/tests/unit/proxy/guardrails/guardrail_hooks/test_lakera_ai_v2.py @@ -1782,6 +1782,15 @@ class TestGeminiContents: == GEMINI_INSPECTION_MESSAGES ) + def test_gemini_function_response_reaches_inspection_exactly_as_the_provider_gets_it(self): + result: Final = {"output": "4111 1111 1111 1111", "\x00output": "clean", "name": "José"} + data: Final = {"contents": [{"parts": [{"functionResponse": {"name": "lookup", "response": result}}]}]} + + messages: Final = _build_lakera_inspection_messages(data, call_type="pass_through_endpoint") + + assert json.loads(messages[0]["content"]) == result + assert "José" in messages[0]["content"] + @pytest.mark.parametrize("field", ["functionResponse", "function_response"]) def test_gemini_function_response_is_read_as_a_tool_turn(self, field: str): data: Final = {