From ba8d4036d3648af9eb85b2c7d9146fde329a79fd Mon Sep 17 00:00:00 2001 From: Muspi Merol Date: Mon, 31 Aug 2026 01:44:32 +0800 Subject: [PATCH] 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 887716a383a..d8c24e40260 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8266,6 +8266,7 @@ def _fast_serialize_simple_model_response_stream( "annotations", "reasoning_content", "thinking_blocks", + "reasoning_items", "provider_specific_fields", "refusal", ) diff --git a/tests/test_litellm/proxy/proxy_server/test_streaming_helpers.py b/tests/test_litellm/proxy/proxy_server/test_streaming_helpers.py index fdaad567f95..f74b743037d 100644 --- a/tests/test_litellm/proxy/proxy_server/test_streaming_helpers.py +++ b/tests/test_litellm/proxy/proxy_server/test_streaming_helpers.py @@ -517,6 +517,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 # --------------------------------------------------------------------------- @@ -540,6 +550,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``)."""