fix(logging): write spend log for streaming /v1/responses when response is a dict

This commit is contained in:
Devin AI 2026-07-08 15:39:47 +00:00
parent cd6e8cdf23
commit d617862c9c
2 changed files with 55 additions and 13 deletions

View file

@ -3161,21 +3161,23 @@ class Logging(LiteLLMLoggingBaseClass):
(ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent),
):
## return unified Usage object
if isinstance(result.response.usage, ResponseAPIUsage):
transformed_usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(
result.response.usage
)
resp = result.response
usage = resp.get("usage") if isinstance(resp, dict) else resp.usage
if isinstance(usage, ResponseAPIUsage) or (
isinstance(usage, dict) and ResponseAPILoggingUtils._is_response_api_usage(usage)
):
transformed_usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)
# Set as dict instead of Usage object so model_dump() serializes it correctly
setattr(
result.response,
"usage",
(
transformed_usage.model_dump()
if hasattr(transformed_usage, "model_dump")
else dict(transformed_usage)
),
transformed_usage_dict = (
transformed_usage.model_dump()
if hasattr(transformed_usage, "model_dump")
else dict(transformed_usage)
)
return result.response
if isinstance(resp, dict):
resp["usage"] = transformed_usage_dict
else:
setattr(resp, "usage", transformed_usage_dict)
return resp
else:
return None

View file

@ -3707,3 +3707,43 @@ def test_set_cost_breakdown_stores_reasoning_cost():
cost_for_built_in_tools_cost_usd_dollar=0.0,
)
assert "reasoning_cost" not in no_reasoning.cost_breakdown
def test_get_assembled_streaming_response_handles_dict_response(logging_obj):
"""
Regression test for streaming /v1/responses spend logging crash.
In the async streaming Responses path, ResponseCompletedEvent.response is a
plain dict at runtime (the base models allow extra fields), so accessing
result.response.usage raised AttributeError and the success handler crashed
before writing a SpendLogs row. The assembly helper must accept a dict
response and transform its Responses-API usage into chat-completion usage.
"""
from datetime import datetime
from litellm.types.llms.openai import (
ResponseCompletedEvent,
ResponsesAPIStreamEvents,
)
event = ResponseCompletedEvent.model_construct(
type=ResponsesAPIStreamEvents.RESPONSE_COMPLETED,
response={
"id": "resp_dict",
"usage": {"input_tokens": 11, "output_tokens": 7, "total_tokens": 18},
},
)
assert isinstance(event.response, dict)
assembled = logging_obj._get_assembled_streaming_response(
result=event,
start_time=datetime.now(),
end_time=datetime.now(),
is_async=True,
streaming_chunks=[],
)
assert isinstance(assembled, dict)
assert assembled["usage"]["prompt_tokens"] == 11
assert assembled["usage"]["completion_tokens"] == 7
assert assembled["usage"]["total_tokens"] == 18