textopt@0.1.0
Minor Changes
-
ca8a541: A harvested rollout cannot end the demo block it is stored in.
Demo blocks are delimited by
<demo>, and the values inside them were written
raw. A system that quotes its own prompt back produces the one output that
breaks: a rollout worth keeping whose text carries</demo>, which closes the
block early and leaves the rest of it as loose text.parseDemosthen returned
a demo that was not the one stored, and SIMBA reparses and rewrites its demo
components at every step, so the loss compounded over a run instead of showing
up once.<demo>,<input>and<output>are now escaped in serialized demo values and
unescaped on the way back, so a demo containing a demo round trips as itself.
The escape is escaped first, so a value that already reads<demo>survives
too. Demo blocks written by earlier versions still parse; blocks whose values
contain those tags will render them escaped from now on, which changes the text
a component holds and so the candidates a run compares.A custom
renderDemois responsible for its own escaping: the library cannot
know which part of what a renderer emits is the delimiter it meant to write. -
b25abd2: SIMBA's advice proposer sees what each component already says.
buildAdvicePromptnamed the components it wanted advice for but never showed
their text, while the advice it produces is appended to that text rather than
replacing it. A proposer that cannot read what it is appending to writes blind:
it restates guidance the component already carries, and it cannot correct
guidance that is wrong, since contradicting a line it never saw is not something
it can choose to do. SIMBA's reference implementation passes the current
instructions for exactly this reason.AdvicePromptArgsnow carriescurrent, a map from component name to what that
component holds, and the prompt renders each as a<component name="…">… </component>block followed by the instruction not to restate advice already
present. A customAdvicePromptBuilderreceives the extra field and may ignore
it; anything constructingAdvicePromptArgsby hand, or parsing the built
prompt's component list, has to be updated. -
fec8f51: Ceilings hold where a run actually spends, checkpoints describe whole rounds,
and an instance id names one row.Harvesting takes a
maxCostUsdof its own.harvestRolloutsand
harvestFewShotExamplescheck it between batches, and MIPRO and bootstrapped
few-shot search pass what is left of the run's ceiling into each pass. MIPRO
also stops building demo sets once the ceiling is reached. A demo menu is many
evaluations on a separate evaluator, so a ceiling it never consulted bounded
only the trial loop that followed it, and a run could spend its whole allowance
choosing demos and never score a candidate.OPRO and MIPRO checkpoint after the sweep their cadence schedules, not before
it. A snapshot names a round, and a resumed run schedules its next sweep an
interval past the round the snapshot names, so a checkpoint taken first
described half a round and the resumed run skipped that sweep entirely. A MIPRO
trial whose rollouts all failed transiently now checkpoints and runs its cadence
like any other: the rollouts were bought and the counter moved either way.Bootstrapped few-shot search reads every sweep it dispatched before it leaves a
wave. Stopping on the first failure abandoned the sweeps behind it, which went
on calling the adapter, and spending, after the caller had been handed the
error.An adapter reading of
NaN,Infinityor a negative token count is refused
where it enters. Folded into the totals it silently disabledmaxCostUsd:
every later comparison against aNaNcost is false, so the ceiling stopped
holding without saying so.createJudgerefuses ascalethat is zero or
negative for the same reason — every grade is divided by it.Instance ids fall back to the row's position for a datum a content hash cannot
read, not only for one that will not serialize. AMap, aSetand a class
instance holding its state privately all serialize to{}, so distinct rows
shared an id and were served each other's cached scores. The six optimizers now
share onedefaultInstanceIdrather than six copies of it.A file cache terminates a record its previous process left half-written. The
truncated record was already lost; appending onto the line it left open lost the
next one too.The LangChain adapter counts a provider that reports tokens in both shapes once.
Integrations that fillllmOutput.tokenUsageandusage_metadatafor the same
call were billed twice, and the message-level shape now wins with the legacy
total as a fallback. Usage a scorer reports is no longer dropped when the run
itself counted none — the guard consulted only the callback total, so a judge's
spend went unreported. -
b25abd2: A demonstration lands in a component without erasing what else it says.
SIMBA's
appendDemorebuilt a demo component from its demos alone, so any text
the component held that was not a demo block was gone the first time a rollout
was harvested into it — including the adviceappendRulehad just written
there. The two mutations could not share a component, which is why
instructionComponentsdefaulted to the componentsdemoComponentsdid not
name, and why a candidate with a single component got one mutation instead of
two. SIMBA's reference implementation appends demos and instructions to the same
predictor; a component is the closest thing this library has to one.replaceDemosrewrites the demo blocks in a text and leaves the rest of it
alone, and bothappendDemoand the loop's demo-dropping now go through it. A
component named indemoComponentscan hold instructions too, and the default
instructionComponentsfalls back to every component when every component holds
demos, rather than leavingappendRulewith nowhere to write and throwing.Runs where demo and instruction components were already disjoint are unaffected
except that a demo block now keeps its position in the text rather than
replacing it. Runs where they overlapped were losing text and are not
comparable to their old results. -
155cb19: First public release: GEPA, SIMBA, OPRO, MIPRO, bootstrapped few-shot search,
and random search behind a shared optimizer interface, with a LangChain
adapter. The shared substrate handles budgets in rollouts, dollars and wall
clock, transient-failure retry, durable caching, checkpoints and resume for
every optimizer, a model-graded judge, a multi-module pipeline adapter, and
compare()for deciding between two runs with a p-value rather than a hunch.
Every run reports throughreporters: any number of observers, each with an
onEventcalled synchronously and aflushawaited as the run ends, including
when it ends by throwing. Each search emits its own event union, but every one
of them announces an accepted candidate with the text that scored and its row
over the validation set, and reports the held-out sweep per instance rather
than as a lone mean. A reporter that reads only those narrows with
isCandidateAcceptedandisRunFinishedand drops into any optimizer.The Vercel AI SDK, Braintrust and LangSmith packages are still in beta and ship
from the repository only. -
38dfb12: A ceiling bounds what it was told it bounds, and a reading it cannot be checked
against is refused wherever it enters.The held-out sweep is reported apart from the search.
maxCostUsdand
maxWallClockMsbound the search loop, and atestSetis measured once after
that loop has already stopped — charging it would let the size of a held-out set
decide which candidate wins.metricCallsalready excluded those rollouts and
reported them astestMetricCalls;usagedid not, so a run that honoured a $4
ceiling and then swept a hundred held-out rows reported spending $104 and looked
like it had overrun. Their dollars are nowtestUsage, alongside
testMetricCalls, in all six optimizers. SIMBA and bootstrapped few-shot search
counttestMetricCallsthe way the other four already did — the rollouts the
sweep bought, not the rows it was handed, which differ once one is retried or
served from the cache.EvaluatorgainsunchargedUsage()for the same split, andusage()now
returns only what the search spent. Nothing bounds the held-out sweep, so what a
run costs end to end isusageplustestUsage: budget for atestSetthe way
you would budget for one full validation sweep.A usage reading that is not a non-negative finite number is refused whether it is
a number or not. The guard only fired onNaN,Infinityand negatives, so a
JavaScript adapter reportingcostUsdas a string concatenated onto the totals
and left the ceiling checked against text. Resumed usage and usage absorbed from
a harvest are checked too: a hand-edited checkpoint could poison the totals with
nothing to say it had.The LangChain adapter reads the legacy total again when the message-level shape
counts nothing. Preferringusage_metadataon its presence rather than on what
it carries meant an integration attaching a zeroed one — as some do to
intermediate generations — reported zero tokens for a call whose real total was
sitting inllmOutput.tokenUsageall along. -
2606e36: Rollout accounting survives a resume, and the numbers a run reports name the
candidate it returns.maxCostUsdis a ceiling on the run rather than on the segment: every snapshot
now carries the usage already spent, and a resumed run folds it back in instead
of restarting its token and dollar totals at zero. Harvesting is part of that
total —harvestRolloutsandharvestFewShotExamplesreport theusagetheir
own evaluator spent, and MIPRO and bootstrapped few-shot search absorb it
throughEvaluator.absorbUsage, so the pass that collects demos is no longer
invisible to a cost ceiling that is supposed to bound it.SIMBA keys a minibatch by dataset row instead of by position within the batch.
Two steps drawing different rows shared the ids0..n-1, so a candidate that
recurred across steps could be served a cached score another instance had
measured. Its defaultinstanceIdhashes the datum, as bootstrapped few-shot
search's now does and as the other four optimizers already did. This is the
breaking half of the release: a seeded run that changed nothing now reaches
different candidates, because a score measured on another row no longer decides
one.bench/results/latest.jsonmoves with it.OPRO sweeps the held-out set with the candidate it returns. A run that screens
on ascoringSetSizesubset can end on an incumbent the closing full sweep
never confirmed, andtestScoredescribed that unreturned candidate rather than
bestCandidate.Candidate ids continue across a resume in OPRO, MIPRO, bootstrapped few-shot
search and random search. Reporters key rows bycandidateId, and a counter
that restarted at zero made a resumed run overwrite the run it continued.SAMPLING_POOLis exported fromtextopt/testing, which the README had
documented but the module did not export. -
b2be4d6: A comparison reports what each entrant was given, and claims only what its
seeds actually put to the test.compare()reports the work a run got for free. Two entrants held to the same
maxMetricCallsdo not do the same amount of work: one that revisits candidates
it has already scored is served from the cache, and one that never revisits pays
for every rollout, so ranking them onmetricCallsalone credits the difference
to the search. Every run now carriescacheHitsandreflectionCalls, summarized
asmeanCacheHitsandmeanReflectionCalls, and reflection calls are counted
because no metric budget covers them — an entrant can be cheap on rollouts and
expensive on proposals.OptimizerResultcarries both:cacheHitson every
optimizer,reflectionCallson the five that propose text. Anything implementing
that interface by hand has to reportcacheHits.A p-value is withheld where the seeds never earned one.
compare()reported
pValueVsWinnerfor every non-winner, including entrants whose margin over the
winner was identical at every seed — a deterministic search against a
deterministic model, or one whose seed reaches nothing that varies. A sign-flip
test over n seeds answers a question about n independent trials; n copies of one
realization is a single trial reported as n, and the p-value it produces goes as
low as 2^-n while resting on nothing. Those comparisons now report no p-value at
all, anddistinctScoressays how many distinct outcomes an entrant actually
had, so a row of identical scores is visible rather than inferred. A margin of
exactly zero at every seed is unaffected: p = 1 claims nothing and is honest.pValueVsWinnerHolmcorrects for the family the raw p-value is read against.
Comparing six entrants against a winner runs five tests, and the smallest of five
is smaller than one test's worth of evidence. Holm-Bonferroni step-down adjusts
each surviving comparison against the whole family — including the slots held by
comparisons withheld above, which are still members of it.Exact enumeration now reaches twenty seeds rather than sixteen.
signFlipPValue
builds the reachable sums by doubling instead of re-summing each of the 2^n sign
masks, which costs O(2^n) rather than O(2^n · n) — about four times faster at
n = 20, 16ms against 63ms. Comparisons between sixteen and twenty seeds get the
exact p-value where they previously fell back to the normal approximation.Random search starts a round whenever one more sweep is affordable. It required
the whole round up front — every variant proposed and scored — which stranded up
tovariants * |validationSet| - 1rollouts unspent at the end of a run, around
a fifth of a typical budget at the default settings. A variant needs only its own
sweep to be compared against the incumbent, and the round already truncates to
what the remainder can fund whole sweeps for. The cost is bounded and paid once:
the final round proposes up tovariants - 1texts it never scores. Runs at a
fixedmaxMetricCallswill evaluate more variants than before and can select a
different candidate.
Patch Changes
- 3336175:
harvestRolloutsruns a candidate over data and returns the rollouts the
metric rewarded, without the demo ceilingharvestFewShotExamplesapplies, and
toTrainingJsonlserializes them as chat-messages JSONL for distilling a run
into a smaller model.harvestFewShotExampleskeeps its behaviour and now
calls the same primitive; Distilling a run covers which
data to sweep and how much of the optimized candidate to leave in the training
input. - 7809e1d: SIMBA and bootstrapped few-shot search take a
concurrencyoption, and OPRO
and random search now apply theirs to evaluation as well as to proposals: a
round's screens or sweeps run together, SIMBA overlaps the candidates a step
built and the finalist sweeps, and a bootstrap candidate's sweep overlaps the
harvest behind it. Every fan-out draws its random stream and prices its
schedule before dispatching, and commits in the order the search proposed, so a
seeded run reaches the same candidates and spends the same rollouts at any
concurrency. Defaults stay at one evaluation at a time.