From 25ee7b2aeb67d2ca19ed46e4112feef3078692b9 Mon Sep 17 00:00:00 2001 From: mukktinaadh <159904553+mukktinaadh@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:24:29 +0530 Subject: [PATCH] Add usage tracking to Responses API streaming completion events (Fixes #38065) - Extract usage from ResponseCompletedEvent.response.usage - Convert ResponseAPIUsage to dict using model_dump() - Include usage in interaction.completed/interaction.complete events - Update tests to properly mock usage attribute Fixes #38065 --- .../streaming_iterator.py | 9 +++++++-- .../test_gemini_interactions_transformation.py | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/litellm/interactions/litellm_responses_transformation/streaming_iterator.py b/litellm/interactions/litellm_responses_transformation/streaming_iterator.py index 1a9fce5a9d7..fdab4add79f 100644 --- a/litellm/interactions/litellm_responses_transformation/streaming_iterator.py +++ b/litellm/interactions/litellm_responses_transformation/streaming_iterator.py @@ -135,7 +135,7 @@ class LiteLLMResponsesInteractionsStreamingIterator: index=0, ) - def _build_completion_event(self, response_id: str) -> InteractionsAPIStreamingResponse: + def _build_completion_event(self, response_id: str, usage: dict | None = None) -> InteractionsAPIStreamingResponse: if self._use_legacy: return InteractionsAPIStreamingResponse( event_type="interaction.complete", @@ -144,6 +144,7 @@ class LiteLLMResponsesInteractionsStreamingIterator: status="completed", model=self.model, outputs=[{"type": "text", "text": self.collected_text}], + usage=usage, ) return InteractionsAPIStreamingResponse( event_type="interaction.completed", @@ -157,6 +158,7 @@ class LiteLLMResponsesInteractionsStreamingIterator: "content": [{"type": "text", "text": self.collected_text}], } ], + usage=usage, ) # ------------------------------------------------------------------ @@ -218,11 +220,14 @@ class LiteLLMResponsesInteractionsStreamingIterator: self.finished = True response: Final = responses_chunk.response response_id = self._interaction_id or getattr(response, "id", None) or f"interaction_{id(self)}" + # Extract usage from the response if available and convert to dict + usage_obj = getattr(response, "usage", None) + usage = usage_obj.model_dump() if usage_obj is not None else None terminal: Final[list[InteractionsAPIStreamingResponse]] = [] if self.sent_content_start: terminal.append(self._build_content_stop_event(response_id)) - terminal.append(self._build_completion_event(response_id)) + terminal.append(self._build_completion_event(response_id, usage)) self._sent_completion_event = True return terminal diff --git a/tests/test_litellm/interactions/test_gemini_interactions_transformation.py b/tests/test_litellm/interactions/test_gemini_interactions_transformation.py index d9b7cc790e6..5f5d1ef7fd4 100644 --- a/tests/test_litellm/interactions/test_gemini_interactions_transformation.py +++ b/tests/test_litellm/interactions/test_gemini_interactions_transformation.py @@ -444,7 +444,12 @@ class TestStreamingIterator: text_event = self._make_text_delta("hi") completed = MagicMock(spec=ResponseCompletedEvent) - completed.response = MagicMock(id="resp_999") + # Create a proper mock response with usage attribute that has model_dump method + mock_response = MagicMock(id="resp_999") + mock_usage = MagicMock() + mock_usage.model_dump.return_value = {"input_tokens": 10, "output_tokens": 20, "total_tokens": 30} + mock_response.usage = mock_usage + completed.response = mock_response sync_iter = MagicMock() sync_iter.__iter__ = lambda self: self