Skip to content

Surface think blocks from reasoning models instead of stripping - #6

Merged
tjdoomer merged 2 commits into
mainfrom
feature/think-passthrough
Apr 2, 2026
Merged

tjdoomer merged 2 commits into
mainfrom
feature/think-passthrough

Conversation

@tjdoomer

@tjdoomer tjdoomer commented Apr 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Previously: <think>...</think> tags from reasoning models (DeepSeek-R1, QwQ, Qwen3-coder) were silently stripped
  • Now: extracted and emitted as { thought: true, text } parts that the existing Turn system surfaces as DeltaEventType.Thought events
  • New thinkBlockParser.ts — extractThinkBlocks() for non-streaming, StreamingThinkExtractor class for cross-chunk streaming
  • OpenAI generator: replaced filterThinkTags stripping with extraction in both paths
  • Anthropic generator: handles thinking and thinking_delta content blocks from Claude's extended thinking

Test plan

  • 11 think block parser unit tests (single/multi chunk, partial tags, reset)
  • Updated OpenAI generator tests to expect thought parts instead of stripped output
  • All 1873 existing tests pass
  • Full workspace build clean
  • Manual: run a reasoning model (QwQ, DeepSeek-R1) via LM Studio and verify thinking is visible

Summary by CodeRabbit

  • New Features

    • AI model internal reasoning (think blocks/thinking) is now preserved and emitted as separate components instead of being removed.
    • Internal reasoning from both OpenAI and Anthropic models can be selectively displayed in the UI with a toggle option.
  • Documentation

    • Added roadmap documentation outlining the multi-phase implementation strategy for exposing internal reasoning blocks.

Surface reasoning traces from DeepSeek-R1, QwQ, Qwen3 etc instead
of stripping them. First-class think parts with collapsible UI.
Previously, <think>...</think> tags from local reasoning models
(DeepSeek-R1, QwQ, Qwen3-coder) were silently stripped. Now they're
extracted and emitted as { thought: true, text } parts that the
existing Turn system surfaces as DeltaEventType.Thought events.

Changes:
- New thinkBlockParser.ts with extractThinkBlocks() for non-streaming
  and StreamingThinkExtractor for cross-chunk streaming
- OpenAI generator: replaced filterThinkTags stripping with extraction
  in both streaming and non-streaming paths
- Anthropic generator: handle 'thinking' and 'thinking_delta' content
  blocks from Claude's extended thinking feature

11 parser tests + all 1873 existing tests pass.
@tjdoomer
tjdoomer merged commit a99f8b8 into main Apr 2, 2026
@coderabbitai

coderabbitai Bot commented Apr 2, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 342b0d03-479e-41cd-9df9-7a7f76673e82

📥 Commits

Reviewing files that changed from the base of the PR and between 55adf32 and adae23b.

📒 Files selected for processing (6)
  • docs/roadmap/06-think-passthrough.md
  • packages/core/src/core/anthropicContentGenerator.ts
  • packages/core/src/core/openaiContentGenerator.test.ts
  • packages/core/src/core/openaiContentGenerator.ts
  • packages/core/src/utils/thinkBlockParser.test.ts
  • packages/core/src/utils/thinkBlockParser.ts

📝 Walkthrough

Walkthrough

This change implements "Think Block Passthrough" functionality by introducing utilities to extract and preserve <think> blocks as separate GenAI parts instead of filtering them out. The OpenAI and Anthropic generators are updated to use these utilities for both streaming and non-streaming responses, and comprehensive tests validate the new behavior.

Changes

Cohort / File(s) Summary
Think Block Parsing Utilities
packages/core/src/utils/thinkBlockParser.ts, packages/core/src/utils/thinkBlockParser.test.ts
New utilities for extracting <think> blocks from content. extractThinkBlocks() handles non-streaming extraction via regex, while StreamingThinkExtractor manages chunk-aware buffering for streaming with state tracking. Tests validate both extraction strategies and boundary-spanning tag handling.
OpenAI Generator Updates
packages/core/src/core/openaiContentGenerator.ts
Replaced stateful tag-filtering logic with StreamingThinkExtractor for streaming and extractThinkBlocks() for non-streaming. Think content now emits as separate { thought: true, text: ... } parts instead of being stripped.
OpenAI Generator Tests
packages/core/src/core/openaiContentGenerator.test.ts
Renamed test suite from "think tag filtering" to "think tag handling" and updated expectations: think blocks now appear as separate thought parts alongside visible text parts in both streaming and non-streaming modes.
Anthropic Generator Support
packages/core/src/core/anthropicContentGenerator.ts
Added handling for thinking_delta events in streaming and thinking blocks in non-streaming responses, yielding them as { thought: true, text: ... } parts.
Roadmap Documentation
docs/roadmap/06-think-passthrough.md
New documentation outlining multi-phase implementation strategy for think block passthrough, current behavior across generators, and proposed UI rendering with configuration flags.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OpenAIGen as OpenAI Generator
    participant Extractor as StreamingThinkExtractor
    participant Output as GenAI Parts

    Client->>OpenAIGen: Stream chunk received
    OpenAIGen->>Extractor: process(chunk)
    
    alt Contains closing </think>
        Extractor->>Extractor: Detect </think>, finalize think content
        Extractor->>Output: { thought: true, text: thinkContent }
        Extractor->>Output: { text: visibleText }
    else No closing tag yet
        Extractor->>Extractor: Buffer incomplete think block
        Extractor->>Output: { text: visibleText }
    end
    
    Output->>Client: Emit parts with thought attribution
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Whisper, whisper, think blocks bound,
No more hidden underground!
Extracted clean, with streaming care,
Thoughts now visible, laid bare! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/think-passthrough

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

2 participants