From 81dc6c1175fabc0acb7cbdd50c8f3db88bfef9b8 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:04:26 -0700 Subject: [PATCH] fix(proxy): encode non-dict Mapping Responses chunks as JSON objects --- litellm/proxy/proxy_server.py | 2 +- .../proxy/proxy_server/test_streaming_helpers.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a6648e47de6..aef2bca785c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10204,7 +10204,7 @@ def _format_streaming_sse_chunk(chunk: object) -> str | bytes: def _format_responses_sse_chunk(chunk: object) -> str | bytes: if isinstance(chunk, Mapping): - return f"data: {json.dumps(chunk, default=str)}\n\n" + return f"data: {json.dumps({**chunk}, default=str)}\n\n" return _format_streaming_sse_chunk(chunk=chunk) diff --git a/tests/unit/proxy/proxy_server/test_streaming_helpers.py b/tests/unit/proxy/proxy_server/test_streaming_helpers.py index b3a6e7b1fbf..b75a2494588 100644 --- a/tests/unit/proxy/proxy_server/test_streaming_helpers.py +++ b/tests/unit/proxy/proxy_server/test_streaming_helpers.py @@ -18,6 +18,7 @@ from __future__ import annotations import asyncio import json from collections.abc import AsyncIterator, Callable, Mapping +from types import MappingProxyType from typing import Final, Literal from unittest.mock import AsyncMock, MagicMock @@ -148,8 +149,14 @@ async def test_responses_stream_dispatches_named_events_without_a_chat_done_sent @pytest.mark.asyncio -@pytest.mark.parametrize("as_mapping", [False, True]) -async def test_responses_stream_sends_no_failure_event_after_an_upstream_error_event(as_mapping: bool) -> None: +@pytest.mark.parametrize( + "as_chunk", + [lambda event: event, lambda event: event.model_dump(), lambda event: MappingProxyType(event.model_dump())], + ids=["model", "dict", "mapping_proxy"], +) +async def test_responses_stream_sends_no_failure_event_after_an_upstream_error_event( + as_chunk: Callable[[ErrorEvent], BaseModel | Mapping[str, object]], +) -> None: response: Final = ResponsesAPIResponse(id="resp_error_then_raise", created_at=1, model="local-model", output=[]) created: Final = ResponseCreatedEvent.model_validate( {"type": "response.created", "sequence_number": 0, "response": response} @@ -160,7 +167,7 @@ async def test_responses_stream_sends_no_failure_event_after_an_upstream_error_e async def upstream() -> AsyncIterator[BaseModel | Mapping[str, object]]: yield created - yield error_event.model_dump() if as_mapping else error_event + yield as_chunk(error_event) raise litellm.APIError(status_code=500, message="Stream close failed", llm_provider="openai", model="local-model") frames: Final = tuple(