Skip to content

Commit 399114d

Browse files
authored
ellmer 0.2.0 (#736)
1 parent 62e44ee commit 399114d

File tree

6 files changed

+2857
-0
lines changed

6 files changed

+2857
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
output: hugodown::hugo_document
3+
4+
slug: ellmer-0-2-0
5+
title: ellmer 0.2.0
6+
date: 2025-05-28
7+
author: Hadley Wickham
8+
description: >
9+
ellmer 0.2.0 lands with a swag of upgrades: Garrick Aden‑Buie joins the
10+
team, we make a couple of breaking changes, and add serious scale with
11+
`parallel_chat()` and `batch_chat()`. A new `params()` helper standardises
12+
model settings across providers and chats now report how much they cost.
13+
The release also tidies `chat_*` names, bumps default models and adds
14+
Hugging Face, Mistral AI, and Portkey connectors.
15+
photo:
16+
url: https://unsplash.com/photos/elephant-walking-during-daytime-QJbyG6O0ick
17+
author: Nam Anh
18+
19+
# one of: "deep-dive", "learn", "package", "programming", "roundup", or "other"
20+
categories: [package]
21+
tags: [ellmer, ai]
22+
---
23+
24+
25+
```{r, include = FALSE}
26+
knitr::opts_chunk$set(
27+
collapse = TRUE,
28+
comment = "#>"
29+
)
30+
```
31+
32+
<!--
33+
TODO:
34+
* [x] Look over / edit the post's title in the yaml
35+
* [x] Edit (or delete) the description; note this appears in the Twitter card
36+
* [x] Pick category and tags (see existing with `hugodown::tidy_show_meta()`)
37+
* [x] Find photo & update yaml metadata
38+
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
39+
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
40+
* [x] `hugodown::use_tidy_thumbnails()`
41+
* [x] Add intro sentence, e.g. the standard tagline for the package
42+
* [x] `usethis::use_tidy_thanks()`
43+
-->
44+
45+
# ellmer 0.2.0
46+
47+
I'm thrilled to announce the release of [ellmer 0.2.0](https://ellmer.tidyverse.org)! ellmer is an R package designed to make it easy to use large language models (LLMs) from R. It supports a wide variety of providers (including OpenAI, Anthropic, Azure, Google, Snowflake, Databricks and many more), makes it easy to [extract structured data](https://ellmer.tidyverse.org/articles/structured-data.html), and to give the LLM the ability to call R functions via [tool calling](https://ellmer.tidyverse.org/articles/tool-calling.html).
48+
49+
You can install it from CRAN with:
50+
51+
```{r, eval = FALSE}
52+
install.packages("ellmer")
53+
```
54+
55+
Before diving into the details of what's new, I wanted to welcome Garrick Aden-Buie to the development team! Garrick is one of my colleagues at Posit, and has been instrumental in building out the developer side of ellmer, particularly as it pertains to tool calling and async, with the goal of making [shinychat](https://posit-dev.github.io/shinychat/) as useful as possible.
56+
57+
In this post, I'll walk you through the key changes in this release: a couple of breaking changes, new batched and parallel processing capabilities, a cleaner way to set model parameters, built-in cost estimates, and general updates to our provider ecosystem. This was a giant release, and I'm only touching on the most important topics here, so if you want all the details, please check out the [release notes](https://github.com/tidyverse/ellmer/releases/tag/v0.2.0).
58+
59+
```{r setup}
60+
library(ellmer)
61+
```
62+
63+
## Breaking changes
64+
65+
Before we dive into the cool new features, we need to talk about the less fun stuff: some breaking changes. As the ellmer package is still experimental (i.e. it has not yet reached 1.0.0), we will be making some breaking changes from time-to-time. That said, we'll always provide a way to revert to the old behaviour and will generally avoid changes that we expect will affect a lot of existing code. There are three breaking changes in this release:
66+
67+
* If you save a `Chat` object to disk, the API key is no longer recorded. This protects you from accidentally saving your API key in an insecure location at the cost of not allowing you to resume a chat you saved to disk (we'll see if we can fix that problem in the future).
68+
69+
* We've made some refinements to how ellmer converts JSON to R data structures. The most important change is that tools are now invoked with their inputs converted to standard R data structures. This means you'll get proper R vectors, lists, and data frames instead of raw JSON objects, making your functions easier to write. If you prefer the old behavior, you can opt out with `tool(convert = FALSE)`.
70+
71+
* The `turn` argument has been removed from the `chat_` functions; use `Chat$set_turns()` instead.
72+
73+
* `Chat$tokens()` has been renamed to `Chat$get_tokens()` and it now returns a correctly structured data frame with rows aligned to turns.
74+
75+
## Batch and parallel chat
76+
77+
One of the most exciting additions in 0.2.0 is support for processing multiple chats efficiently. If you've ever found yourself wanting to run the same prompt against hundreds or thousands of different inputs, you now have two powerful options: `parallel_chat()` and `batch_chat()`.
78+
79+
`parallel_chat()` works with any provider and lets you submit multiple chats simultaneously:
80+
81+
```{r}
82+
chat <- chat_openai()
83+
prompts <- interpolate("
84+
What do people from {{state.name}} bring to a potluck dinner?
85+
Give me the top three things.
86+
")
87+
```
88+
```{r}
89+
#| eval: false
90+
results <- parallel_chat(chat, prompts)
91+
# [working] (32 + 0) -> 10 -> 8 | ■■■■■■ 16%
92+
```
93+
This doesn't save you money, but it can be dramatically faster than processing chats sequentially.
94+
(Also note that `interpolate()` is now vectorised, making it much easier to generate many prompts from vectors or data frames.)
95+
96+
`batch_chat()` currently works with OpenAI and Anthropic, offering a different trade-off:
97+
98+
```{r}
99+
chat <- chat_openai()
100+
results <- batch_chat(chat, prompts, path = "potluck.json")
101+
results[[1]]
102+
```
103+
104+
Batch requests can take up to 24 hours to complete (although often finish much faster), but cost 50% less than regular requests. This makes them perfect for large-scale analysis where you can afford to wait. Since they can take a long time to complete, `batch_chat()` requires a `path`, which is used to store information about the state of the job, ensuring that you never lose any work. If you want to keep using your R session, you can either set `wait = FALSE` or simply interrupt the waiting process, then later, either call `batch_chat()` to resume where you left off or call `batch_chat_completed()` to see if the results are ready to retrieve. `batch_chat()` will store the chat responses in this file, so you can either keep it around to cache the results, or delete it to free up disk space.
105+
106+
Both functions come with structured data variations: `batch_chat_structured()` and `parallel_chat_structured()`, which make it easy to extract structured data from multiple strings.
107+
108+
```{r}
109+
prompts <- list(
110+
"I go by Alex. 42 years on this planet and counting.",
111+
"Pleased to meet you! I'm Jamal, age 27.",
112+
"They call me Li Wei. Nineteen years young.",
113+
"Fatima here. Just celebrated my 35th birthday last week.",
114+
"The name's Robert - 51 years old and proud of it.",
115+
"Kwame here - just hit the big 5-0 this year."
116+
)
117+
type_person <- type_object(name = type_string(), age = type_number())
118+
119+
data <- batch_chat_structured(
120+
chat = chat,
121+
prompts = prompts,
122+
path = "people-data.json",
123+
type = type_person
124+
)
125+
data
126+
```
127+
128+
This family of functions is experimental because I'm still refining the user interface, particularly around error handling. I'd love to hear your feedback!
129+
130+
## Parameters
131+
132+
Previously, setting model parameters like `temperature` and `seed` required knowing the details of each provider's API. The new `params()` function provides a consistent interface across providers:
133+
134+
```{r}
135+
chat1 <- chat_openai(params = params(temperature = 0.7, seed = 42))
136+
chat2 <- chat_anthropic(params = params(temperature = 0.7, max_tokens = 100))
137+
```
138+
139+
ellmer automatically maps these to the appropriate provider-specific parameter names. If a provider doesn't support a particular parameter, it will generate a warning, not an error. This allows you to write provider-agnostic code without worrying about compatibility.
140+
141+
`params()` is currently supported by `chat_anthropic()`, `chat_azure()`, `chat_openai()`, and `chat_gemini()`; feel free to [file an issue](https://github.com/tidyverse/ellmer/issues/new) if you'd like us to add support for another provider.
142+
143+
## Cost estimates
144+
145+
Understanding the cost of your LLM usage is crucial, especially when working at scale. ellmer now tracks and displays cost estimates. For example, when you print a `Chat` object, you'll see estimated costs alongside token usage:
146+
147+
```{r}
148+
chat <- chat_openai(echo = FALSE)
149+
joke <- chat$chat("Tell me a joke")
150+
chat
151+
```
152+
153+
You can also access costs programmatically with `Chat$get_cost()` and see detailed breakdowns with `tokens_usage()`:
154+
155+
```{r}
156+
chat$get_cost()
157+
158+
token_usage()
159+
```
160+
161+
(The numbers will be more interesting for real use cases.)
162+
163+
Keep in mind that these are estimates based on published pricing. LLM providers make it surprisingly difficult to determine exact costs, so treat these as helpful approximations rather than precise accounting.
164+
165+
## Provider updates
166+
167+
The ellmer ecosystem continues to grow! We've added support for three new providers:
168+
169+
- [Hugging Face](https://huggingface.co) via `chat_huggingface()`, thanks to [Simon Spavound](https://github.com/s-spavound).
170+
- [Mistral AI](https://mistral.ai) via `chat_mistral()`.
171+
- [Portkey](https://portkey.ai) via `chat_portkey()`, thanks to [Maciej Banaś](https://github.com/maciekbanas).
172+
173+
`chat_snowflake()` and `chat_databricks()` are now considerably more featureful, thanks to improvements in the underlying APIs. They now also both default to Claude Sonnet 3.7, and `chat_databricks()` picks up Databricks workspace URLs set in the Databricks configuration file, improving compatibility with the Databricks CLI.
174+
175+
We've also cleaned up the naming scheme for existing providers. The old function names still work but are deprecated:
176+
177+
- `chat_anthropic()` replaces `chat_claude()`.
178+
- `chat_azure_openai()` replaces `chat_azure()`.
179+
- `chat_aws_bedrock()` replaces `chat_bedrock()`.
180+
- `chat_google_gemini()` replaces `chat_gemini()`.
181+
182+
And updated some default models: `chat_anthropic()` now uses Claude Sonnet 4, and `chat_openai()` uses GPT-4.1.
183+
184+
Finally, we've added a family of `models_*()` functions that let you discover available models for each provider:
185+
186+
```{r}
187+
tibble::as_tibble(models_anthropic())
188+
```
189+
190+
These return data frames with model IDs, pricing information (where available), and other provider-specific metadata.
191+
192+
## Developer tools
193+
194+
This release includes several improvements for developers building more sophisticated LLM applications, particularly around tool usage and debugging.
195+
196+
The most immediately useful addition is `echo = "output"` in `Chat$chat()`. When you're working with tools, this shows you exactly what's happening as tool requests and results flow back and forth. For example:
197+
198+
```{r}
199+
chat <- chat_anthropic(echo = "output")
200+
chat$set_tools(btw::btw_tools("session"))
201+
chat$chat("Do I have bslib installed?")
202+
```
203+
204+
For more advanced use cases, we've added **tool annotations** via `tool_annotations()`. These follow the [Model Context Protocol](https://modelcontextprotocol.io/introduction) and let you provide richer descriptions of your tools:
205+
206+
```r
207+
weather_tool <- tool(
208+
fun = get_weather,
209+
description = "Get current weather for a location",
210+
.annotations = tool_annotations(
211+
audience = list("user", "assistant"),
212+
level = "beginner"
213+
)
214+
)
215+
```
216+
217+
We've also introduced `tool_reject()`, which lets you reject tool requests with an explanation:
218+
219+
```r
220+
my_tool <- tool(function(dangerous_action) {
221+
if (dangerous_action == "delete_everything") {
222+
tool_reject("I can't perform destructive actions")
223+
}
224+
# ... normal tool logic
225+
})
226+
```
227+
228+
## Acknowledgements
229+
230+
A big thanks to all 67 contributors who helped out with ellmer development through thoughtful discussions, bug reports, and pull requests. [&#x0040;13479776](https://github.com/13479776), [&#x0040;adrbmdns](https://github.com/adrbmdns), [&#x0040;AlvaroNovillo](https://github.com/AlvaroNovillo), [&#x0040;andersolarsson](https://github.com/andersolarsson), [&#x0040;andrie](https://github.com/andrie), [&#x0040;arnavchauhan7](https://github.com/arnavchauhan7), [&#x0040;arunrajes](https://github.com/arunrajes), [&#x0040;asb2111](https://github.com/asb2111), [&#x0040;atheriel](https://github.com/atheriel), [&#x0040;bakaburg1](https://github.com/bakaburg1), [&#x0040;billsanto](https://github.com/billsanto), [&#x0040;bzzzwa](https://github.com/bzzzwa), [&#x0040;calderonsamuel](https://github.com/calderonsamuel), [&#x0040;christophscheuch](https://github.com/christophscheuch), [&#x0040;conorotompkins](https://github.com/conorotompkins), [&#x0040;CorradoLanera](https://github.com/CorradoLanera), [&#x0040;david-diviny-nousgroup](https://github.com/david-diviny-nousgroup), [&#x0040;DavisVaughan](https://github.com/DavisVaughan), [&#x0040;dm807cam](https://github.com/dm807cam), [&#x0040;dylanpieper](https://github.com/dylanpieper), [&#x0040;edgararuiz](https://github.com/edgararuiz), [&#x0040;gadenbuie](https://github.com/gadenbuie), [&#x0040;genesis-gh-yshteyman](https://github.com/genesis-gh-yshteyman), [&#x0040;hadley](https://github.com/hadley), [&#x0040;Ifeanyi55](https://github.com/Ifeanyi55), [&#x0040;jcheng5](https://github.com/jcheng5), [&#x0040;jimbrig](https://github.com/jimbrig), [&#x0040;jsowder](https://github.com/jsowder), [&#x0040;jvroberts](https://github.com/jvroberts), [&#x0040;kbenoit](https://github.com/kbenoit), [&#x0040;kieran-mace](https://github.com/kieran-mace), [&#x0040;kleinlennart](https://github.com/kleinlennart), [&#x0040;larry77](https://github.com/larry77), [&#x0040;lindbrook](https://github.com/lindbrook), [&#x0040;maciekbanas](https://github.com/maciekbanas), [&#x0040;mark-andrews](https://github.com/mark-andrews), [&#x0040;Marwolaeth](https://github.com/Marwolaeth), [&#x0040;mattschaelling](https://github.com/mattschaelling), [&#x0040;maurolepore](https://github.com/maurolepore), [&#x0040;michael-dewar](https://github.com/michael-dewar), [&#x0040;michaelgrund](https://github.com/michaelgrund), [&#x0040;mladencucak](https://github.com/mladencucak), [&#x0040;mladencucakSYN](https://github.com/mladencucakSYN), [&#x0040;moodymudskipper](https://github.com/moodymudskipper), [&#x0040;mrembert](https://github.com/mrembert), [&#x0040;natashanath](https://github.com/natashanath), [&#x0040;noslouch](https://github.com/noslouch), [&#x0040;pedrobtz](https://github.com/pedrobtz), [&#x0040;prasven](https://github.com/prasven), [&#x0040;ries9112](https://github.com/ries9112), [&#x0040;s-spavound](https://github.com/s-spavound), [&#x0040;schloerke](https://github.com/schloerke), [&#x0040;schmidb](https://github.com/schmidb), [&#x0040;scjohannes](https://github.com/scjohannes), [&#x0040;seawavevan](https://github.com/seawavevan), [&#x0040;simonpcouch](https://github.com/simonpcouch), [&#x0040;smach](https://github.com/smach), [&#x0040;sree1658](https://github.com/sree1658), [&#x0040;stefanlinner](https://github.com/stefanlinner), [&#x0040;szzhou4](https://github.com/szzhou4), [&#x0040;t-kalinowski](https://github.com/t-kalinowski), [&#x0040;trafficfan](https://github.com/trafficfan), [&#x0040;Vinnish-A](https://github.com/Vinnish-A), [&#x0040;vorpalvorpal](https://github.com/vorpalvorpal), [&#x0040;walkerke](https://github.com/walkerke), [&#x0040;wch](https://github.com/wch), and [&#x0040;WickM](https://github.com/WickM).

0 commit comments

Comments
 (0)