dsprrr lets you write LLM features in R as small programs instead of prompt strings. You declare a task’s inputs and typed outputs; dsprrr builds the prompt, calls the model, and returns an R list with the types you asked for. Once you have labeled examples, you can score the program with a metric and let an optimizer tune its instructions and few-shot examples against that score.
The design follows DSPy. Model calls go through ellmer, so any provider ellmer supports works. dsprrr is experimental and not yet on CRAN; the API may still change.
Installation
# install.packages("pak")
pak::pak("JamesHWade/dsprrr")You also need credentials for a model provider, for example OPENAI_API_KEY in your .Renviron.
Example
A signature names the inputs and outputs of a task. module() turns it into something you can run.
library(dsprrr)
chat <- ellmer::chat_openai(model = "gpt-6-luna")
analyzer <- module(signature(
"review -> sentiment: enum('positive', 'negative', 'neutral'), stars: int, summary: string"
))
result <- run(
analyzer,
review = "I've been using this blender for 6 months now. It's incredibly powerful and easy to clean. The only downside is it's quite loud. Overall, I'm very happy with it.",
.llm = chat
)
str(result)
#> List of 3
#> $ sentiment: chr "positive"
#> $ stars : int 4
#> $ summary : chr "Powerful and easy-to-clean blender, but a bit loud."stars comes back as an integer and sentiment is always one of the three labels, so the result can go straight into a data frame. The output above was recorded from a real call to gpt-4.1 in Tutorial 3; gpt-6-luna may word the summary differently.
How the pieces fit
Signatures
Declare the task. Inputs on the left of the arrow, typed outputs on the right, and optional instructions. The prompt is generated from this.
sig <- signature(
"ticket -> urgency: enum('low', 'high'), team: string",
instructions = "Route the support ticket."
)Modules
Choose how it runs. The same signature can be answered in one call, with step-by-step reasoning first, or by a loop that calls R functions as tools.
route <- module(sig) # one call
route <- chain_of_thought(sig) # reason, then answer
lookup <- ellmer::tool(
function(query) "Duplicate charges are refunded by billing.",
description = "Look up support policy",
arguments = list(query = ellmer::type_string())
)
route <- react(sig, tools = list(lookup)) # call tools as neededMetrics
Measure it. Run the module over labeled rows and score each prediction. You get the mean score, per-row scores, and the predictions to inspect.
tickets <- tibble::tibble(
ticket = c(
"I was charged twice for my order",
"How do I change my profile photo?",
"The app deletes my files when I sync",
"Can I get last year's invoice?"
),
urgency = c("high", "low", "high", "low"),
team = c("billing", "account", "engineering", "billing")
)
scores <- evaluate(
route,
tickets,
metric = metric_exact_match(field = "urgency"),
.llm = chat
)
scores$mean_scoreOptimizers
Improve it with data. compile() returns a new module whose instructions or examples were chosen to raise the metric on your training rows. Check the result on rows it did not see.
optimizer <- BootstrapFewShot(
metric = metric_exact_match(field = "urgency"),
max_bootstrapped_demos = 4L
)
split <- split_dataset(tickets, prop = 0.5, seed = 1)
optimized <- route |>
compile(optimizer, trainset = split$train, .llm = chat)
evaluate(
optimized,
split$val,
metric = metric_exact_match(field = "urgency"),
.llm = chat
)Where to go next
| To | Read |
|---|---|
| Learn the basics one step at a time | Tutorial 1: Your first LLM call |
| Pick a module for your task | Choose a module type |
| Measure quality with metrics | Metrics and evaluation |
| Improve a module with labeled data | Compile and optimize |
| Decide which optimizer to use | Choose an optimizer |
| Translate DSPy code to R | dsprrr for DSPy users |
| Look up a function | Reference |
Some features are experimental: rlm_module() lets a model explore a large R object by writing code (How RLM works), flex() lets GEPA rewrite a whole program (Flex), and Omni() and the agentic harnesses compose optimizers (Omni, agentic harnesses).
dsprrr is inspired by DSPy from Stanford NLP. It builds on ellmer for model access and bridges to vitals for evaluation.