From eeebcdcdbbc479e58f241c72b6b1720b51b710f2 Mon Sep 17 00:00:00 2001 From: narmstrong-berri Date: Fri, 6 Nov 2026 05:24:37 +0000 Subject: [PATCH] fix(proxy): release parked native success logs for every call type The proxy defers non-stream success logging past post-call guardrails, and the native host parks its log on _native_pending_logging until the proxy releases it. The release only ran for OCR, so a unary /v1/messages call served by Rust logged no success event, no SpendLogs row and no key, team, user or end-user spend --- litellm/proxy/common_request_processing.py | 9 ++++----- .../guardrails/test_deferred_guardrail_logging.py | 14 ++++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index e812b03385d..05bb737ff9c 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -4168,11 +4168,10 @@ class ProxyBaseLLMRequestProcessing: Extracted as a static method so tests can exercise the production gating logic directly rather than reimplementing the finally block. """ - if getattr(logging_obj, "call_type", None) in ("ocr", "aocr"): - pending: Final = getattr(logging_obj, "_native_pending_logging", None) - if pending is not None: - logging_obj._native_pending_logging = None # rebind-ok: consume the native OCR release signal once - pending.release(not exception_raised) + pending: Final = getattr(logging_obj, "_native_pending_logging", None) + if pending is not None: + logging_obj._native_pending_logging = None # rebind-ok: consume the native release signal once + pending.release(not exception_raised) _enqueue_fn: Final = getattr(logging_obj, "enqueue_deferred_logging", None) if _enqueue_fn is None: return diff --git a/tests/unit/proxy/guardrails/test_deferred_guardrail_logging.py b/tests/unit/proxy/guardrails/test_deferred_guardrail_logging.py index e9e0a585949..2c66f8922bf 100644 --- a/tests/unit/proxy/guardrails/test_deferred_guardrail_logging.py +++ b/tests/unit/proxy/guardrails/test_deferred_guardrail_logging.py @@ -416,9 +416,11 @@ async def test_deferred_anthropic_messages_bridged_to_the_responses_api_logs_the # --------------------------------------------------------------------------- -@pytest.mark.parametrize("call_type", ["ocr", "aocr", "completion", "acompletion", "embedding", "responses"]) +@pytest.mark.parametrize( + "call_type", ["ocr", "aocr", "anthropic_messages", "completion", "acompletion", "embedding", "responses"] +) @pytest.mark.parametrize("exception_raised", [False, True]) -def test_native_pending_logging_is_released_only_for_ocr(call_type: str, exception_raised: bool) -> None: +def test_native_pending_logging_is_released_once_for_every_call_type(call_type: str, exception_raised: bool) -> None: pending: Final = MagicMock() enqueue: Final = MagicMock() logger: Final = MagicMock( @@ -436,12 +438,8 @@ def test_native_pending_logging_is_released_only_for_ocr(call_type: str, excepti exception_raised=exception_raised, ) - if call_type in ("ocr", "aocr"): - pending.release.assert_called_once_with(not exception_raised) - assert logger._native_pending_logging is None - else: - pending.release.assert_not_called() - assert logger._native_pending_logging is pending + pending.release.assert_called_once_with(not exception_raised) + assert logger._native_pending_logging is None if exception_raised: enqueue.assert_not_called() else: