diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index c2060ceaad7..27c49ea658d 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -189,7 +189,11 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): return completed_response, error_message def _parse_sse_json_chunk(self, chunk: str) -> Optional[Any]: - stripped_chunk = CustomStreamWrapper._strip_sse_data_from_chunk(chunk) + # Strip outer whitespace before removing the SSE `data:` prefix. + # `_strip_sse_data_from_chunk` only matches the prefix at position 0, + # so chunks with leading whitespace (e.g. ` data: {...}`) would + # otherwise be returned unchanged and fail JSON parsing silently. + stripped_chunk = CustomStreamWrapper._strip_sse_data_from_chunk(chunk.strip()) if not stripped_chunk: return None stripped_chunk = stripped_chunk.strip() diff --git a/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py b/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py index 22f65da21d3..90a1c24bada 100644 --- a/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py +++ b/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py @@ -248,6 +248,48 @@ class TestChatGPTResponsesAPITransformation: assert parsed.output_text == "Hello from stream!" + def test_chatgpt_non_stream_sse_recovers_whitespace_padded_chunks(self): + """Chunks with leading whitespace before `data:` must still parse. + + `_strip_sse_data_from_chunk` only matches the prefix at position 0, + so without an outer `.strip()` such chunks would fail JSON parsing + and silently drop the contained event. + """ + config = ChatGPTResponsesAPIConfig() + response_payload = { + "id": "resp_test", + "object": "response", + "created_at": 1700000000, + "status": "completed", + "model": "gpt-5.4", + "output": [], + } + streamed_output_item = { + "type": "message", + "role": "assistant", + "content": [{"type": "output_text", "text": "Recovered from padded"}], + } + sse_body = "\n".join( + [ + f" data: {json.dumps({'type': 'response.output_item.done', 'output_index': 0, 'item': streamed_output_item})} ", + f"\tdata: {json.dumps({'type': 'response.completed', 'response': response_payload})}", + "data: [DONE]", + "", + ] + ) + raw_response = httpx.Response( + 200, headers={"content-type": "text/event-stream"}, text=sse_body + ) + logging_obj = MagicMock() + + parsed = config.transform_response_api_response( + model="chatgpt/gpt-5.4", + raw_response=raw_response, + logging_obj=logging_obj, + ) + + assert parsed.output_text == "Recovered from padded" + @pytest.mark.parametrize( "error_chunk", [