Skip to content

fix(streaming): detect text-only stream drops with no finish_reason (#32086) - #67616

Closed
ajzrva-sys wants to merge 1 commit into
NousResearch:mainfrom
ajzrva-sys:fix/text-only-stream-drop-detection
Closed

fix(streaming): detect text-only stream drops with no finish_reason (#32086)#67616
ajzrva-sys wants to merge 1 commit into
NousResearch:mainfrom
ajzrva-sys:fix/text-only-stream-drop-detection

Conversation

@ajzrva-sys

Copy link
Copy Markdown
Contributor

Summary

When a streaming response ends cleanly (HTTP 200, no error) with no finish_reason after delivering text but no tool calls, the chunk collector silently stamps finish_reason='stop' and the conversation loop presents truncated text as a complete response. The model's intended next step is lost, and the user sees a partial sentence with no indication anything went wrong.

Current State

Three stream-drop paths exist in _interruptible_streaming_api_call's chunk collector:

Path Condition Handled?
1. Zero-chunk No content, no reasoning, no tool calls ✓ EmptyStreamError, retried
2. Tool-call in progress Incomplete tool args, no finish_reason ✓ Partial-stream-stub (fixed in #32012)
3. Text-only Text content, no tool calls, no finish_reason Silently stamped as stop

Fix

Adds Path 3 detection: when finish_reason is None and we received text content but no tool calls, route through the same PARTIAL_STREAM_STUB_ID + FINISH_REASON_LENGTH machinery as Path 2. The conversation loop already handles this — it shows ⚠️ Stream interrupted by network error then ↻ Stream interrupted — requesting continuation (1/4)... and injects a continue prompt.

Changes

  • agent/chat_completion_helpers.py: add text-only stream-drop guard between the tool-call drop check and the effective_finish_reason fallthrough
  • tests/run_agent/test_partial_stream_finish_reason.py: new test test_no_finish_reason_text_only_routes_to_stub — clean stream end with text only, no finish_reason, verifies stub routing

Validation

.venv/bin/python -m pytest tests/run_agent/test_partial_stream_finish_reason.py tests/run_agent/test_streaming.py tests/run_agent/test_stream_interrupt_retry.py tests/run_agent/test_stream_drop_logging.py -v
# 88 passed

Fixes #32086

…ousResearch#32086)

When a streaming response ends cleanly (HTTP 200) with no finish_reason
after delivering text but no tool calls, the chunk collector silently
stamps finish_reason='stop' and the conversation loop presents truncated
text as a complete response.

Three stream-drop paths now exist after chunk collection:

1. Zero-chunk → EmptyStreamError, retried (existing)
2. Tool-call in progress, no finish_reason → partial-stream-stub (existing)
3. Text-only, no finish_reason → partial-stream-stub (NEW — this fix)

Path 3 routes through the same PARTIAL_STREAM_STUB_ID + FINISH_REASON_LENGTH
machinery as path 2. The conversation loop shows 'Stream interrupted —
requesting continuation' and injects a continue prompt, giving the model
a chance to resume where the stream dropped.

Observed with DeepSeek provider where CloudFront drops SSE streams
mid-response after delivering partial text.
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/streaming Streaming responses: gateway delivery, provider wire labels Jul 19, 2026
@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 19, 2026
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #67923 — your fix was cherry-picked with authorship preserved, plus a follow-up refactor extracting a shared _build_partial_stream_stub() helper to deduplicate the stub construction between the tool-call-drop and text-only-drop paths.

Thanks for the clear writeup and the well-targeted fix!

@ajzrva-sys
ajzrva-sys deleted the fix/text-only-stream-drop-detection branch August 1, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silent failure: stream timeout after partial text delivery shows no error to user

4 participants