mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(logging): preserve prompt/completion tokens in non-streaming /v1/responses usage logging
This commit is contained in:
parent
4d33964898
commit
607d068d38
2 changed files with 45 additions and 8 deletions
|
|
@ -1778,16 +1778,13 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
if isinstance(result, ResponsesAPIResponse):
|
||||
result = result.model_copy()
|
||||
transformed_usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(result.usage)
|
||||
setattr(result, "usage", transformed_usage)
|
||||
transformed_usage_dict = (
|
||||
transformed_usage.model_dump() if hasattr(transformed_usage, "model_dump") else dict(transformed_usage)
|
||||
)
|
||||
setattr(result, "usage", transformed_usage_dict)
|
||||
if (standard_logging_payload := self.model_call_details.get("standard_logging_object")) is not None:
|
||||
response_dict = result.model_dump() if hasattr(result, "model_dump") else dict(result)
|
||||
# Ensure usage is properly included with transformed chat format
|
||||
if transformed_usage is not None:
|
||||
response_dict["usage"] = (
|
||||
transformed_usage.model_dump()
|
||||
if hasattr(transformed_usage, "model_dump")
|
||||
else dict(transformed_usage)
|
||||
)
|
||||
response_dict["usage"] = transformed_usage_dict
|
||||
standard_logging_payload["response"] = response_dict
|
||||
elif isinstance(result, TranscriptionResponse):
|
||||
from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import (
|
||||
|
|
|
|||
|
|
@ -3639,6 +3639,46 @@ def test_handle_anthropic_messages_response_logging_degrades_on_unparseable_resp
|
|||
assert result.usage.prompt_tokens == 4 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def _responses_logging_obj():
|
||||
return LitellmLogging(
|
||||
model="openai/gpt-4o",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=False,
|
||||
call_type="aresponses",
|
||||
start_time=time.time(),
|
||||
litellm_call_id="33688",
|
||||
function_id="33688",
|
||||
)
|
||||
|
||||
|
||||
def test_transform_usage_objects_keeps_prompt_and_completion_tokens_serializable():
|
||||
"""Regression for #33688. _transform_usage_objects transforms a Responses API
|
||||
usage object into the chat Usage shape, but it must store it as a plain dict on
|
||||
result.usage. If it stores the Pydantic Usage object instead, result.model_dump()
|
||||
serializes it under the declared ResponseAPIUsage schema and silently drops
|
||||
prompt_tokens/completion_tokens (only total_tokens survives), so Prometheus,
|
||||
custom callbacks, and SpendLogs lose those counts."""
|
||||
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
|
||||
|
||||
logging_obj = _responses_logging_obj()
|
||||
result = logging_obj._transform_usage_objects(result=_responses_api_response_with_text())
|
||||
|
||||
assert isinstance(result.usage, dict)
|
||||
assert result.usage["prompt_tokens"] == 11
|
||||
assert result.usage["completion_tokens"] == 7
|
||||
assert result.usage["total_tokens"] == 18
|
||||
|
||||
dumped = result.model_dump()
|
||||
assert dumped["usage"]["prompt_tokens"] == 11
|
||||
assert dumped["usage"]["completion_tokens"] == 7
|
||||
assert dumped["usage"]["total_tokens"] == 18
|
||||
|
||||
usage_dict = StandardLoggingPayloadSetup.get_usage_as_dict(response_obj=dumped)
|
||||
assert usage_dict["prompt_tokens"] == 11
|
||||
assert usage_dict["completion_tokens"] == 7
|
||||
assert usage_dict["total_tokens"] == 18
|
||||
|
||||
|
||||
class _SuccessCapturingLogger(CustomLogger):
|
||||
"""Records the success payload. success_payload is populated only in
|
||||
async_log_success_event, so it stays None when the buggy no-op
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue