From 1f3ddc54852f1ae02be22409b62f1963300b3793 Mon Sep 17 00:00:00 2001 From: MengMa Date: Sun, 27 Sep 2026 18:08:37 +0800 Subject: [PATCH] fix(proxy): serialize late SSE payloads that are dicts A keepalive can open the response before the body exists as bytes. Reading .body on a dict crashed the stream. --- litellm/proxy/common_request_processing.py | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 15610da9aec..adeccef7e3f 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -1164,12 +1164,33 @@ async def _aclose_late_response(produced: Response) -> None: verbose_proxy_logger.debug("error closing relayed streaming generator: %s", exc) -async def _relay_late_response(produced: Response) -> AsyncGenerator[bytes, None]: +def _late_response_body(produced: object) -> bytes: + """Bytes for one SSE data frame. + + A late payload may be a dict, raw bytes, or a Response whose ``body`` is either. + """ + payload: Final = ( + produced + if isinstance(produced, (bytes, bytearray, Mapping)) + else getattr(produced, "body", produced) + ) + if isinstance(payload, (bytes, bytearray)): + raw: Final = bytes(payload) + return raw or b"{}" + if isinstance(payload, str): + text: Final = payload.encode() + return text or b"{}" + if isinstance(payload, Mapping): + return orjson.dumps(payload) + return b"{}" + + +async def _relay_late_response(produced: object) -> AsyncGenerator[bytes, None]: """Replay a Response that was built after a keepalive had already opened the wire.""" if not isinstance(produced, StreamingResponse): # The status line is already on the wire, so a non-streaming body, an error # body included, can only reach the client as an SSE frame. - yield b"data: " + (bytes(produced.body) or b"{}") + b"\n\n" + yield b"data: " + _late_response_body(produced) + b"\n\n" yield b"data: [DONE]\n\n" return