perf: skip Usage Pydantic round-trip in logging payload (6.8x faster)

This commit is contained in:
Ryan Crabbe
2026-02-11 12:39:45 -08:00
parent b88b2520c0
commit 7dda6b0cbb
2 changed files with 125 additions and 5 deletions
+37 -5
View File
@@ -4599,6 +4599,37 @@ class StandardLoggingPayloadSetup:
raise ValueError(f"usage is required, got={usage} of type {type(usage)}")
@staticmethod
def get_usage_as_dict(
response_obj: Optional[dict],
combined_usage_object: Optional[Usage] = None,
) -> dict:
"""
Like get_usage_from_response_obj but returns a plain dict, skipping
the Pydantic Usage construction on the hot path.
"""
_empty: dict = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
if combined_usage_object is not None:
return combined_usage_object.model_dump()
if not response_obj:
return _empty
_raw = response_obj.get("usage", None)
if _raw is None:
return _empty
if isinstance(_raw, ResponseAPIUsage):
return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(
_raw
).model_dump()
if isinstance(_raw, dict):
if ResponseAPILoggingUtils._is_response_api_usage(_raw):
return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(
_raw
).model_dump()
return _raw
if isinstance(_raw, Usage):
return _raw.model_dump()
return _empty
@staticmethod
def get_model_cost_information(
base_model: Optional[str],
@@ -5055,7 +5086,8 @@ def get_standard_logging_object_payload(
completion_start_time = kwargs.get("completion_start_time", end_time)
call_type = kwargs.get("call_type")
cache_hit = kwargs.get("cache_hit", False)
usage = StandardLoggingPayloadSetup.get_usage_from_response_obj(
# Extract usage as a plain dict, avoiding Pydantic round-trip
usage_dict = StandardLoggingPayloadSetup.get_usage_as_dict(
response_obj=response_obj,
combined_usage_object=cast(
Optional[Usage], kwargs.get("combined_usage_object")
@@ -5102,7 +5134,7 @@ def get_standard_logging_object_payload(
vector_store_request_metadata=kwargs.get(
"vector_store_request_metadata", None
),
usage_object=usage.model_dump(),
usage_object=usage_dict,
proxy_server_request=proxy_server_request,
start_time=start_time,
response_id=id,
@@ -5188,9 +5220,9 @@ def get_standard_logging_object_payload(
cache_key=clean_hidden_params["cache_key"],
response_cost=response_cost,
cost_breakdown=logging_obj.cost_breakdown,
total_tokens=usage.total_tokens,
prompt_tokens=usage.prompt_tokens,
completion_tokens=usage.completion_tokens,
total_tokens=usage_dict.get("total_tokens", 0),
prompt_tokens=usage_dict.get("prompt_tokens", 0),
completion_tokens=usage_dict.get("completion_tokens", 0),
request_tags=request_tags,
end_user=end_user_id or "",
api_base=StandardLoggingPayloadSetup.strip_trailing_slash(
@@ -721,6 +721,94 @@ def test_cost_breakdown_missing_in_standard_logging_payload():
print("✅ Cost breakdown missing test passed!")
@pytest.mark.parametrize(
"use_combined_usage_object",
[False, True],
ids=["normal_usage_dict", "combined_usage_object"],
)
def test_usage_dict_roundtrip_in_payload(use_combined_usage_object):
"""
Regression test: verify that usage data flows correctly through
get_standard_logging_object_payload without unnecessary Pydantic round-trips.
Checks:
- usage_object in StandardLoggingMetadata is a plain dict with correct token values
- prompt_tokens, completion_tokens, total_tokens on the payload match the usage dict
- Works for both normal usage dict path and combined_usage_object (realtime API) path
"""
from litellm.litellm_core_utils.litellm_logging import (
get_standard_logging_object_payload,
Logging,
)
from datetime import datetime
logging_obj = Logging(
model="gpt-4o",
messages=[{"role": "user", "content": "Hi"}],
stream=False,
call_type="completion",
start_time=datetime.now(),
litellm_call_id="test-usage-roundtrip",
function_id="test-fn",
)
mock_response = {
"id": "chatcmpl-usage-test",
"object": "chat.completion",
"model": "gpt-4o",
"usage": {
"prompt_tokens": 42,
"completion_tokens": 58,
"total_tokens": 100,
},
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello!"},
"finish_reason": "stop",
}
],
}
kwargs = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hi"}],
"response_cost": 0.01,
"custom_llm_provider": "openai",
}
if use_combined_usage_object:
kwargs["combined_usage_object"] = Usage(
prompt_tokens=42, completion_tokens=58, total_tokens=100
)
start_time = datetime.now()
end_time = datetime.now()
payload = get_standard_logging_object_payload(
kwargs=kwargs,
init_response_obj=mock_response,
start_time=start_time,
end_time=end_time,
logging_obj=logging_obj,
status="success",
)
assert payload is not None
# Top-level token fields must match
assert payload["prompt_tokens"] == 42
assert payload["completion_tokens"] == 58
assert payload["total_tokens"] == 100
# usage_object in metadata must be a plain dict (not a Pydantic model)
usage_obj = payload["metadata"]["usage_object"]
assert isinstance(usage_obj, dict)
assert usage_obj["prompt_tokens"] == 42
assert usage_obj["completion_tokens"] == 58
assert usage_obj["total_tokens"] == 100
def test_merge_litellm_metadata_basic():
"""
Test that merge_litellm_metadata correctly merges metadata and litellm_metadata.