From 20d4a0d5dccb64f20f680ed7adf8417d0b31dcbd Mon Sep 17 00:00:00 2001 From: Muspi Merol Date: Mon, 31 Aug 2026 01:44:32 +0800 Subject: [PATCH 1/2] fix(proxy): keep reasoning_items on the streaming fast-serialization path --- litellm/proxy/proxy_server.py | 1 + .../proxy_server/test_streaming_helpers.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index db6294a364d..9e519839ce9 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -9626,6 +9626,7 @@ def _fast_serialize_simple_model_response_stream( "annotations", "reasoning_content", "thinking_blocks", + "reasoning_items", "provider_specific_fields", "refusal", ) diff --git a/tests/unit/proxy/proxy_server/test_streaming_helpers.py b/tests/unit/proxy/proxy_server/test_streaming_helpers.py index 69fa195e9d6..4ae4361cc1a 100644 --- a/tests/unit/proxy/proxy_server/test_streaming_helpers.py +++ b/tests/unit/proxy/proxy_server/test_streaming_helpers.py @@ -550,6 +550,16 @@ def test_fast_serialize_simple_model_response_stream_with_usage_returns_none_inv assert _fast_serialize_simple_model_response_stream(chunk) is None +def test_fast_serialize_simple_model_response_stream_with_reasoning_items_returns_none(): + """``reasoning_items`` carries the provider's encrypted reasoning state, which + the fast path cannot emit — it must bail so the slow path serializes it.""" + chunk = _simple_chunk(content="") + chunk.choices[0].delta.reasoning_items = [ + {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} + ] + assert _fast_serialize_simple_model_response_stream(chunk) is None + + # --------------------------------------------------------------------------- # _serialize_streaming_chunk # --------------------------------------------------------------------------- @@ -573,6 +583,20 @@ def test_serialize_streaming_chunk_simple_uses_fast_path_bytes(): } +def test_serialize_streaming_chunk_preserves_reasoning_items(): + """End-to-end pin for the bug: a chunk whose only payload beside text is + ``reasoning_items`` must still reach the wire with those items. The fast + path emits ``role``/``content`` only, so it has to decline this chunk.""" + chunk = _simple_chunk(content="") + chunk.choices[0].delta.reasoning_items = [ + {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} + ] + payload = json.loads(_serialize_streaming_chunk(chunk)) + assert payload["choices"][0]["delta"]["reasoning_items"] == [ + {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} + ] + + def test_serialize_streaming_chunk_invalid_input_raises_attribute_error(): """The helper is typed as ``BaseModel`` — handing it a plain dict trips the attribute-access path (no ``model_dump_json``).""" From 297d7d50afce82feb42d0a198730a55b405df1cd Mon Sep 17 00:00:00 2001 From: Muspi Merol Date: Tue, 20 Oct 2026 15:57:35 +0800 Subject: [PATCH 2/2] test(proxy): assert the complete reasoning items stream payload --- .../proxy_server/test_streaming_helpers.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/unit/proxy/proxy_server/test_streaming_helpers.py b/tests/unit/proxy/proxy_server/test_streaming_helpers.py index 4ae4361cc1a..165f3a14cbe 100644 --- a/tests/unit/proxy/proxy_server/test_streaming_helpers.py +++ b/tests/unit/proxy/proxy_server/test_streaming_helpers.py @@ -551,8 +551,6 @@ def test_fast_serialize_simple_model_response_stream_with_usage_returns_none_inv def test_fast_serialize_simple_model_response_stream_with_reasoning_items_returns_none(): - """``reasoning_items`` carries the provider's encrypted reasoning state, which - the fast path cannot emit — it must bail so the slow path serializes it.""" chunk = _simple_chunk(content="") chunk.choices[0].delta.reasoning_items = [ {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} @@ -584,17 +582,27 @@ def test_serialize_streaming_chunk_simple_uses_fast_path_bytes(): def test_serialize_streaming_chunk_preserves_reasoning_items(): - """End-to-end pin for the bug: a chunk whose only payload beside text is - ``reasoning_items`` must still reach the wire with those items. The fast - path emits ``role``/``content`` only, so it has to decline this chunk.""" chunk = _simple_chunk(content="") chunk.choices[0].delta.reasoning_items = [ {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} ] - payload = json.loads(_serialize_streaming_chunk(chunk)) - assert payload["choices"][0]["delta"]["reasoning_items"] == [ - {"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"} - ] + payload: Final = json.loads(_serialize_streaming_chunk(chunk)) + assert payload == { + "id": "chatcmpl-test", + "created": 0, + "model": "gpt-4", + "object": "chat.completion.chunk", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + "content": "", + "reasoning_items": [{"id": "rs_abc", "type": "reasoning", "encrypted_content": "ENCRYPTED-BLOB"}], + }, + } + ], + } def test_serialize_streaming_chunk_invalid_input_raises_attribute_error():