This page shows how dsprrr fits with tidymodels today: score module
output with yardstick, and build grids for optimize_grid()
with dials. It also explains what the parsnip model
llm_predict() does, and why it can’t be fitted yet.
Score predictions with yardstick
Run the module over a data frame with run_dataset(),
which keeps every column and adds a result list column.
Turn the predicted field into a factor with the truth’s levels, and any
yardstick metric works:
library(yardstick)
reviews <- tibble::tibble(
text = c("Arrived broken.", "Works great.", "It's a chair."),
sentiment = factor(c("negative", "positive", "neutral"))
)
classify <- module(
signature("text -> sentiment: enum('negative', 'neutral', 'positive')")
)
scored <- run_dataset(
classify,
reviews,
.llm = ellmer::chat_openai(model = "gpt-6-luna")
)
scored$.pred_class <- factor(
vapply(
scored$result,
\(r) if (is.list(r)) r$sentiment else NA_character_,
character(1)
),
levels = levels(scored$sentiment)
)
accuracy(scored, truth = sentiment, estimate = .pred_class)
conf_mat(scored, truth = sentiment, estimate = .pred_class)A row whose call failed holds NA in result,
hence the is.list() check. Setting levels
matters: yardstick refuses a prediction factor whose levels differ from
the truth’s, which happens whenever some class is never predicted.
Build grids with dials
temperature(), top_p() and
reasoning_effort() are dials parameter objects.
temperature() and top_p() default to the range
0 to 1:
temperature()
#> Temperature (quantitative)
#> Range: [0, 1]
reasoning_effort()
#> Reasoning Effort (qualitative)
#> 3 possible values include:
#> 'low', 'medium', and 'high'
dials::grid_regular(temperature(), levels = 3)
#> # A tibble: 3 × 1
#> temperature
#> <dbl>
#> 1 0
#> 2 0.5
#> 3 1optimize_grid() takes such a grid directly, or a
dials::parameters() set to build one from. It tries each
row on the data and keeps the best settings in the module:
optimize_grid(
classify,
data = reviews,
metric = metric_exact_match(field = "sentiment"),
grid = dials::grid_regular(temperature(), levels = 3),
# gpt-6-luna accepts temperature only with reasoning turned off
.llm = ellmer::chat_openai(
model = "gpt-6-luna",
params = ellmer::params(reasoning_effort = "none")
)
)
module_trials(classify)module_parameters(classify) suggests a parameter set for
a module. To tune how much a reasoning model thinks, use a
reasoning_effort() grid instead; see Models, providers and
streaming.
The parsnip model
When parsnip is loaded, dsprrr registers a model type,
llm_predict(), with the engine "dsprrr" for
classification and regression:
library(parsnip)
spec <- llm_predict(
mode = "classification",
signature = "text -> sentiment: enum('negative', 'neutral', 'positive')",
temperature = 0
) |>
set_engine("dsprrr")
spec
#>
#> ── LLM Predict Model Specification
#> Mode: classification
#> Engine: dsprrrFitting this model does not learn from the training data. The
engine’s fit step builds a zero-shot module from signature
(or, without one, from the predictor names and the outcome’s distinct
values, such as
text -> output: enum('negative', 'positive')), puts
temperature and top_p in the module’s config,
and records the predictor names. The training rows are not used as
examples. predict() then runs the module on
new_data with the default Chat, so call
set_default_chat() first. It returns
.pred_class for classification, as a factor with only the
levels that were predicted, or .pred for regression.
With parsnip 1.6.0, fit() and fit_xy() on
this specification currently stop with “no applicable method for
‘filter’ applied to an object of class”NULL”“, because the engine
registers no predictor encoding. The same failure stops
tune::tune_grid() and workflows, which fit models through
these functions. Until that is fixed, the module and
run_dataset() approach at the top of this page gives the
same zero-shot predictions.
For evaluation with graded or repeated samples, see Evaluate with vitals.