Merge pull request #20440 from ryan-crabbe/perf/skip-duplicate-logging-payload

perf: skip duplicate get_standard_logging_object_payload for non-streaming req's
This commit is contained in:
ryan-crabbe 2026-02-21 12:07:31 -08:00 committed by GitHub
commit be016b683b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 2 deletions

View file

@ -1639,6 +1639,13 @@ class Logging(LiteLLMLoggingBaseClass):
logging_result, start_time, end_time
)
if (
standard_logging_payload := self.model_call_details.get(
"standard_logging_object"
)
) is not None:
emit_standard_logging_payload(standard_logging_payload)
def _build_standard_logging_payload(
self, init_response_obj: Any, start_time: Any, end_time: Any
) -> Any:
@ -1752,6 +1759,12 @@ class Logging(LiteLLMLoggingBaseClass):
] = self._build_standard_logging_payload(
result, start_time, end_time
)
if (
standard_logging_payload := self.model_call_details.get(
"standard_logging_object"
)
) is not None:
emit_standard_logging_payload(standard_logging_payload)
elif standard_logging_object is not None:
self.model_call_details[
"standard_logging_object"
@ -3211,6 +3224,8 @@ class Logging(LiteLLMLoggingBaseClass):
is_async: bool,
streaming_chunks: List[Any],
) -> Optional[Union[ModelResponse, TextCompletionResponse, ResponsesAPIResponse]]:
if self.stream is not True:
return None
if isinstance(result, ModelResponse):
return result
elif isinstance(result, TextCompletionResponse):

View file

@ -14,7 +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
from litellm.types.utils import ModelResponse, TextCompletionResponse
@pytest.fixture
@ -264,7 +264,7 @@ async def test_logging_result_for_bridge_calls(logging_obj):
mock_response="Hello, world!",
)
await asyncio.sleep(1)
assert mock_should_run_logging.call_count == 2 # called twice per call
assert mock_should_run_logging.call_count == 1
@pytest.mark.asyncio
@ -1357,3 +1357,116 @@ def test_get_error_information_error_code_priority():
assert result["error_class"] == "NoCodeException"
# ──────────────────────────────────────────────────────────────────────
# Tests for _get_assembled_streaming_response non-streaming early return
# ──────────────────────────────────────────────────────────────────────
def _make_logging_obj(stream: bool) -> LitellmLogging:
return LitellmLogging(
model="openai/codex-mini-latest",
messages=[{"role": "user", "content": "Hey"}],
stream=stream,
call_type="completion",
start_time=time.time(),
litellm_call_id="test-123",
function_id="test-fn",
)
def test_get_assembled_streaming_response_returns_none_for_non_streaming():
"""Non-streaming requests should return None so the streaming block is skipped."""
import datetime
logging_obj = _make_logging_obj(stream=False)
result = ModelResponse(id="resp-1", choices=[], model="test")
assembled = logging_obj._get_assembled_streaming_response(
result=result,
start_time=datetime.datetime.now(),
end_time=datetime.datetime.now(),
is_async=True,
streaming_chunks=[],
)
assert assembled is None
def test_get_assembled_streaming_response_returns_result_for_streaming():
"""Streaming requests should return the ModelResponse for further processing."""
import datetime
logging_obj = _make_logging_obj(stream=True)
result = ModelResponse(id="resp-1", choices=[], model="test")
assembled = logging_obj._get_assembled_streaming_response(
result=result,
start_time=datetime.datetime.now(),
end_time=datetime.datetime.now(),
is_async=True,
streaming_chunks=[],
)
assert assembled is result
def test_get_assembled_streaming_response_returns_none_for_non_streaming_text_completion():
"""Non-streaming TextCompletionResponse should also return None."""
import datetime
logging_obj = _make_logging_obj(stream=False)
result = TextCompletionResponse(id="resp-1", choices=[], model="test")
assembled = logging_obj._get_assembled_streaming_response(
result=result,
start_time=datetime.datetime.now(),
end_time=datetime.datetime.now(),
is_async=True,
streaming_chunks=[],
)
assert assembled is None
@pytest.mark.asyncio
async def test_non_streaming_computes_standard_logging_object_once():
"""
Non-streaming acompletion should call get_standard_logging_object_payload
exactly once, not twice.
"""
import asyncio
import litellm
with patch.object(
litellm.litellm_core_utils.litellm_logging,
"get_standard_logging_object_payload",
) as mock_payload:
await litellm.acompletion(
max_tokens=100,
messages=[{"role": "user", "content": "Hey"}],
model="openai/codex-mini-latest",
mock_response="Hello, world!",
)
await asyncio.sleep(1)
assert mock_payload.call_count == 1
@pytest.mark.asyncio
async def test_emit_standard_logging_payload_called_for_non_streaming():
"""
emit_standard_logging_payload should still be called for non-streaming
requests (moved from the streaming block to _process_hidden_params_and_response_cost).
"""
import asyncio
import litellm
with patch.object(
litellm.litellm_core_utils.litellm_logging,
"emit_standard_logging_payload",
) as mock_emit:
await litellm.acompletion(
max_tokens=100,
messages=[{"role": "user", "content": "Hey"}],
model="openai/codex-mini-latest",
mock_response="Hello, world!",
)
await asyncio.sleep(1)
assert mock_emit.call_count >= 1