Skip to content

Replace tag injection with ContentThinkingDelta class#299

Merged
cpsievert merged 6 commits intomainfrom
fix/content-thinking-delta
May 7, 2026
Merged

Replace tag injection with ContentThinkingDelta class#299
cpsievert merged 6 commits intomainfrom
fix/content-thinking-delta

Conversation

@cpsievert
Copy link
Copy Markdown
Collaborator

@cpsievert cpsievert commented May 7, 2026

Summary

  • Introduces ContentThinkingDelta — a streaming fragment class with a phase property ("start", "body", or "end") that communicates thinking block boundaries to downstream consumers.
  • Providers now emit ContentThinkingDelta(thinking=text) instead of ContentThinking._as_chunk(text).
  • content="text": thinking is suppressed entirely (not yielded).
  • content="all": ContentThinkingDelta objects are yielded with phase annotations — no synthetic tag strings injected into the stream.
  • Removes _complete / _as_chunk() from ContentThinking — it reverts to always wrapping in <thinking> tags (for complete thoughts in turn history).
  • Console echo still wraps thinking in <thinking> tags regardless of mode.

Closes #293
Closes #291
Follow up to #294 and #297

Motivation

Feedback on posit-dev/shinychat#210 identified that injecting <thinking> tag strings into the stream couples upstream libraries to a specific string convention. Typed objects with phase metadata let downstream consumers (like shinychat) make their own rendering decisions while still getting explicit boundary signals.

This aligns chatlas with the approach taken in tidyverse/ellmer#975.

Test plan

  • 10 unit tests rewritten to cover new behavior (suppression, phase sequence, sentinel end)
  • Full test suite passes (469 passed, 11 skipped)
  • Pyright passes with 0 errors

🤖 Generated with Claude Code

Providers now emit ContentThinkingDelta (with a phase property) instead
of ContentThinking._as_chunk(). The central streaming loop annotates
phase ("start", "body", "end") and suppresses thinking in content="text"
mode. In content="all" mode, typed delta objects are yielded instead of
synthetic tag strings.

This aligns chatlas with the approach taken in tidyverse/ellmer#975 and
addresses feedback from posit-dev/shinychat#210 about preferring typed
objects over string conventions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

This comment was marked as resolved.

cpsievert and others added 3 commits May 7, 2026 17:55
- Give ContentThinkingDelta a distinct content_type ("thinking_delta")
  so it won't round-trip as ContentThinking during (de)serialization.
- Construct a new ContentThinkingDelta instead of mutating phase
  in-place, avoiding Pydantic validation bypass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

This comment was marked as resolved.

@cpsievert cpsievert merged commit f5d92af into main May 7, 2026
8 checks passed
@cpsievert cpsievert deleted the fix/content-thinking-delta branch May 7, 2026 23:23
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.

Streaming ContentThinking chunks lack proper <thinking> tag boundaries Documentation doesn't include examples of how to use reasoning parameter

2 participants