mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
Merge 34eb909d3b into b5a72c2042
This commit is contained in:
commit
b37f6b7352
2 changed files with 58 additions and 19 deletions
|
|
@ -5185,27 +5185,35 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
(ResponseCompletedEvent, ResponseIncompleteEvent, ResponseFailedEvent),
|
||||
):
|
||||
## return unified Usage object
|
||||
if isinstance(result.response.usage, ResponseAPIUsage):
|
||||
set_response_cost_in_hidden_params(
|
||||
result.response,
|
||||
provider_reported_usage_cost(
|
||||
result.response.usage, self.model_call_details.get("custom_llm_provider")
|
||||
),
|
||||
)
|
||||
transformed_usage: Final = ResponseAPILoggingUtils.transform_response_api_usage_to_chat_usage(
|
||||
result.response.usage
|
||||
)
|
||||
# `result.response` may be a plain dict on the streaming /v1/responses
|
||||
# spend-log path (#29913) because ResponseCompletedEvent allows extra
|
||||
# fields; a bare `.usage` access raises AttributeError there and no
|
||||
# spend row is written at all.
|
||||
resp: Final = result.response
|
||||
resp_dict: Final = (
|
||||
cast(dict[str, object], resp) # cast-ok: the event allows extra fields, so a plain dict can land here
|
||||
if isinstance(resp, dict)
|
||||
else None
|
||||
)
|
||||
usage: Final = resp_dict.get("usage") if resp_dict is not None else getattr(resp, "usage", None)
|
||||
if isinstance(usage, ResponseAPIUsage):
|
||||
if resp_dict is None:
|
||||
set_response_cost_in_hidden_params(
|
||||
resp,
|
||||
provider_reported_usage_cost(usage, self.model_call_details.get("custom_llm_provider")),
|
||||
)
|
||||
transformed_usage: Final = 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)
|
||||
),
|
||||
new_usage: Final = (
|
||||
transformed_usage.model_dump()
|
||||
if hasattr(transformed_usage, "model_dump")
|
||||
else dict(transformed_usage)
|
||||
)
|
||||
return result.response
|
||||
if resp_dict is not None:
|
||||
resp_dict["usage"] = new_usage
|
||||
else:
|
||||
setattr(resp, "usage", new_usage)
|
||||
return resp
|
||||
elif isinstance(result, InteractionsAPIStreamingResponse):
|
||||
return self._assemble_completed_interaction_response(result)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -5271,6 +5271,37 @@ def test_get_assembled_streaming_response_returns_result_for_streaming():
|
|||
assert assembled is result
|
||||
|
||||
|
||||
def test_get_assembled_streaming_response_handles_dict_response():
|
||||
"""ResponseCompletedEvent.response may hold a plain dict on the streaming
|
||||
/v1/responses spend-log path (#29913). A bare `.usage` access raised
|
||||
AttributeError there, so no LiteLLM_SpendLogs row was written at all."""
|
||||
import datetime
|
||||
|
||||
from litellm.types.llms.openai import ResponseCompletedEvent
|
||||
|
||||
logging_obj = _make_logging_obj(stream=True)
|
||||
event = ResponseCompletedEvent.model_construct(
|
||||
type="response.completed",
|
||||
response={
|
||||
"id": "resp-dict-1",
|
||||
"usage": {"input_tokens": 10, "output_tokens": 5, "total_tokens": 15},
|
||||
},
|
||||
)
|
||||
|
||||
assembled = logging_obj._get_assembled_streaming_response(
|
||||
result=event,
|
||||
start_time=datetime.datetime.now(),
|
||||
end_time=datetime.datetime.now(),
|
||||
is_async=False,
|
||||
streaming_chunks=[],
|
||||
)
|
||||
|
||||
assert assembled == {
|
||||
"id": "resp-dict-1",
|
||||
"usage": {"input_tokens": 10, "output_tokens": 5, "total_tokens": 15},
|
||||
}
|
||||
|
||||
|
||||
def test_streaming_success_handler_includes_vertex_ai_metadata_in_standard_logging():
|
||||
"""Assembled streaming responses should include Vertex AI metadata in logging payload."""
|
||||
import datetime
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue