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:
parent
0bb53f5048
commit
59df75276c
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
"""
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user