Skip to content

server: extend reasoning-delta token-boundary fix to Responses and Anthropic - #712

Open
Flor1an-B wants to merge 1 commit into
antirez:mainfrom
Flor1an-B:fix-reasoning-streaming-boundaries
Open

server: extend reasoning-delta token-boundary fix to Responses and Anthropic#712
Flor1an-B wants to merge 1 commit into
antirez:mainfrom
Flor1an-B:fix-reasoning-streaming-boundaries

Conversation

@Flor1an-B

Copy link
Copy Markdown

Complements #692 (@rareba) — extends the same fix to the two other paths issue #685 explicitly flagged as having the identical bug, which #692 doesn't touch.

Background

Issue #685 reports streaming reasoning deltas split at an arbitrary fixed byte offset (7 bytes before the end, held back to detect a possibly-split </think> marker) instead of at actual token boundaries — across three independently-shaped but identical code paths: OpenAI chat/completions, Responses, and Anthropic. #692 already fixes the OpenAI path (its diff is scoped to openai_sse_stream_update() only): track the end of the previous update's text (reasoning_token_end) and use that as the emit boundary, since production code calls *_sse_stream_update() once per newly decoded token, so the boundary between two calls is always a real token edge.

This PR applies the exact same technique, 1:1, to responses_sse_stream_update() and anthropic_sse_stream_update() — the two other places #685 pointed at (~line 7188 and ~line 8098 respectively). Not a competing fix for the OpenAI path; doesn't touch it at all.

Changes

In both responses_stream and anthropic_stream:

  • add reasoning_token_end
  • set it to raw_len at each point the THINKING branch returns without a close tag or finish (the prefix-check early return, the tool-seen-but-incomplete return, and the plain "still streaming" return)
  • use it instead of the fixed "</think>") - 1 holdback when computing the emit limit

Testing

  • Full existing ds4_test suite passes unchanged.
  • New test_anthropic_stream_preserves_reasoning_token_boundaries and test_responses_stream_preserves_reasoning_token_boundaries, mirroring server: preserve reasoning_content token boundaries #692's own test structure: three *_sse_stream_update() calls simulating three decode steps assert reasoning deltas land on the actual per-call text growth ("We need" / " to generate a title"), not a fixed mid-word suffix cut, for both paths.

🤖 Generated with Claude Code

…thropic

Issue antirez#685 reports that streaming reasoning_content/thinking deltas are
split at an arbitrary fixed byte offset (7 bytes before the end, held back
to detect a possibly-split "</think>" marker) instead of at actual token
boundaries, across three independent but identically-shaped code paths:
OpenAI chat/completions (~line 6477), Responses (~line 7188), and Anthropic
(~line 8098).

PR antirez#692 (@rareba) already fixes the OpenAI path: track the end of the
previous update's text (reasoning_token_end) and use that as the emit
boundary instead of a fixed suffix cut, since production code calls
*_sse_stream_update() once per newly decoded token, so the boundary between
two calls is always a real token edge. This commit applies the exact same
technique to the other two paths antirez#685 explicitly flagged as having the
identical shape, which antirez#692 doesn't touch (its diff is scoped to
openai_sse_stream_update() only) -- so this is additive to antirez#692, not a
competing fix for the same path.

Changes, mirrored 1:1 from antirez#692's approach in both
responses_sse_stream_update() and anthropic_sse_stream_update():
- add reasoning_token_end to responses_stream and anthropic_stream
- set it to raw_len at each point the THINKING branch returns without a
  close tag or finish (the prefix-check early return, the
  tool-seen-but-incomplete return, and the plain "still streaming" return)
- use it instead of the fixed "</think>" length - 1 holdback when computing
  the emit limit

Tested:
- Full existing ds4_test suite passes unchanged.
- New test_anthropic_stream_preserves_reasoning_token_boundaries and
  test_responses_stream_preserves_reasoning_token_boundaries, mirroring
  antirez#692's own test: three *_sse_stream_update() calls simulating three
  decode steps assert reasoning deltas land on the actual per-call text
  growth ("We need" / " to generate a title"), not a fixed mid-word suffix
  cut, for both paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant