diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index e0a799d8e5..7d036615f5 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1853,6 +1853,14 @@ class Logging(LiteLLMLoggingBaseClass): cache_hit=cache_hit, standard_logging_object=kwargs.get("standard_logging_object", None), ) + litellm_params = self.model_call_details.get("litellm_params", {}) + is_sync_request = ( + litellm_params.get(CallTypes.acompletion.value, False) is not True + and litellm_params.get(CallTypes.aresponses.value, False) is not True + and litellm_params.get(CallTypes.aembedding.value, False) is not True + and litellm_params.get(CallTypes.aimage_generation.value, False) is not True + and litellm_params.get(CallTypes.atranscription.value, False) is not True + ) try: ## BUILD COMPLETE STREAMED RESPONSE complete_streaming_response: Optional[ @@ -1915,7 +1923,6 @@ class Logging(LiteLLMLoggingBaseClass): self.has_run_logging(event_type="sync_success") for callback in callbacks: try: - litellm_params = self.model_call_details.get("litellm_params", {}) should_run = self.should_run_callback( callback=callback, litellm_params=litellm_params, @@ -2185,22 +2192,7 @@ class Logging(LiteLLMLoggingBaseClass): if ( callback == "openmeter" - and self.model_call_details.get("litellm_params", {}).get( - "acompletion", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aembedding", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aimage_generation", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "atranscription", False - ) - is not True + and is_sync_request ): global openMeterLogger if openMeterLogger is None: @@ -2229,22 +2221,7 @@ class Logging(LiteLLMLoggingBaseClass): ) if ( isinstance(callback, CustomLogger) - and self.model_call_details.get("litellm_params", {}).get( - "acompletion", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aembedding", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aimage_generation", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "atranscription", False - ) - is not True + and is_sync_request and self.call_type != CallTypes.pass_through.value # pass-through endpoints call async_log_success_event ): # custom logger class @@ -2272,22 +2249,7 @@ class Logging(LiteLLMLoggingBaseClass): ) if ( callable(callback) is True - and self.model_call_details.get("litellm_params", {}).get( - "acompletion", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aembedding", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aimage_generation", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "atranscription", False - ) - is not True + and is_sync_request and customLogger is not None ): # custom logger functions print_verbose( @@ -2737,6 +2699,15 @@ class Logging(LiteLLMLoggingBaseClass): event_type="sync_failure" ): # prevent double logging return + litellm_params = self.model_call_details.get("litellm_params", {}) + is_sync_request = ( + litellm_params.get(CallTypes.acompletion.value, False) is not True + and litellm_params.get(CallTypes.aresponses.value, False) is not True + and litellm_params.get(CallTypes.aembedding.value, False) is not True + and litellm_params.get(CallTypes.aimage_generation.value, False) is not True + and litellm_params.get(CallTypes.atranscription.value, False) is not True + ) + try: start_time, end_time = self._failure_handler_helper_fn( exception=exception, @@ -2762,7 +2733,6 @@ class Logging(LiteLLMLoggingBaseClass): self.has_run_logging(event_type="sync_failure") for callback in callbacks: try: - litellm_params = self.model_call_details.get("litellm_params", {}) should_run = self.should_run_callback( callback=callback, litellm_params=litellm_params, @@ -2830,14 +2800,7 @@ class Logging(LiteLLMLoggingBaseClass): ) if ( isinstance(callback, CustomLogger) - and self.model_call_details.get("litellm_params", {}).get( - "acompletion", False - ) - is not True - and self.model_call_details.get("litellm_params", {}).get( - "aembedding", False - ) - is not True + and is_sync_request ): # custom logger class callback.log_failure_event( start_time=start_time, diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 07fc3cb02c..71d94287e8 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -668,6 +668,7 @@ def responses( optional_params=dict(responses_api_request_params), litellm_params={ **responses_api_request_params, + "aresponses": _is_async, "litellm_call_id": litellm_call_id, "metadata": metadata, }, 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 bae0e5bbb4..a4d3206fdc 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -14,6 +14,7 @@ import time from litellm.constants import SENTRY_DENYLIST, SENTRY_PII_DENYLIST from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging from litellm.litellm_core_utils.litellm_logging import set_callbacks +from litellm.types.utils import ModelResponse @pytest.fixture @@ -277,6 +278,87 @@ async def test_logging_non_streaming_request(): litellm.callbacks = original_callbacks +@pytest.mark.parametrize("async_flag", ["acompletion", "aresponses"]) +def test_success_handler_skips_sync_callbacks_for_async_requests(logging_obj, async_flag): + """Ensure sync success callbacks are skipped when async call type flags are set.""" + from litellm.integrations.custom_logger import CustomLogger + + class DummyLogger(CustomLogger): + pass + + logging_obj.stream = False # simulate non-streaming request where sync callbacks would normally run + logging_obj.model_call_details["litellm_params"] = {async_flag: True} + logging_obj.litellm_params = logging_obj.model_call_details["litellm_params"] + + dummy_logger = DummyLogger() + dummy_logger.log_success_event = MagicMock() + dummy_logger.log_stream_event = MagicMock() + + model_response = ModelResponse( + id="resp-123", + model="gpt-4o-mini", + choices=[ + { + "message": {"role": "assistant", "content": "hello"}, + "finish_reason": "stop", + "index": 0, + } + ], + usage={"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + ) + + with patch.object( + logging_obj, + "get_combined_callback_list", + return_value=[dummy_logger], + ): + logging_obj.success_handler(result=model_response) + + dummy_logger.log_success_event.assert_not_called() + dummy_logger.log_stream_event.assert_not_called() + + +@pytest.mark.parametrize("call_type", ["completion", "responses"]) +def test_success_handler_runs_sync_callbacks_for_sync_requests(logging_obj, call_type): + """Ensure sync success callbacks execute when call type is sync (completion/responses).""" + from litellm.integrations.custom_logger import CustomLogger + + class DummyLogger(CustomLogger): + pass + + logging_obj.stream = False + logging_obj.call_type = call_type + logging_obj.model_call_details["litellm_params"] = {} + logging_obj.litellm_params = {} + + dummy_logger = DummyLogger() + dummy_logger.log_success_event = MagicMock() + dummy_logger.log_stream_event = MagicMock() + + model_response = ModelResponse( + id="resp-123", + model="gpt-4o-mini", + choices=[ + { + "message": {"role": "assistant", "content": "hello"}, + "finish_reason": "stop", + "index": 0, + } + ], + usage={"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + ) + + with patch.object( + logging_obj, + "get_combined_callback_list", + return_value=[dummy_logger], + ): + logging_obj.success_handler(result=model_response) + + dummy_logger.log_success_event.assert_called_once() + dummy_logger.log_stream_event.assert_not_called() + + def test_get_user_agent_tags(): from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup