From b7d41d49bdb026d7582b3234e7237a7d98e67086 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 27 Feb 2026 04:49:53 +0000 Subject: [PATCH] fix(logging): release httpx.Response/Headers after callbacks complete The Logging object's model_call_details retains full httpx.Response objects (key: 'httpx_response') and httpx.Headers (key: 'response_headers') for every LLM request. These contain OrderedDict instances that accumulate when the Logging object isn't promptly garbage-collected (e.g. referenced by pending asyncio tasks). Added _cleanup_heavy_references() that runs at the end of all four logging paths (success_handler, async_success_handler, failure_handler, async_failure_handler) to explicitly pop heavyweight references from model_call_details. This is the primary fix for the OrderedDict leak observed growing from 18K to 155K objects. Co-authored-by: Ishaan Jaff --- litellm/litellm_core_utils/litellm_logging.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 4ad2d1002bc..2830bf6b4dd 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -2306,6 +2306,8 @@ class Logging(LiteLLMLoggingBaseClass): str(e) ), ) + finally: + self._cleanup_heavy_references() async def async_success_handler( # noqa: PLR0915 self, result=None, start_time=None, end_time=None, cache_hit=None, **kwargs @@ -2626,6 +2628,23 @@ class Logging(LiteLLMLoggingBaseClass): self._handle_callback_failure(callback=callback) pass + self._cleanup_heavy_references() + + def _cleanup_heavy_references(self) -> None: + """Release heavyweight objects from model_call_details after logging. + + httpx.Response objects and their Headers hold OrderedDicts that + accumulate if the Logging object isn't promptly garbage-collected + (e.g. when referenced by pending asyncio tasks). + """ + for key in ( + "httpx_response", + "response_headers", + "raw_request_typed_dict", + "complete_streaming_response", + ): + self.model_call_details.pop(key, None) + def _handle_callback_failure(self, callback: Any): """ Handle callback logging failures by incrementing Prometheus metrics. @@ -2921,6 +2940,8 @@ class Logging(LiteLLMLoggingBaseClass): str(e) ) ) + finally: + self._cleanup_heavy_references() async def async_failure_handler( self, exception, traceback_exception, start_time=None, end_time=None @@ -2986,6 +3007,8 @@ class Logging(LiteLLMLoggingBaseClass): # Track callback logging failures in Prometheus self._handle_callback_failure(callback=callback) + self._cleanup_heavy_references() + def _get_trace_id(self, service_name: Literal["langfuse"]) -> Optional[str]: """ For the given service (e.g. langfuse), return the trace_id actually logged.