BetterTogether: run several optimizers in sequence
Source:R/teleprompter-better-together.R
BetterTogether.RdBetterTogether() chains optimizers. A strategy string such as
"p -> g -> p" runs the optimizer named p, then g on its result, then
p again. Every intermediate program is scored on a validation set and the
best one is returned. It mirrors DSPy's BetterTogether.
Usage
BetterTogether(
metric = NULL,
optimizers = list(),
...,
metric_threshold = NULL,
max_errors = 5L,
default_strategy = "p",
valset_ratio = 0.1,
shuffle_trainset_between_steps = TRUE,
seed = NULL,
verbose = TRUE
)Arguments
- metric
A metric function (required) used to score every candidate on the validation set, such as
metric_exact_match(field = "answer").- optimizers
Named list of optimizer objects, such as
list(p = BootstrapFewShotWithRandomSearch(metric = metric)).- ...
Further named optimizer objects, combined with
optimizers.- metric_threshold
Accepted for consistency with the other optimizers (see
Teleprompter());BetterTogether()does not use it.- max_errors
Does not stop the run; set
max_errorson each wrapped optimizer instead.- default_strategy
Strategy used when
compile()gets nostrategy(default"p"): optimizer names joined by->.- valset_ratio
Share of
trainsetheld out for validation when novalsetis given (default0.1).0skips validation.- shuffle_trainset_between_steps
Whether to shuffle the training rows before each step (default
TRUE).- seed
Optional seed for the validation split and the shuffles.
- verbose
Whether to print progress messages (default
TRUE).
Value
A BetterTogether object to pass to compile().
Details
Name the optimizers in optimizers or as named arguments in ...; the
strategy refers to those names. Without any, BetterTogether() uses
p = BootstrapFewShotWithRandomSearch(metric = metric).
compile() accepts extra arguments for this optimizer: strategy
(overrides default_strategy), optimizer_compile_args (a list, named by
optimizer, of further arguments for that optimizer's compile() call), and
valset_ratio, shuffle_trainset_between_steps and seed, which override
the values stored here.
The validation set is valset when given. Otherwise floor(valset_ratio * nrow(trainset)) rows are held out, which is none for fewer than 10 rows at
the default ratio. Each step receives the remaining training rows and the
validation set. The original program is scored as well, so the result can
be the unchanged program. Without a validation set, the last program in the
strategy is returned. A step that fails raises a warning and ends the run
with the best program so far; the default p optimizer fails this way when
there is no validation set.
The scored candidates are stored in
optimization_result(compiled)$extensions$better_together$candidate_programs.
Examples
metric <- metric_exact_match(field = "answer")
tp <- BetterTogether(
metric = metric,
p = BootstrapFewShotWithRandomSearch(metric = metric),
g = GEPA(metric = metric, population_size = 4L, generations = 2L),
default_strategy = "p -> g -> p"
)
tp
#>
#> ── BetterTogether Teleprompter
#> Default strategy: "p -> g -> p"
#> Validation split: 0.1
#> Optimizers: p and g
if (FALSE) { # \dontrun{
qa <- module(signature("question -> answer"))
trainset <- data.frame(
question = c("Capital of France?", "Capital of Peru?", "Capital of Chad?"),
answer = c("Paris", "Lima", "N'Djamena")
)
valset <- data.frame(
question = c("Capital of Japan?", "Capital of Kenya?"),
answer = c("Tokyo", "Nairobi")
)
llm <- ellmer::chat_openai(model = "gpt-6-luna")
compiled <- compile(qa, tp, trainset, valset = valset, .llm = llm)
optimization_result(compiled)$extensions$better_together$candidate_programs
# Try another strategy without building a new optimizer
compiled_g <- compile(
qa,
tp,
trainset,
valset = valset,
.llm = llm,
strategy = "g"
)
} # }