mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Merge cc4d13c270 into e6c4580a31
This commit is contained in:
commit
4393ebf488
7 changed files with 163 additions and 77 deletions
|
|
@ -927,6 +927,12 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac
|
|||
if "messages" not in (excluded_fields or []) and standard_logging_object_copy.get("messages") is not None:
|
||||
standard_logging_object_copy["messages"] = [Message(content=redacted_str).model_dump()]
|
||||
|
||||
if (
|
||||
"system_prompt" not in (excluded_fields or [])
|
||||
and standard_logging_object_copy.get("system_prompt") is not None
|
||||
):
|
||||
standard_logging_object_copy["system_prompt"] = redacted_str
|
||||
|
||||
if "response" not in (excluded_fields or []) and standard_logging_object_copy.get("response") is not None:
|
||||
response: Final = standard_logging_object_copy["response"]
|
||||
# Check if this is a ResponsesAPIResponse (has "output" field)
|
||||
|
|
|
|||
|
|
@ -5074,29 +5074,17 @@ class StandardLoggingPayloadSetup:
|
|||
return start_time_float, end_time_float, completion_start_time_float
|
||||
|
||||
@staticmethod
|
||||
def append_system_prompt_messages(kwargs: dict | None = None, messages: Any | None = None):
|
||||
"""
|
||||
Append system prompt messages to the messages
|
||||
"""
|
||||
if kwargs is not None:
|
||||
if kwargs.get("system") is not None and isinstance(kwargs.get("system"), str):
|
||||
if messages is None:
|
||||
return [{"role": "system", "content": kwargs.get("system")}]
|
||||
elif isinstance(messages, list):
|
||||
if len(messages) == 0:
|
||||
return [{"role": "system", "content": kwargs.get("system")}]
|
||||
# check for duplicates
|
||||
if messages[0].get("role") == "system" and messages[0].get("content") == kwargs.get("system"):
|
||||
return messages
|
||||
messages = [{"role": "system", "content": kwargs.get("system")}] + messages
|
||||
elif isinstance(messages, str):
|
||||
messages = [
|
||||
{"role": "system", "content": kwargs.get("system")},
|
||||
{"role": "user", "content": messages},
|
||||
]
|
||||
return messages
|
||||
def get_system_prompt_from_kwargs(kwargs: dict | None = None) -> str | list | dict | None:
|
||||
if kwargs is None:
|
||||
return None
|
||||
|
||||
return messages
|
||||
for key in ("system_instructions", "instructions", "system"):
|
||||
value = kwargs.get(key)
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, (str, list, dict)):
|
||||
return value
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def merge_litellm_metadata(litellm_params: Mapping[str, object]) -> dict:
|
||||
|
|
@ -6046,11 +6034,8 @@ def get_standard_logging_object_payload(
|
|||
model_id=_model_id,
|
||||
requester_ip_address=clean_metadata.get("requester_ip_address", None),
|
||||
user_agent=clean_metadata.get("user_agent", None),
|
||||
messages=truncate_base64_in_messages(
|
||||
StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=kwargs.get("messages")
|
||||
)
|
||||
),
|
||||
messages=truncate_base64_in_messages(kwargs.get("messages")),
|
||||
system_prompt=StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(kwargs=kwargs),
|
||||
response=final_response_obj,
|
||||
model_parameters=ModelParamHelper.get_standard_logging_model_parameters(
|
||||
kwargs.get("optional_params", None) or {}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ def _redact_standard_logging_object(model_call_details: dict):
|
|||
if standard_logging_object.get("messages") is not None:
|
||||
standard_logging_object["messages"] = [{"role": "user", "content": redacted_str}]
|
||||
|
||||
if standard_logging_object.get("system_prompt") is not None:
|
||||
standard_logging_object["system_prompt"] = redacted_str
|
||||
|
||||
response: Final = standard_logging_object.get("response")
|
||||
if response is not None:
|
||||
if isinstance(response, dict) and "output" in response:
|
||||
|
|
|
|||
|
|
@ -3254,6 +3254,7 @@ class StandardLoggingPayload(TypedDict):
|
|||
requester_ip_address: str | None
|
||||
user_agent: str | None
|
||||
messages: str | list | dict | None
|
||||
system_prompt: str | list | dict | None
|
||||
response: str | list | dict | None
|
||||
error_str: str | None
|
||||
error_information: StandardLoggingPayloadErrorInformation | None
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ def create_sample_standard_logging_payload() -> Dict:
|
|||
"requester_ip_address": None,
|
||||
"user_agent": None,
|
||||
"messages": [{"role": "user", "content": "Hello, this is sensitive data!"}],
|
||||
"system_prompt": [{"type": "text", "text": "sensitive system prompt"}],
|
||||
"response": {
|
||||
"choices": [{"message": {"content": "This is a sensitive response!"}}]
|
||||
},
|
||||
|
|
@ -223,6 +224,7 @@ class TestStandardLoggingPayloadExcludedFields:
|
|||
assert (
|
||||
result["standard_logging_object"]["messages"][0]["content"] == redacted_str
|
||||
)
|
||||
assert result["standard_logging_object"]["system_prompt"] == redacted_str
|
||||
assert (
|
||||
result["standard_logging_object"]["response"]["choices"][0]["message"][
|
||||
"content"
|
||||
|
|
|
|||
|
|
@ -2465,63 +2465,103 @@ def test_get_usage_as_dict():
|
|||
assert result == {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
||||
|
||||
|
||||
def test_append_system_prompt_messages():
|
||||
"""
|
||||
Test append_system_prompt_messages prepends system message from kwargs to messages list.
|
||||
"""
|
||||
def test_get_system_prompt_from_kwargs():
|
||||
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
|
||||
|
||||
# Test case 1: system in kwargs with existing messages
|
||||
kwargs = {"system": "You are a helpful assistant"}
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=messages
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result[0] == {"role": "system", "content": "You are a helpful assistant"}
|
||||
assert result[1] == {"role": "user", "content": "Hello"}
|
||||
|
||||
# Test case 2: system in kwargs with None messages
|
||||
kwargs = {"system": "You are a helpful assistant"}
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=None
|
||||
)
|
||||
assert len(result) == 1
|
||||
assert result[0] == {"role": "system", "content": "You are a helpful assistant"}
|
||||
|
||||
# Test case 3: system in kwargs with empty messages list
|
||||
kwargs = {"system": "You are a helpful assistant"}
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=[]
|
||||
)
|
||||
assert len(result) == 1
|
||||
assert result[0] == {"role": "system", "content": "You are a helpful assistant"}
|
||||
|
||||
# Test case 4: duplicate system message should not be added
|
||||
kwargs = {"system": "You are a helpful assistant"}
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful assistant"},
|
||||
{"role": "user", "content": "Hello"},
|
||||
system_blocks = [
|
||||
{"type": "text", "text": "SHAPE-SECRET", "cache_control": {"type": "ephemeral"}},
|
||||
]
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=messages
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(
|
||||
kwargs={"system": system_blocks, "messages": [{"role": "user", "content": "hi"}]}
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result[0] == {"role": "system", "content": "You are a helpful assistant"}
|
||||
assert result == system_blocks
|
||||
|
||||
# Test case 5: no system in kwargs returns messages unchanged
|
||||
kwargs = {}
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=kwargs, messages=messages
|
||||
)
|
||||
assert result == messages
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(kwargs={"system": "Be helpful"})
|
||||
assert result == "Be helpful"
|
||||
|
||||
# Test case 6: None kwargs returns messages unchanged
|
||||
result = StandardLoggingPayloadSetup.append_system_prompt_messages(
|
||||
kwargs=None, messages=messages
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(kwargs={"instructions": "Follow policy"})
|
||||
assert result == "Follow policy"
|
||||
|
||||
gemini_system = [{"role": "system", "content": "Be concise."}]
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(
|
||||
kwargs={"system_instructions": gemini_system}
|
||||
)
|
||||
assert result == messages
|
||||
assert result == gemini_system
|
||||
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(
|
||||
kwargs={
|
||||
"system_instructions": "From Gemini",
|
||||
"instructions": "From Responses",
|
||||
"system": "From Anthropic",
|
||||
}
|
||||
)
|
||||
assert result == "From Gemini"
|
||||
|
||||
result = StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(
|
||||
kwargs={"system_instructions": [], "instructions": "From Responses"}
|
||||
)
|
||||
assert result == []
|
||||
|
||||
assert StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(kwargs={}) is None
|
||||
assert StandardLoggingPayloadSetup.get_system_prompt_from_kwargs(kwargs=None) is None
|
||||
|
||||
|
||||
def test_get_standard_logging_object_payload_keeps_system_prompt_separate_from_messages(logging_obj):
|
||||
import datetime
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import get_standard_logging_object_payload
|
||||
|
||||
user_messages = [{"role": "user", "content": "hello"}]
|
||||
mock_response = {
|
||||
"id": "msg_123",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "hi"}],
|
||||
"model": "claude-sonnet-4-5",
|
||||
"usage": {"input_tokens": 5, "output_tokens": 2},
|
||||
}
|
||||
now = datetime.datetime.now()
|
||||
|
||||
system_blocks = [
|
||||
{"type": "text", "text": "SHAPE-SECRET", "cache_control": {"type": "ephemeral"}},
|
||||
]
|
||||
list_kwargs = {
|
||||
"model": "anthropic/claude-sonnet-4-5",
|
||||
"system": system_blocks,
|
||||
"messages": user_messages,
|
||||
"litellm_params": {},
|
||||
}
|
||||
list_payload = get_standard_logging_object_payload(
|
||||
kwargs=list_kwargs,
|
||||
init_response_obj=mock_response,
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
logging_obj=logging_obj,
|
||||
status="success",
|
||||
)
|
||||
|
||||
assert list_payload is not None
|
||||
assert list_payload["system_prompt"] == system_blocks
|
||||
assert list_payload["messages"] == user_messages
|
||||
|
||||
string_kwargs = {
|
||||
"model": "anthropic/claude-sonnet-4-5",
|
||||
"system": "Be helpful",
|
||||
"messages": user_messages,
|
||||
"litellm_params": {},
|
||||
}
|
||||
string_payload = get_standard_logging_object_payload(
|
||||
kwargs=string_kwargs,
|
||||
init_response_obj=mock_response,
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
logging_obj=logging_obj,
|
||||
status="success",
|
||||
)
|
||||
|
||||
assert string_payload is not None
|
||||
assert string_payload["system_prompt"] == "Be helpful"
|
||||
assert string_payload["messages"] == user_messages
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from litellm.integrations.custom_logger import CustomLogger
|
|||
from litellm.litellm_core_utils.redact_messages import (
|
||||
_redact_responses_api_output,
|
||||
perform_redaction,
|
||||
redact_message_input_output_from_logging,
|
||||
redact_streaming_responses_for_custom_logger,
|
||||
should_redact_message_logging,
|
||||
)
|
||||
|
|
@ -182,6 +183,9 @@ class TestPerformRedaction:
|
|||
"input": "sensitive input",
|
||||
"standard_logging_object": {
|
||||
"messages": [{"role": "user", "content": "sensitive input"}],
|
||||
"system_prompt": [
|
||||
{"type": "text", "text": "SHAPE-SECRET", "cache_control": {"type": "ephemeral"}},
|
||||
],
|
||||
"response": {
|
||||
"output": [
|
||||
{"text": "top-level text"},
|
||||
|
|
@ -208,6 +212,7 @@ class TestPerformRedaction:
|
|||
]
|
||||
assert details["prompt"] == ""
|
||||
assert details["input"] == ""
|
||||
assert details["standard_logging_object"]["system_prompt"] == "redacted-by-litellm"
|
||||
|
||||
logged_response = details["standard_logging_object"]["response"]
|
||||
assert logged_response["usage"] == {"total_tokens": 1}
|
||||
|
|
@ -858,3 +863,47 @@ class TestRedactStreamingResponsesForCustomLogger:
|
|||
|
||||
assert result_details is model_call_details
|
||||
assert response_obj.choices[0].message.content == "secret content"
|
||||
|
||||
|
||||
class TestSystemPromptRedaction:
|
||||
REDACTED = "redacted-by-litellm"
|
||||
SYSTEM_PROMPT = [{"type": "text", "text": "SHAPE-SECRET"}]
|
||||
|
||||
def _details_with_system_prompt(self, **extra):
|
||||
details = {
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"standard_logging_object": {
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"system_prompt": self.SYSTEM_PROMPT,
|
||||
"response": {"choices": [{"message": {"content": "secret"}}]},
|
||||
},
|
||||
"litellm_params": {"metadata": {}},
|
||||
}
|
||||
details.update(extra)
|
||||
return details
|
||||
|
||||
def test_global_turn_off_message_logging_redacts_system_prompt(self):
|
||||
litellm.turn_off_message_logging = True
|
||||
details = self._details_with_system_prompt()
|
||||
|
||||
redact_message_input_output_from_logging(details, result=None)
|
||||
|
||||
assert details["standard_logging_object"]["system_prompt"] == self.REDACTED
|
||||
|
||||
def test_request_level_dynamic_param_redacts_system_prompt(self):
|
||||
details = self._details_with_system_prompt(
|
||||
standard_callback_dynamic_params={"turn_off_message_logging": True}
|
||||
)
|
||||
|
||||
redact_message_input_output_from_logging(details, result=None)
|
||||
|
||||
assert details["standard_logging_object"]["system_prompt"] == self.REDACTED
|
||||
|
||||
def test_per_callback_turn_off_message_logging_redacts_system_prompt(self):
|
||||
details = self._details_with_system_prompt()
|
||||
logger = CustomLogger(turn_off_message_logging=True)
|
||||
|
||||
result = logger.redact_standard_logging_payload_from_model_call_details(details)
|
||||
|
||||
assert result["standard_logging_object"]["system_prompt"] == self.REDACTED
|
||||
assert details["standard_logging_object"]["system_prompt"] == self.SYSTEM_PROMPT
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue