Fix: Respect LiteLLM-Disable-Message-Redaction header for Responses API (#15966)

* fix overide for logging unredacted messages

* Use _get_metadata_variable_name_from_kwargs

* fix test related to redaction
This commit is contained in:
Sameer Kankute 2025-10-28 02:16:21 +05:30 committed by GitHub
parent 0bb53f5048
commit 59df75276c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 117 additions and 7 deletions

View File

@ -138,6 +138,22 @@ def add_missing_spend_metadata_to_litellm_metadata(
return litellm_metadata
def get_metadata_variable_name_from_kwargs(
kwargs: dict,
) -> str:
"""
Helper to return what the "metadata" field should be called in the request data
- New endpoints return `litellm_metadata`
- Old endpoints return `metadata`
Context:
- LiteLLM used `metadata` as an internal field for storing metadata
- OpenAI then started using this field for their metadata
- LiteLLM is now moving to using `litellm_metadata` for our metadata
"""
return "litellm_metadata" if "litellm_metadata" in kwargs else "metadata"
def get_litellm_metadata_from_kwargs(kwargs: dict):
"""
Helper to get litellm metadata from all litellm request kwargs

View File

@ -14,6 +14,9 @@ import litellm
from litellm.integrations.custom_logger import CustomLogger
from litellm.secret_managers.main import str_to_bool
from litellm.types.utils import StandardCallbackDynamicParams
from litellm.litellm_core_utils.core_helpers import (
get_metadata_variable_name_from_kwargs,
)
import asyncio
if TYPE_CHECKING:
@ -107,11 +110,13 @@ def should_redact_message_logging(model_call_details: dict) -> bool:
"""
Determine if message logging should be redacted.
"""
_request_headers = (
model_call_details.get("litellm_params", {}).get("metadata", {}) or {}
)
request_headers = _request_headers.get("headers", {})
litellm_params = model_call_details.get("litellm_params", {})
metadata_field = get_metadata_variable_name_from_kwargs(litellm_params)
metadata = litellm_params.get(metadata_field, {})
# Get headers from the metadata
request_headers = metadata.get("headers", {}) if isinstance(metadata, dict) else {}
possible_request_headers = [
"litellm-enable-message-redaction", # old header. maintain backwards compatibility

View File

@ -54,7 +54,10 @@ from litellm.caching.caching import (
from litellm.constants import DEFAULT_MAX_LRU_CACHE_SIZE
from litellm.integrations.custom_logger import CustomLogger
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import _get_parent_otel_span_from_kwargs
from litellm.litellm_core_utils.core_helpers import (
_get_parent_otel_span_from_kwargs,
get_metadata_variable_name_from_kwargs,
)
from litellm.litellm_core_utils.coroutine_checker import coroutine_checker
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
from litellm.litellm_core_utils.dd_tracing import tracer
@ -4756,7 +4759,7 @@ class Router:
- OpenAI then started using this field for their metadata
- LiteLLM is now moving to using `litellm_metadata` for our metadata
"""
return "litellm_metadata" if "litellm_metadata" in kwargs else "metadata"
return get_metadata_variable_name_from_kwargs(kwargs)
def log_retry(self, kwargs: dict, e: Exception) -> dict:
"""

View File

@ -279,3 +279,89 @@ async def test_redaction_with_streaming_response():
"logged standard logging payload for streaming with coroutine handling",
json.dumps(standard_logging_payload, indent=2),
)
@pytest.mark.asyncio
async def test_disable_redaction_header_responses_api():
"""
Test that LiteLLM-Disable-Message-Redaction header works for Responses API.
This test verifies the fix for the issue where the header wasn't respected
because Responses API uses 'litellm_metadata' instead of 'metadata'.
"""
litellm.turn_off_message_logging = True
test_custom_logger = TestCustomLogger()
litellm.callbacks = [test_custom_logger]
# Mock a ResponsesAPIResponse-style response
mock_response = {
"output": [{"text": "This is a test response"}],
"model": "gpt-3.5-turbo",
"usage": {"input_tokens": 5, "output_tokens": 5, "total_tokens": 10}
}
# Pass the header via litellm_metadata (as the proxy does for Responses API)
response = await litellm.aresponses(
model="gpt-3.5-turbo",
input="hi",
mock_response=mock_response,
litellm_metadata={
"headers": {
"litellm-disable-message-redaction": "true"
}
}
)
await asyncio.sleep(1)
standard_logging_payload = test_custom_logger.logged_standard_logging_payload
assert standard_logging_payload is not None
# Verify that messages are NOT redacted because the header was set
print(
"logged standard logging payload for ResponsesAPI with disable header",
json.dumps(standard_logging_payload, indent=2, default=str),
)
# The content should NOT be redacted
assert standard_logging_payload["response"] != {"text": "redacted-by-litellm"}
assert standard_logging_payload["messages"][0]["content"] == "hi"
@pytest.mark.asyncio
async def test_redaction_with_metadata_completion_api():
"""
Test redaction behavior with metadata field for Completion API.
This test verifies that get_metadata_variable_name_from_kwargs properly
selects the appropriate metadata field for header detection.
"""
litellm.turn_off_message_logging = True
test_custom_logger = TestCustomLogger()
litellm.callbacks = [test_custom_logger]
# When metadata is passed, the system uses get_metadata_variable_name_from_kwargs
# to determine which field to check
response = await litellm.acompletion(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "hi"}],
mock_response="hello",
metadata={
"headers": {
"litellm-disable-message-redaction": "true"
}
}
)
await asyncio.sleep(1)
standard_logging_payload = test_custom_logger.logged_standard_logging_payload
assert standard_logging_payload is not None
print(
"logged standard logging payload for Completion API with metadata",
json.dumps(standard_logging_payload, indent=2),
)
# Verify the helper function works correctly - with get_metadata_variable_name_from_kwargs,
# the system checks the appropriate field for headers
assert standard_logging_payload["response"] == {"text": "redacted-by-litellm"}
assert standard_logging_payload["messages"][0]["content"] == "redacted-by-litellm"