Skip to content

Conversation

@simonpcouch
Copy link
Collaborator

Addresses #837, and a subset of #828. Companion to posit-dev/shinychat#167.

Changes stream_text() to return Content objects instead of strings, allowing downstream consumers to distinguish between content types during streaming. With the companion shinychat PR, results look like this:

ellmer-shinychat-thinking.mp4

I'm not sure if this is the opposite direction of the dev dependency that yall would usually introduce. With main shinychat and this PR, each thinking delta would otherwise get its own collapsible:

collapsible-per-chunk

So, I opted to make ellmer depend on the shinychat Remotes. If this is a no-go, we could add some version-dependent logic, but felt cleaner to start with this approach.

Here's the reprex I use in the video:

# pak::pak("posit-dev/shinychat/pkg-r#167")
library(ellmer)
library(shinychat)

# Anthropic with thinking ------------------------------------------
chat_anthropic <- chat_claude(
  model = "claude-sonnet-4-5-20250929",
  params = params(reasoning_tokens = 1024)
)

clipr::write_clip(
  "Write a limerick about the tidyverse, returning only the limerick."
)
chat_app(chat_anthropic)

# OpenAI with reasoning ------------------------------------------
chat_openai <- chat_openai(
  model = "gpt-5.1",
  params = params(reasoning_effort = "high")
)

chat_app(chat_openai)

# Gemini with thinking ------------------------------------------
chat_gemini <- chat_google_gemini(
  model = "gemini-3-flash-preview",
  params = params(reasoning_tokens = 1024)
)

chat_app(chat_gemini)

R/chat.R Outdated
"ellmer::ContentThinking" = content@thinking,
"ellmer::ContentText" = content@text,
if (S7_inherits(content, Content)) format(content) else content
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure if using switch on S7 objects is considered bad practice, but the control flow was gnarly otherwise😅

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should have a stream_data() that returns the object and then stream_text() would contain this code to extract out just the text?

Copy link
Member

Choose a reason for hiding this comment

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

Or stream_content()?

@simonpcouch simonpcouch requested a review from hadley January 7, 2026 17:25
)

stream_content <- function(provider, event) {
result <- stream_text(provider, event)
Copy link
Member

Choose a reason for hiding this comment

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

This feels inside out to me? Why doesn't stream_text() call stream_content()?

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.

3 participants