diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 5d5bcc6e1e9..71d3f1e900e 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1334,6 +1334,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ): super().__init__(streaming_response, sync_stream, json_mode) self._chat_completion_id: str | None = None + self._served_service_tier: str | None = None self._tool_call_index_map: dict[int, int] = {} # mutable-ok: per-stream accumulator state def _handle_string_chunk( @@ -1645,12 +1646,28 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ModelResponseStream: OpenAI-formatted streaming chunk """ verbose_logger.debug("Chat provider: transform_streaming_response called with chunk: %s", chunk) - return self._with_stream_scoped_id( - OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream( - chunk, tool_call_index_map=self._tool_call_index_map + self._remember_served_service_tier(chunk) + return self._with_served_service_tier( + self._with_stream_scoped_id( + OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream( + chunk, tool_call_index_map=self._tool_call_index_map + ) ) ) + def _remember_served_service_tier(self, chunk: dict[str, object]) -> None: + response_payload: Final = chunk.get("response") + if not isinstance(response_payload, dict): + return + served_tier: Final = response_payload.get("service_tier") + if isinstance(served_tier, str) and served_tier: + self._served_service_tier = served_tier + + def _with_served_service_tier(self, chunk: "ModelResponseStream") -> "ModelResponseStream": + if self._served_service_tier is not None and chunk.model_dump().get("service_tier") is None: + setattr(chunk, "service_tier", self._served_service_tier) # noqa: B010 # pydantic extra, not a declared field + return chunk + def _with_stream_scoped_id(self, chunk: "ModelResponseStream") -> "ModelResponseStream": if self._chat_completion_id is None: self._chat_completion_id = chunk.id diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index 1d0e1be7c0f..282b84104a6 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -4369,3 +4369,22 @@ def test_response_completed_carries_the_served_service_tier(): ) assert result.model_dump()["service_tier"] == "default" + + +def test_every_bridged_chunk_after_response_created_carries_the_served_service_tier(): + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + OpenAiResponsesToChatCompletionStreamIterator, + ) + + iterator = OpenAiResponsesToChatCompletionStreamIterator(streaming_response=None, sync_stream=True) + events = [ + {"type": "response.created", "response": {"id": "resp_1", "status": "in_progress", "service_tier": "default"}}, + {"type": "response.output_item.added", "output_index": 0, "item": {"type": "message"}}, + {"type": "response.output_text.delta", "output_index": 0, "delta": "Hi"}, + {"type": "response.output_item.done", "output_index": 0, "item": {"type": "message"}}, + {"type": "response.completed", "response": {"id": "resp_1", "status": "completed", "output": []}}, + ] + + relayed = [iterator.chunk_parser(event).model_dump().get("service_tier") for event in events] + + assert relayed == ["default"] * len(events), relayed