Skip to content

fix(proxy): preserve reasoning fields on streaming cache hits - #32096

Open
jesco-absolut wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
jesco-absolut:litellm_fix_streaming_cache_reasoning
Open

fix(proxy): preserve reasoning fields on streaming cache hits#32096
jesco-absolut wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
jesco-absolut:litellm_fix_streaming_cache_reasoning

Conversation

@jesco-absolut

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes #32068

Linear ticket

N/A

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Streaming cache hits were replayed through convert_to_streaming_response_async, which rebuilt Delta with only content, role, function_call, and tool_calls. Cached reasoning_content and thinking_blocks were present in the stored response but dropped during replay

The regression test failed before the fix with:

AttributeError: 'Delta' object has no attribute 'reasoning_content'

After this change, async streaming replay preserves cached reasoning fields on the first replay chunk while later content-slice chunks still avoid duplicating metadata

Live proxy proof runbook for a reviewer with Redis and a reasoning-capable model configured:

curl -N -i http://localhost:4000/v1/chat/completions \
  -H 'Authorization: Bearer sk-1234' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "REASONING_MODEL",
    "stream": true,
    "messages": [
      {"role": "user", "content": "Think briefly, then answer with the word done."}
    ]
  }'

Run the same request twice. Expected behavior: the first request is a cache miss and includes streamed delta.reasoning_content; the second request is a cache hit and still includes streamed delta.reasoning_content

I could not run this live proxy proof locally because this checkout does not have a Redis-backed proxy and real reasoning provider key configured

Type

Bug Fix
Test

Changes

Follow-up to #31022 in the same reasoning-streaming area

This changes async cached streaming replay to construct Delta from the full cached message dict, matching the sync cached replay path. That preserves reasoning_content, thinking_blocks, and any future Delta fields instead of hard-coding a small subset

Regression coverage verifies that cached async streaming replay preserves reasoning metadata on the first replay chunk and does not duplicate it on later content-slice chunks

Validation run:

uv run --no-sync pytest tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py::TestConvertToStreamingResponseAsync::test_preserves_reasoning_fields -q
uv run --no-sync pytest tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py -q
uv run --no-sync black --check tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py
uv run --no-sync ruff check litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py
uv run --no-sync ruff check --ignore T201,F401 tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py
uv run --no-sync python -m py_compile litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py
git --no-pager diff --check

Results:

74 passed
All checks passed for the changed production file and the new test path

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes async streaming cache-hit replay to preserve reasoning_content and thinking_blocks by constructing Delta from the full cached message dict rather than an explicit four-field subset, bringing it in line with the already-correct sync path.

  • Production change (1 line): Delta(**choice["message"]) replaces the hard-coded Delta(content=..., role=..., function_call=..., tool_calls=...) in convert_to_streaming_response_async, matching convert_to_streaming_response at line 299.
  • Test addition: test_preserves_reasoning_fields verifies that cached reasoning_content and thinking_blocks appear on the first replay chunk and are correctly stripped from subsequent content-slice chunks by the existing _clear_later_replay_slice_metadata helper.

Confidence Score: 5/5

Safe to merge — a one-line change that aligns the async replay path with the already-shipped sync path, accompanied by a targeted regression test.

The async path was constructing Delta with four hard-coded fields while the sync counterpart already used Delta(**choice["message"]). The fix is a single-line alignment; the sync path has been production-stable, and the new test confirms first-chunk field preservation and correct later-chunk stripping via the existing _clear_later_replay_slice_metadata helper. No existing tests were modified or weakened.

No files require special attention.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py Replaces explicit four-field Delta construction in the async streaming replay path with Delta(**choice["message"]), matching the existing sync path (line 299). Preserves reasoning_content, thinking_blocks, and any future Delta fields from cached responses.
tests/llm_translation/test_llm_response_utils/test_convert_dict_to_chat_completion.py Adds test_preserves_reasoning_fields covering that the first replay chunk carries reasoning_content and thinking_blocks from the cache while later content-slice chunks do not duplicate them. All new tests; no existing test coverage weakened.

Reviews (1): Last reviewed commit: "fix(proxy): preserve reasoning fields on..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jesco-absolut
jesco-absolut force-pushed the litellm_fix_streaming_cache_reasoning branch from df9e379 to 33f60b6 Compare August 9, 2026 06:19
@codspeed-hq

codspeed-hq Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing jesco-absolut:litellm_fix_streaming_cache_reasoning (33f60b6) with litellm_internal_staging (ecba48d)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: reasoning_content lost on streaming cache hit; FIX included

1 participant