diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index 25a2ce6672c..8180c803b28 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -168,9 +168,11 @@ def _redact_standard_logging_object(model_call_details: dict): # Input-bearing keys that show up in request-body snapshots # (proxy_server_request.body and additional_args.complete_input_dict). # "messages"/"prompt"/"input" are the OpenAI-style payload entry points; +# "contents" is the Gemini/Vertex native user-turn field. # "system"/"system_prompt"/"instructions" are the provider-native top-level -# system-prompt fields (Anthropic `system`, Responses API `instructions`), -# which carry user content just as the messages do. +# system-prompt fields (Anthropic `system`, Responses API `instructions`); +# "system_instruction"/"systemInstruction" are the Gemini/Vertex equivalents. +# All carry user content just as the messages do. def _redact_request_body_dict(body: dict): """Scrub the input/system-prompt keys on a materialised request-body dict.""" if "messages" in body: @@ -179,7 +181,17 @@ def _redact_request_body_dict(body: dict): body["prompt"] = "" if "input" in body: body["input"] = "" - for key in ("system", "system_prompt", "instructions"): + if "contents" in body: + body["contents"] = [ + {"role": "user", "parts": [{"text": "redacted-by-litellm"}]} + ] + for key in ( + "system", + "system_prompt", + "instructions", + "system_instruction", + "systemInstruction", + ): if key in body: body[key] = "redacted-by-litellm" diff --git a/tests/test_litellm/litellm_core_utils/test_redact_messages.py b/tests/test_litellm/litellm_core_utils/test_redact_messages.py index 63bfb9fa446..aedccb56fce 100644 --- a/tests/test_litellm/litellm_core_utils/test_redact_messages.py +++ b/tests/test_litellm/litellm_core_utils/test_redact_messages.py @@ -592,3 +592,67 @@ class TestPerformRedaction: assert cid["system"] == "redacted-by-litellm" assert cid["system_prompt"] == "redacted-by-litellm" assert cid["instructions"] == "redacted-by-litellm" + + def test_redacts_gemini_native_fields_in_complete_input_dict(self): + """Gemini/Vertex wire-format requests carry the user turn in `contents` + and the system prompt in `system_instruction` / `systemInstruction`, + not the OpenAI-style `messages` / `system` keys — these provider-native + fields must be scrubbed from the wire-format request snapshot too.""" + details = { + "additional_args": { + "complete_input_dict": { + "model": "gemini-2.0-flash", + "contents": [ + { + "role": "user", + "parts": [{"text": "CANARY_INPUT_should_be_redacted"}], + } + ], + "system_instruction": { + "parts": [{"text": "CANARY_SYSTEM_should_be_redacted"}] + }, + "systemInstruction": { + "parts": [{"text": "CANARY_SYSTEM_should_be_redacted"}] + }, + } + } + } + + perform_redaction(details, None) + + cid = details["additional_args"]["complete_input_dict"] + assert cid["contents"] == [ + {"role": "user", "parts": [{"text": "redacted-by-litellm"}]} + ] + assert cid["system_instruction"] == "redacted-by-litellm" + assert cid["systemInstruction"] == "redacted-by-litellm" + + def test_redacts_gemini_native_fields_in_proxy_server_request_body(self): + """Same Gemini/Vertex native input fields can also land in the proxy + body snapshot — `contents` / `system_instruction` must be scrubbed.""" + details = { + "litellm_params": { + "proxy_server_request": { + "body": { + "model": "gemini-2.0-flash", + "contents": [ + { + "role": "user", + "parts": [{"text": "CANARY_INPUT_should_be_redacted"}], + } + ], + "system_instruction": { + "parts": [{"text": "CANARY_SYSTEM_should_be_redacted"}] + }, + } + } + }, + } + + perform_redaction(details, None) + + body = details["litellm_params"]["proxy_server_request"]["body"] + assert body["contents"] == [ + {"role": "user", "parts": [{"text": "redacted-by-litellm"}]} + ] + assert body["system_instruction"] == "redacted-by-litellm"