Skip to content

fix(channels): strip <think> tags from streaming draft updates#5505

Merged
theonlyhennygod merged 2 commits intozeroclaw-labs:masterfrom
rodela-ai:fix/streaming-think-tag-leak
Apr 8, 2026
Merged

fix(channels): strip <think> tags from streaming draft updates#5505
theonlyhennygod merged 2 commits intozeroclaw-labs:masterfrom
rodela-ai:fix/streaming-think-tag-leak

Conversation

@DaBlitzStein
Copy link
Copy Markdown
Contributor

Summary

  • Models like Qwen emit <think>...</think> reasoning blocks that were stripped from the final response via strip_think_tags() but leaked to users during partial streaming
  • DraftEvent::Content and DraftEvent::Progress sent raw accumulated text to update_draft() without sanitization
  • Add strip_think_tags_inline() to channels/mod.rs that strips think blocks from streaming draft text before sending to the channel
  • Handles single blocks, multiple blocks, unclosed blocks (drops tail), and trims whitespace

Root cause

The existing strip_think_tags functions in compatible.rs, loop_.rs, and ollama.rs only operate on the final complete response. The streaming draft handler accumulates text chunks and sends them directly to the channel without any think-tag sanitization.

Test plan

  • 6 unit tests for strip_think_tags_inline: single block, multiple blocks, unclosed block, no tags, empty string, whitespace trimming
  • cargo clippy --all-targets -- -D warnings passes
  • cargo fmt -- --check passes
  • Manual test: Qwen model with stream_mode = "partial" no longer shows <think> content during streaming

Qwen and similar models emit <think>...</think> reasoning blocks that
were stripped from the final response but leaked to users during
partial streaming via DraftEvent::Content and DraftEvent::Progress.

Add strip_think_tags_inline() to sanitize accumulated draft text
before sending to the channel, preventing reasoning tokens from
appearing in streaming updates.

Includes unit tests for single blocks, multiple blocks, unclosed
blocks, empty strings, and whitespace trimming.
@github-actions github-actions bot added the channel Auto scope: src/channels/** changed. label Apr 8, 2026
Copy link
Copy Markdown
Collaborator

@theonlyhennygod theonlyhennygod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehension Summary

What: Adds strip_think_tags_inline() to the streaming draft handler in src/channels/mod.rs, ensuring <think>...</think> reasoning blocks from models like Qwen are stripped before being sent to users during partial streaming updates.

Why: The existing strip_think_tags functions in compatible.rs, loop_.rs, and ollama.rs only operate on the final complete response. During streaming, raw <think> content leaked into DraftEvent::Content and DraftEvent::Progress updates sent to channels.

Blast radius: Streaming draft updates in process_channel_message(). No changes to final response handling, provider logic, or config schema.


Review

Risk: risk: low — channels subsystem, additive sanitization function.

Verified:

  • strip_think_tags_inline() correctly handles: single blocks, multiple blocks, unclosed blocks (drops tail), empty strings, and whitespace trimming.
  • Applied to both DraftEvent::Progress and DraftEvent::Content paths.
  • 6 unit tests cover all edge cases with clear assertions.
  • The function uses simple string scanning (no regex) — efficient for streaming hot path.
  • CI Required Gate is green across all platforms.
  • PR template sections are filled out (summary format is compact but all key info is present).
  • Privacy/data hygiene: pass.

Security/Performance Assessment:

  • No security impact — sanitization only removes reasoning tokens from user-visible output.
  • No performance impact — strip_think_tags_inline is O(n) with no allocations beyond the result string. Called per streaming chunk, which is acceptable overhead.

This PR is ready for maintainer merge.

Thank you for the clean fix and thorough test coverage.

@theonlyhennygod theonlyhennygod added the agent-approved PR approved by automated review agent label Apr 8, 2026
@theonlyhennygod theonlyhennygod merged commit c70e86c into zeroclaw-labs:master Apr 8, 2026
20 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in ZeroClaw Project Board Apr 8, 2026
5queezer added a commit to 5queezer/hrafn that referenced this pull request Apr 10, 2026
Port upstream fix from zeroclaw-labs/zeroclaw#5505. Models like Qwen
emit <think>...</think> reasoning blocks that were stripped from the
final response but leaked to users during partial streaming via
DraftEvent::Content and DraftEvent::Progress. Add
strip_think_tags_inline() that sanitizes draft text before sending to
the channel, handling single/multiple blocks, unclosed blocks, and
whitespace trimming.

Co-Authored-By: DaBlitzStein <DaBlitzStein@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5queezer added a commit to 5queezer/hrafn that referenced this pull request Apr 11, 2026
Port upstream fix from zeroclaw-labs/zeroclaw#5505. Models like Qwen
emit <think>...</think> reasoning blocks that were stripped from the
final response but leaked to users during partial streaming via
DraftEvent::Content and DraftEvent::Progress. Add
strip_think_tags_inline() that sanitizes draft text before sending to
the channel, handling single/multiple blocks, unclosed blocks, and
whitespace trimming.

Co-authored-by: DaBlitzStein <DaBlitzStein@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lzc256 pushed a commit to lzc256/zeroclaw that referenced this pull request Apr 11, 2026
…law-labs#5505)

* fix(channels): strip <think> tags from streaming draft updates

Qwen and similar models emit <think>...</think> reasoning blocks that
were stripped from the final response but leaked to users during
partial streaming via DraftEvent::Content and DraftEvent::Progress.

Add strip_think_tags_inline() to sanitize accumulated draft text
before sending to the channel, preventing reasoning tokens from
appearing in streaming updates.

Includes unit tests for single blocks, multiple blocks, unclosed
blocks, empty strings, and whitespace trimming.

* style: apply rustfmt to strip_think_tags_inline tests
whtiehack pushed a commit to whtiehack/zeroclaw that referenced this pull request Apr 13, 2026
…law-labs#5505)

Manual port of upstream c70e86c.

Qwen and similar models emit <think>...</think> reasoning blocks that
leaked to users during partial streaming via DraftEvent::Content and
DraftEvent::Progress. Add strip_think_tags_inline() to sanitize
accumulated draft text before sending to the channel.

Directly benefits wecom_ws draft flow.
whtiehack added a commit to whtiehack/zeroclaw that referenced this pull request Apr 18, 2026
Upstream c70e86c (zeroclaw-labs#5505) appended `.trim()` to strip_think_tags_inline,
which eats the trailing `\n` that Progress events carry (e.g. `"⏳ tool\n"`).
wecom_ws note_progress_update then push_str'es straight into the work log
with no separator, producing stacked progress lines without line breaks.

Fix: after trim_start / trim_end, re-append `\n` if the original post-strip
text ended with one. Behaviour of 0d2b57e (zeroclaw-labs#4394) is restored without
losing the think-tag stripping guarantee.

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

Labels

agent-approved PR approved by automated review agent channel Auto scope: src/channels/** changed.

Projects

Status: Shipped

Development

Successfully merging this pull request may close these issues.

2 participants