From a4d0ffcc830a429aef07acb2c575e7b8e8ef3b9c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 18 May 2026 14:05:54 +0530 Subject: [PATCH] fix(interactions): address greptile review feedback - Avoid mutating caller's generation_config dict by shallow-copying before popping image_config, preventing silent failures on retries - Skip schema key in response_format when response_format is None to avoid sending schema: null to the Google Interactions API - Remove delta field from step.stop events (new schema only); the StepStop model has no delta field and sending it duplicates already- streamed text and breaks spec-conformant clients Co-authored-by: Cursor --- .../streaming_iterator.py | 70 ++++++++++++------- .../gemini/interactions/transformation.py | 9 ++- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/litellm/interactions/litellm_responses_transformation/streaming_iterator.py b/litellm/interactions/litellm_responses_transformation/streaming_iterator.py index 51d9fa8b25e..3627736fde9 100644 --- a/litellm/interactions/litellm_responses_transformation/streaming_iterator.py +++ b/litellm/interactions/litellm_responses_transformation/streaming_iterator.py @@ -241,13 +241,18 @@ class LiteLLMResponsesInteractionsStreamingIterator: and self.sent_content_start and transformed.event_type == completion_event_type ): - stop_chunk = InteractionsAPIStreamingResponse( - event_type=stop_event_type, - index=0, - id=transformed.id, - object="content", - delta={"type": "text", "text": self.collected_text}, - ) + stop_kwargs: Dict[str, Any] = { + "event_type": stop_event_type, + "index": 0, + "id": transformed.id, + "object": "content", + } + if self._use_legacy: + stop_kwargs["delta"] = { + "type": "text", + "text": self.collected_text, + } + stop_chunk = InteractionsAPIStreamingResponse(**stop_kwargs) self._pending_interaction_complete = transformed return stop_chunk return transformed @@ -262,12 +267,17 @@ class LiteLLMResponsesInteractionsStreamingIterator: stop_event_type = ( "content.stop" if self._use_legacy else "step.stop" ) - return InteractionsAPIStreamingResponse( - event_type=stop_event_type, - index=0, - object="content", - delta={"type": "text", "text": self.collected_text}, - ) + stop_kwargs = { + "event_type": stop_event_type, + "index": 0, + "object": "content", + } + if self._use_legacy: + stop_kwargs["delta"] = { + "type": "text", + "text": self.collected_text, + } + return InteractionsAPIStreamingResponse(**stop_kwargs) raise StopIteration @@ -317,12 +327,19 @@ class LiteLLMResponsesInteractionsStreamingIterator: and self.sent_content_start and transformed.event_type == completion_event_type ): + stop_kwargs_async: Dict[str, Any] = { + "event_type": stop_event_type, + "index": 0, + "id": transformed.id, + "object": "content", + } + if self._use_legacy: + stop_kwargs_async["delta"] = { + "type": "text", + "text": self.collected_text, + } stop_chunk = InteractionsAPIStreamingResponse( - event_type=stop_event_type, - index=0, - id=transformed.id, - object="content", - delta={"type": "text", "text": self.collected_text}, + **stop_kwargs_async ) self._pending_interaction_complete = transformed return stop_chunk @@ -338,11 +355,16 @@ class LiteLLMResponsesInteractionsStreamingIterator: stop_event_type = ( "content.stop" if self._use_legacy else "step.stop" ) - return InteractionsAPIStreamingResponse( - event_type=stop_event_type, - index=0, - object="content", - delta={"type": "text", "text": self.collected_text}, - ) + stop_kwargs_async = { + "event_type": stop_event_type, + "index": 0, + "object": "content", + } + if self._use_legacy: + stop_kwargs_async["delta"] = { + "type": "text", + "text": self.collected_text, + } + return InteractionsAPIStreamingResponse(**stop_kwargs_async) raise StopAsyncIteration diff --git a/litellm/llms/gemini/interactions/transformation.py b/litellm/llms/gemini/interactions/transformation.py index f84b668284f..344c213e291 100644 --- a/litellm/llms/gemini/interactions/transformation.py +++ b/litellm/llms/gemini/interactions/transformation.py @@ -191,11 +191,13 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig): or "mime_type" not in response_format ): # Wrap the legacy schema into the new polymorphic format. - response_format = { + new_rf: Dict[str, Any] = { "type": "text", "mime_type": response_mime_type, - "schema": response_format, } + if response_format is not None: + new_rf["schema"] = response_format + response_format = new_rf if response_format is not None: request_body["response_format"] = response_format @@ -207,6 +209,9 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig): if generation_config is not None: image_config = None if isinstance(generation_config, dict): + generation_config = dict( + generation_config + ) # avoid mutating the caller's dict image_config = generation_config.pop("image_config", None) if not generation_config: generation_config = None