From 6fd74527bab837bcd7f0d85c70f3a50b89cc44fc Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Wed, 26 Aug 2026 00:18:49 -0700 Subject: [PATCH] fix(logging): use an empty list for read-call messages A tuple matches no branch in the loggers that walk this value, so lunary's parse_messages falls through to clean_message and raises AttributeError on the success hook. An empty list reads as no messages everywhere: it satisfies the isinstance(list) checks in newrelic, mlflow and datadog, iterates zero times in traceloop and helicone, and is what StandardLoggingPayload.messages is typed to hold. None would be type-legal too but is not iterable, so it trades one crash for another in mlflow and traceloop. --- litellm/utils.py | 2 +- .../litellm_core_utils/test_litellm_logging.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 3770b8caa88..305b6411386 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1073,7 +1073,7 @@ def function_setup( verbose_logger.debug("Error extracting messages from Google contents: %s", e) messages = "default-message-value" elif call_type in NON_INFERENCE_CALL_TYPES: - messages = () + messages = [] # mutable-ok: loggers require a list here and Logging copies it else: messages = "default-message-value" stream = False diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 00204d7bf7b..542617cc159 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -4953,15 +4953,25 @@ class TestNonInferenceCallTypesAreNotBilled: assert payload is not None assert payload["total_tokens"] == 6000 - def test_read_calls_do_not_log_a_placeholder_chat_message(self): + def _read_call_messages(self): logging_obj, _ = litellm.utils.function_setup( original_function="aget_responses", rules_obj=litellm.utils.Rules(), start_time=time.time(), **{"litellm_call_id": "lit5602-setup", "response_id": "resp_lit5602"}, ) + return logging_obj.model_call_details["messages"] - assert logging_obj.model_call_details["messages"] == () + def test_read_calls_do_not_log_a_placeholder_chat_message(self): + assert self._read_call_messages() == [] + + def test_read_call_messages_survive_a_logger_that_walks_them(self): + """Loggers reach into this value expecting a chat history and branch on it being a list. + An empty list reads as no messages; a tuple matches no branch and crashes the success hook, + and None is not iterable where other loggers walk it.""" + from litellm.integrations.lunary import parse_messages + + assert parse_messages(self._read_call_messages()) == [] def _build_success_payload(logging_obj, kwargs):