textopt 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -25
- package/dist/bootstrap-search/index.cjs +159 -73
- package/dist/bootstrap-search/index.d.cts +32 -10
- package/dist/bootstrap-search/index.d.mts +32 -10
- package/dist/bootstrap-search/index.mjs +150 -66
- package/dist/demos-9v5ts7F3.cjs +244 -0
- package/dist/{demos-B0pVQjYC.d.mts → demos-ASsSXYXA.d.mts} +10 -3
- package/dist/demos-Brobjfuc.mjs +215 -0
- package/dist/{demos-BTuzFNsp.d.cts → demos-ByaLZy-Z.d.cts} +10 -3
- package/dist/file-cache.cjs +27 -8
- package/dist/file-cache.d.cts +13 -0
- package/dist/file-cache.d.mts +13 -0
- package/dist/file-cache.mjs +27 -8
- package/dist/gepa/index.cjs +128 -80
- package/dist/gepa/index.d.cts +15 -7
- package/dist/gepa/index.d.mts +15 -7
- package/dist/gepa/index.mjs +101 -55
- package/dist/index.cjs +157 -30
- package/dist/index.d.cts +177 -7
- package/dist/index.d.mts +177 -7
- package/dist/index.mjs +139 -18
- package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
- package/dist/math-Dqme4rYz.mjs +123 -0
- package/dist/mipro/index.cjs +104 -70
- package/dist/mipro/index.d.cts +17 -14
- package/dist/mipro/index.d.mts +17 -14
- package/dist/mipro/index.mjs +90 -58
- package/dist/opro/index.cjs +136 -51
- package/dist/opro/index.d.cts +17 -9
- package/dist/opro/index.d.mts +17 -9
- package/dist/opro/index.mjs +121 -38
- package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-4Zv-Zt2t.d.cts} +90 -5
- package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ds5mzYjz.d.mts} +90 -5
- package/dist/random-search/index.cjs +99 -49
- package/dist/random-search/index.d.cts +15 -13
- package/dist/random-search/index.d.mts +15 -13
- package/dist/random-search/index.mjs +89 -41
- package/dist/{reflection-Cr_upzU0.d.mts → reflection-CMezGu6u.d.mts} +38 -14
- package/dist/{reflection-CQToe-5B.d.cts → reflection-D0A7eahD.d.cts} +38 -14
- package/dist/reporting-bq007_2z.d.cts +294 -0
- package/dist/reporting-bq007_2z.d.mts +294 -0
- package/dist/simba/index.cjs +216 -83
- package/dist/simba/index.d.cts +53 -13
- package/dist/simba/index.d.mts +53 -13
- package/dist/simba/index.mjs +206 -75
- package/dist/testing.cjs +1 -0
- package/dist/testing.d.cts +5 -3
- package/dist/testing.d.mts +5 -3
- package/dist/testing.mjs +1 -1
- package/dist/{evaluation-OZOp6TB7.cjs → warnings-CWRJF-jA.cjs} +228 -5
- package/dist/{evaluation-BV0nSZVx.mjs → warnings-OxvDi9kN.mjs} +175 -6
- package/docs/adapters.md +169 -0
- package/docs/benchmark.md +90 -0
- package/docs/data-prep.md +113 -0
- package/docs/distillation.md +128 -0
- package/docs/evaluation.md +87 -0
- package/docs/metric-preflight.md +132 -0
- package/docs/optimizers.md +293 -0
- package/docs/tuning.md +130 -0
- package/package.json +6 -4
- package/dist/demos-B9BJiNKz.cjs +0 -143
- package/dist/demos-Degx6UmP.mjs +0 -126
- package/dist/math-DhrDmpFS.mjs +0 -78
- package/dist/types-CWv4IQFF.d.cts +0 -129
- package/dist/types-CWv4IQFF.d.mts +0 -129
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Core interfaces and optimizers for textopt.
|
|
|
4
4
|
|
|
5
5
|
This package has no runtime dependencies. For an overview of the algorithms and guidance on choosing one, see the [project README](https://github.com/ctdio/textopt#readme).
|
|
6
6
|
|
|
7
|
+
The long-form guides ship with the package, under `docs/`, so an installed copy documents the version installed rather than whatever `main` has become. Agent-facing guidance lives there and in the doc comments on the API itself.
|
|
8
|
+
|
|
7
9
|
## Entry points
|
|
8
10
|
|
|
9
11
|
| Import | Contains |
|
|
@@ -23,7 +25,7 @@ This package has no runtime dependencies. For an overview of the algorithms and
|
|
|
23
25
|
```ts
|
|
24
26
|
import {
|
|
25
27
|
assertResumable,
|
|
26
|
-
|
|
28
|
+
harvestFewShotExamples,
|
|
27
29
|
buildJudgePrompt,
|
|
28
30
|
compare,
|
|
29
31
|
componentNames,
|
|
@@ -32,11 +34,13 @@ import {
|
|
|
32
34
|
createJudge,
|
|
33
35
|
createMemoryCache,
|
|
34
36
|
formatDemos,
|
|
37
|
+
harvestRollouts,
|
|
35
38
|
mapWithConcurrency,
|
|
36
39
|
parseDemos,
|
|
37
40
|
parseProposedText,
|
|
38
41
|
priceUsage,
|
|
39
42
|
runFingerprint,
|
|
43
|
+
toTrainingJsonl,
|
|
40
44
|
} from "textopt";
|
|
41
45
|
import type {
|
|
42
46
|
Adapter,
|
|
@@ -93,9 +97,9 @@ evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Outp
|
|
|
93
97
|
|
|
94
98
|
**`transient`** marks scores caused by infrastructure failures such as rate limits, 5xx responses, or network errors. Transient scores are not cached.
|
|
95
99
|
|
|
96
|
-
**`Optimizer<Stop extends string>`** defines `optimize(task: OptimizerTask) => Promise<OptimizerResult>`. `OptimizerTask` contains the shared run inputs: `seedCandidate`, `trainingSet`, `validationSet`, `testSet`, `adapter`, `maxMetricCalls`, `maxCostUsd`, `maxWallClockMs`, `cacheNamespace`, `retry`, and `signal`. `OptimizerResult` contains `bestCandidate`, `bestScore`, `bestOutputs`, `metricCalls`, `usage`, `testScore`, `testMetricCalls`, and `stopReason`. Optimizer-specific task and result types extend these interfaces.
|
|
100
|
+
**`Optimizer<Stop extends string>`** defines `optimize(task: OptimizerTask) => Promise<OptimizerResult>`. `OptimizerTask` contains the shared run inputs: `seedCandidate`, `trainingSet`, `validationSet`, `testSet`, `adapter`, `maxMetricCalls`, `maxCostUsd`, `maxWallClockMs`, `cacheNamespace`, `retry`, and `signal`. `OptimizerResult` contains `bestCandidate`, `bestScore`, `bestOutputs`, `metricCalls`, `usage`, `testScore`, `testMetricCalls`, `testUsage`, `warnings`, and `stopReason`. Optimizer-specific task and result types extend these interfaces.
|
|
97
101
|
|
|
98
|
-
**`maxCostUsd`** and **`maxWallClockMs`** are checked between evaluations, so a run overruns by
|
|
102
|
+
**`maxCostUsd`** and **`maxWallClockMs`** are checked between evaluations, so a run overruns by whatever it had in flight when the ceiling was reached — one evaluation at the default concurrency, and up to `concurrency` of them above it. Neither bounds the held-out sweep, which runs once the search has already stopped: it is reported apart from the search as `testMetricCalls` and `testUsage`, and has to be budgeted for separately. Neither follows from `maxMetricCalls`: reflective search grows the text it optimizes, so late rollouts cost more than early ones, and a run behind a rate limit spends almost nothing while taking as long as the provider makes it take.
|
|
99
103
|
|
|
100
104
|
**`cacheNamespace`** scopes every cache key to the system the rollouts were measured under — model id, decoding settings, scorer version. Change it whenever anything outside the candidate text changes.
|
|
101
105
|
|
|
@@ -103,7 +107,11 @@ evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Outp
|
|
|
103
107
|
|
|
104
108
|
**`UsageTotals`** (`inputTokens`, `outputTokens`, `totalTokens`, `costUsd`, `rollouts`) is summed from the `RolloutUsage` entries an adapter reports. Zero throughout when the adapter reports none.
|
|
105
109
|
|
|
106
|
-
**`testSet`** is excluded from search and evaluated once against the winner. Because candidates are selected on `validationSet`, `bestScore` may be fitted to it. `testScore` measures held-out performance. Test rollouts are reported as `testMetricCalls`
|
|
110
|
+
**`testSet`** is excluded from search and evaluated once against the winner. Because candidates are selected on `validationSet`, `bestScore` may be fitted to it. `testScore` measures held-out performance. Test rollouts are reported as `testMetricCalls` costing `testUsage`, outside `maxMetricCalls` and `maxCostUsd` both.
|
|
111
|
+
|
|
112
|
+
**`validationSet`** defaults to `trainingSet`, and a run that took that default reports a `validationSetReusesTraining` warning: the search selected candidates on the instances reflection read. Pass `validationSet: "reuseTraining"` to accept it by name and silence the warning.
|
|
113
|
+
|
|
114
|
+
**`warnings`** is a `RunWarning[]` of what a run cannot say about itself from its own numbers — selection that reused the training instances, a seed the metric scored identically on every instance. Never fatal, always present (empty when there is nothing to say), and repeated on the `finish` event so a reporter records them next to the score.
|
|
107
115
|
|
|
108
116
|
**`TextModel`** is the provider-independent interface `({ prompt, signal }) => Promise<string>`.
|
|
109
117
|
|
|
@@ -117,15 +125,23 @@ For Redis, SQLite, or file-backed caching, implement **`EvaluationCache`** with
|
|
|
117
125
|
|
|
118
126
|
**`componentNames(candidate)`** returns `Object.keys(candidate)` while preserving the component-name union.
|
|
119
127
|
|
|
120
|
-
**`createEvaluator({ adapter, budget, cache, cacheNamespace, retry, trackOutputs, onEvaluation, signal, cacheHits })`** handles adapter calls, caching, budget accounting, transient scores, and evaluation events. `evaluate` returns a `ScoredBatch`. `evaluateTraced` returns an `EvaluationBatch`, or `null` when the remaining budget cannot cover the batch. A batch that exceeds the charged budget throws `BudgetExhausted`. All included optimizers use this evaluator.
|
|
128
|
+
**`createEvaluator({ adapter, budget, cache, cacheNamespace, retry, trackOutputs, onEvaluation, signal, cacheHits, usage })`** handles adapter calls, caching, budget accounting, transient scores, and evaluation events. `cacheHits` and `usage` seed the counters from a checkpoint, so a resumed run reports totals rather than deltas. `usage()` covers the charged rollouts a ceiling is checked against; `unchargedUsage()` covers what `charge: false` bought. `evaluate` returns a `ScoredBatch`. `evaluateTraced` returns an `EvaluationBatch`, or `null` when the remaining budget cannot cover the batch. A batch that exceeds the charged budget throws `BudgetExhausted`. All included optimizers use this evaluator.
|
|
129
|
+
|
|
130
|
+
**`harvestRollouts({ adapter, candidate, data, minScore, maxRollouts, batchSize, maxMetricCalls, maxCostUsd, rng, signal })`** runs a candidate over `data` and returns the `Rollout`s the metric rewarded, alongside `metricCalls` and `attempted`. Omit `minScore` to keep any rollout scoring above zero; omit `maxRollouts` to sweep the whole pool. It carries its own budget and does not use the score cache, because it needs the outputs a cache hit cannot return. `maxCostUsd` bounds its dollars, checked between batches — a caller bounding spend cannot bound this pass from outside, since it runs on its own evaluator. Sweeping a validation set is the mistake to avoid — see [Distilling a run](./docs/distillation.md).
|
|
131
|
+
|
|
132
|
+
**`toTrainingJsonl({ rollouts, render })`** serializes harvested rollouts as one chat-messages example per line. `render` turns a rollout into `{ messages }` or returns `null` to skip it, and decides how much of the optimized candidate stays in the training input. Returns the text; writing it is the caller's job.
|
|
121
133
|
|
|
122
|
-
**`
|
|
134
|
+
**`harvestFewShotExamples({ adapter, candidate, trainingSet, minScore, maxDemos, batchSize, maxMetricCalls, maxCostUsd, rng, renderDemo, signal })`** evaluates a candidate on `trainingSet` and keeps the rollouts the metric rewarded. Omit `minScore` to keep any rollout scoring above zero, as MIPROv2 does without a `metric_threshold`; pass a number to require at least that score. It returns the selected `demos`, a formatted `block`, and the metric calls and usage it spent. It does not use the score cache because it needs rollout outputs.
|
|
123
135
|
|
|
124
136
|
**`formatDemos(demos, { render })`** and **`parseDemos(text)`** write and read the `<demo>`, `<input>`, and `<output>` block format.
|
|
125
137
|
|
|
126
138
|
**`parseProposedText(text)`** extracts a proposal from a reflection response, including responses with fenced blocks or surrounding commentary.
|
|
127
139
|
|
|
128
|
-
**`createJudge({ model, criteria, scale = 5, renderInput, renderOutput, buildPrompt })`** returns a `Judge<Datum, Output>`: `({ input, output, expected, signal }) => Promise<ScoreResult>`. Each `JudgeCriterion` is graded on a small integer scale and normalized; per-criterion values are returned as `objectiveScores` and the aggregate `score` is their mean. A criterion the judge failed to grade returns a transient score rather than a zero, so the instance is retried instead of recorded as a failure. **`buildJudgePrompt`** is the default template and implements `JudgePromptBuilder`.
|
|
140
|
+
**`createJudge({ model, criteria, scale = 5, renderInput, renderOutput, buildPrompt })`** returns a `Judge<Datum, Output>`: `({ input, output, expected, signal }) => Promise<ScoreResult>`. Each `JudgeCriterion` is graded on a small integer scale and normalized; per-criterion values are returned as `objectiveScores` and the aggregate `score` is their weighted mean. A criterion the judge failed to grade returns a transient score rather than a zero, so the instance is retried instead of recorded as a failure. **`buildJudgePrompt`** is the default template and implements `JudgePromptBuilder`.
|
|
141
|
+
|
|
142
|
+
A `JudgeCriterion` takes `weight` (default 1; 0 reports the criterion as an objective without letting it move the score) and `gate`. **`gate`** is the grade, on the judge's own scale, that a criterion must reach for the instance to score at all — below it the instance scores 0 whatever the rest said, and the per-criterion objectives still report which one failed it. A mean lets a search trade a hard requirement away: a candidate that tanks one non-negotiable criterion and aces three cosmetic ones outranks the incumbent that kept the rule. Anything you would not ship without is a gate rather than a term in the average.
|
|
143
|
+
|
|
144
|
+
When `expected` is passed, the default prompt forbids the feedback from restating it. Feedback is rewritten into a reusable instruction, so "the instruction never says to state the thirty-day refund window" is addressed to the instructions as asked and copies the answer key into the prompt.
|
|
129
145
|
|
|
130
146
|
**`compare({ entrants, seeds, concurrency = 1 })`** runs each entrant over every seed and returns a `Comparison` of `winner`, `summaries`, and `runs`. Entrants are `({ seed }) => Promise<OptimizerResult>`, so the caller builds the optimizer-specific task. Ranking is on `testScore` where a run reports one, because the validation score is the number the search selected against. Each `ComparisonSummary` carries `meanScore`, `sdScore`, `minScore`, `maxScore`, `meanMetricCalls`, `meanCostUsd`, and a paired sign-flip `pValueVsWinner`.
|
|
131
147
|
|
|
@@ -137,6 +153,18 @@ For Redis, SQLite, or file-backed caching, implement **`EvaluationCache`** with
|
|
|
137
153
|
|
|
138
154
|
**`BatchSampler<Datum>`** and **`Rng`** are type-only exports. Their default implementations are internal.
|
|
139
155
|
|
|
156
|
+
### Reporting
|
|
157
|
+
|
|
158
|
+
Every optimizer takes **`reporters`**, an array of **`Reporter<Event>`** with an optional `onEvent` and an optional `flush`. `onEvent` is called synchronously on the search's hot path, so a reporter that ships anywhere over a network buffers there and uploads in `flush`, which is awaited once as the run ends — including when it ends by throwing. A reporter that throws is warned about and skipped: observability never fails a run. Persisting a run so it can be resumed is `onCheckpoint`, which is durability and a separate concern.
|
|
159
|
+
|
|
160
|
+
Each search emits its own discriminated union, but two members are shared. **`CandidateAccepted`** carries `candidateId`, the `candidate` text, its `aggregateScore`, its `instanceScores` over the validation set, and its `outputs` under `trackBestOutputs`. **`RunFinished`** carries `bestCandidateId`, `bestScore`, `metricCalls`, and the winner's held-out `testScore`, `testInstanceScores` and `testOutputs`. In both rows, `undefined` marks an instance nothing measured — the evaluation policy skipped it, or an infrastructure failure lost it — and reporting one as a zero shows as a regression that never happened.
|
|
161
|
+
|
|
162
|
+
`candidateAccepted` fires only when the incumbent moves and a full validation sweep measured it, so `instanceScores` never means "a subset, and you work out which". SIMBA accepts on a minibatch, which is too small a sample to name a row against; it reports those in its own `candidate` event and emits `candidateAccepted` when the sweep that confirms a step winner lands.
|
|
163
|
+
|
|
164
|
+
Every optimizer reports the seed as `candidateId` 0 once its own sweep lands, so a report opens with the baseline every later candidate is read against rather than with the first improvement. A resumed run does not re-emit it. Each search adds its own fields to the event — GEPA its `iteration`, `parentIds` and `source`, MIPRO its `trial`, SIMBA its `step`, OPRO and random search their `round`, bootstrap search its `source` and `demos` — and drops anything the seed could not honestly carry, since the events that name a component or a menu selection already carry it.
|
|
165
|
+
|
|
166
|
+
A reporter that reads only those two takes **`OptimizerEvent`** — `{ type: string }` — and narrows with **`isCandidateAccepted`** and **`isRunFinished`**. Because a literal tag is assignable to `string` and the parameter position is contravariant, one such reporter drops into any optimizer's `reporters` array. **`ReportableEvent`** names the union it reads. `@textopt/langsmith` is written this way.
|
|
167
|
+
|
|
140
168
|
## `textopt/gepa`
|
|
141
169
|
|
|
142
170
|
```ts
|
|
@@ -185,17 +213,17 @@ These options control search behavior and can be reused across runs.
|
|
|
185
213
|
|
|
186
214
|
Required: `seedCandidate`, `trainingSet`, `adapter` (a `GepaAdapter`), `reflect` (a `TextModel`), and `maxMetricCalls`.
|
|
187
215
|
|
|
188
|
-
| Option | Default
|
|
189
|
-
| --------------------- |
|
|
190
|
-
| `validationSet` | the trainingSet
|
|
191
|
-
| `testSet` | none. Held out of the search and scored once, on the winner
|
|
192
|
-
| `componentSelector` | `roundRobinComponentSelector()`
|
|
193
|
-
| `batchSampler` | an epoch-shuffled sampler over `minibatchSize`
|
|
194
|
-
| `valEvaluationPolicy` | `fullEvaluationPolicy()`
|
|
195
|
-
| `instanceId` | a content hash of the datum, falling back to its position when
|
|
196
|
-
| `cache` | a per-run memory cache. Pass `false` to disable
|
|
216
|
+
| Option | Default |
|
|
217
|
+
| --------------------- | ---------------------------------------------------------------------------------- |
|
|
218
|
+
| `validationSet` | the trainingSet |
|
|
219
|
+
| `testSet` | none. Held out of the search and scored once, on the winner |
|
|
220
|
+
| `componentSelector` | `roundRobinComponentSelector()` |
|
|
221
|
+
| `batchSampler` | an epoch-shuffled sampler over `minibatchSize` |
|
|
222
|
+
| `valEvaluationPolicy` | `fullEvaluationPolicy()` |
|
|
223
|
+
| `instanceId` | a content hash of the datum, falling back to its position when JSON cannot read it |
|
|
224
|
+
| `cache` | a per-run memory cache. Pass `false` to disable |
|
|
197
225
|
|
|
198
|
-
`
|
|
226
|
+
`reporters`, `onCheckpoint`, `resumeFrom`, and `signal` have no defaults.
|
|
199
227
|
|
|
200
228
|
TypeScript infers component names and the datum type from `seedCandidate` and `trainingSet`. Other fields use `NoInfer` and are checked against those inferred types.
|
|
201
229
|
|
|
@@ -204,11 +232,14 @@ TypeScript infers component names and the datum type from `seedCandidate` and `t
|
|
|
204
232
|
Extends `Adapter` with what reflection needs:
|
|
205
233
|
|
|
206
234
|
```ts
|
|
235
|
+
evaluate(args: EvaluateArgs<Datum, K>): ReflectiveBatch<Trajectory, Output>
|
|
207
236
|
makeReflectiveDataset(args: MakeReflectiveDatasetArgs<Datum, Trajectory, Output, K>): ReflectiveDataset<K>
|
|
208
237
|
proposeNewTexts?(args: ProposeArgs<K>): ComponentPatch<K> // optional
|
|
209
238
|
```
|
|
210
239
|
|
|
211
|
-
|
|
240
|
+
A `ReflectiveBatch` is an `EvaluationBatch` with `feedback` required. It is optional on the shared type because the searches that never reflect have no use for it; here it is the input to the whole method. An adapter that returns scores and no prose reduces every rollout to a number, and reflection then rewrites the instruction from a prompt whose feedback blocks are empty — a run that spends its whole budget, reports a normal-looking `stopReason`, and has been doing blind search. Nothing in the result distinguishes that from a hard task, so it is a type error instead.
|
|
241
|
+
|
|
242
|
+
All three methods may be synchronous or asynchronous. `ReflectiveDataset` is a partial map from component names to `ReflectiveRecord[]`. Each record contains `inputs`, `generatedOutputs`, `feedback`, `score`, and a typed `evidence` field. Adapters only need to return records for the requested components.
|
|
212
243
|
|
|
213
244
|
When `proposeNewTexts` is implemented, the adapter generates proposals without calling `reflect`. The task type still requires `reflect`, so offline runs can pass a stub.
|
|
214
245
|
|
|
@@ -230,7 +261,7 @@ When `proposeNewTexts` is implemented, the adapter generates proposals without c
|
|
|
230
261
|
|
|
231
262
|
Each export is a factory. Selector and acceptance interfaces accept custom functions. A `ValEvaluationPolicy` is an object with `selectInstances` and `bestCandidate` methods.
|
|
232
263
|
|
|
233
|
-
`pairedPermutationAcceptance` and `lowerBoundEvaluationPolicy` exist for metrics whose readings vary between runs of the same text. Both are strictly more conservative than the defaults
|
|
264
|
+
`pairedPermutationAcceptance` and `lowerBoundEvaluationPolicy` exist for metrics whose readings vary between runs of the same text. Both are strictly more conservative than the defaults. In the twenty-seed benchmark the pair scores 0.931 against plain GEPA's 0.920 on the noisy task and 0.945 against 0.947 on the noiseless one, neither gap significant over those seeds — conservative enough to be worth having on a metric that varies, cheap enough not to hurt on one that does not. A sign-flip test also needs a wide enough minibatch to say anything — over three instances the smallest p-value it can produce is 0.125, so at the default `minibatchSize` no proposal clears an `alpha` below that. `GepaOptimizer` refuses that combination at construction rather than letting the run discover it: an acceptance policy reports the smallest batch it could ever accept on as `minimumPairs`, and a `minibatchSize` below it throws.
|
|
234
265
|
|
|
235
266
|
### Reflection prompts
|
|
236
267
|
|
|
@@ -270,7 +301,11 @@ Feedback is end-to-end and every module receives the same string. A metric score
|
|
|
270
301
|
|
|
271
302
|
**`stopReason`** is one of `"budgetExhausted"`, `"costExhausted"`, `"deadlineReached"`, `"reflectionBudgetExhausted"`, `"aborted"`, or `"maxIterations"`.
|
|
272
303
|
|
|
273
|
-
**`
|
|
304
|
+
**`reporters`** is an array of `Reporter<GepaEvent>`, each with an optional `onEvent` and an optional `flush`. `onEvent` receives a discriminated `GepaEvent`: `start`, `iterationStart`, `evaluation`, `proposal`, `candidateAccepted`, `candidateRejected` (with `reason: "worse" | "notSelected"`), `error`, and `finish`. It is called synchronously, on the search's hot path, so a reporter that ships anywhere over a network buffers in `onEvent` and uploads in `flush`, which is awaited once as the run ends — including when it ends by throwing. A reporter that throws is warned about and skipped: observability never fails a run.
|
|
305
|
+
|
|
306
|
+
`candidateAccepted` fires for the seed too, and carries the `candidate` text, its `instanceScores` over the validation set, and its `outputs` under `trackBestOutputs` — the row a candidate put on the frontier, not just the mean. `finish` carries the same for the held-out sweep in `testInstanceScores` and `testOutputs`. In both, `undefined` marks an instance nothing measured — the evaluation policy skipped it, or an infrastructure failure lost it — and reporting one as a zero shows as a regression that never happened.
|
|
307
|
+
|
|
308
|
+
Reporting is observability; persisting a run so it can be resumed is `onCheckpoint`, below.
|
|
274
309
|
|
|
275
310
|
**`onCheckpoint`** runs after seed evaluation and each iteration with a JSON-serializable `GepaSnapshot`. Pass it as `resumeFrom` to continue. A fingerprint prevents resuming with a different seed candidate, instance set, or random seed. Every optimizer here has the same three: `onCheckpoint`, `resumeFrom`, and a `snapshot` on the result. A snapshot handed back as `resumeFrom` is copied, never mutated by the run that continues from it.
|
|
276
311
|
|
|
@@ -280,7 +315,7 @@ Feedback is end-to-end and every module receives the same string. A metric score
|
|
|
280
315
|
import { SimbaOptimizer, buildAdvicePrompt, parseAdvice } from "textopt/simba";
|
|
281
316
|
```
|
|
282
317
|
|
|
283
|
-
SIMBA uses the base `Adapter`: it reads outputs, scores, and feedback and builds its own evidence, so it needs no `makeReflectiveDataset`. `SimbaTask` adds `reflect`, `demoComponents`, `instructionComponents`, `renderDemo`, `buildAdvicePrompt`, `sampler`, `instanceId`, `cache`, `
|
|
318
|
+
SIMBA uses the base `Adapter`: it reads outputs, scores, and feedback and builds its own evidence, so it needs no `makeReflectiveDataset`. `SimbaTask` adds `reflect`, `demoComponents`, `instructionComponents`, `renderDemo`, `buildAdvicePrompt`, `sampler`, `instanceId`, `cache`, `reporters`, `onCheckpoint`, and `resumeFrom`.
|
|
284
319
|
|
|
285
320
|
| Option | Default | Effect |
|
|
286
321
|
| ---------------------- | -------------------- | -------------------------------------------------------------------------- |
|
|
@@ -320,7 +355,7 @@ Ported from DSPy's SIMBA with two deliberate changes. A trajectory sample runs o
|
|
|
320
355
|
import { BootstrapSearchOptimizer } from "textopt/bootstrap-search";
|
|
321
356
|
```
|
|
322
357
|
|
|
323
|
-
DSPy's `BootstrapFewShotWithRandomSearch`. It uses the base `Adapter` and no reflection model at all: every candidate is assembled from outputs the system itself produced, so the search costs rollouts and nothing else. `BootstrapSearchTask` adds `demoComponents` (required), `renderDemo`, `goldOutput`, `instanceId`, `cache`, `
|
|
358
|
+
DSPy's `BootstrapFewShotWithRandomSearch`. It uses the base `Adapter` and no reflection model at all: every candidate is assembled from outputs the system itself produced, so the search costs rollouts and nothing else. `BootstrapSearchTask` adds `demoComponents` (required), `renderDemo`, `goldOutput`, `instanceId`, `cache`, `reporters`, `onCheckpoint`, and `resumeFrom`.
|
|
324
359
|
|
|
325
360
|
| Option | Default | Effect |
|
|
326
361
|
| ------------------ | ------- | -------------------------------------------------------------------- |
|
|
@@ -352,7 +387,7 @@ Unlike DSPy, which bootstraps each predictor separately from the traces of one p
|
|
|
352
387
|
import { OproOptimizer, buildOproPrompt } from "textopt/opro";
|
|
353
388
|
```
|
|
354
389
|
|
|
355
|
-
OPRO uses the base `Adapter`. `OproTask` adds `reflect` and optional `renderDatum`, `instanceId`, `cache`, and `
|
|
390
|
+
OPRO uses the base `Adapter`. `OproTask` adds `reflect` and optional `renderDatum`, `instanceId`, `cache`, and `reporters` fields.
|
|
356
391
|
|
|
357
392
|
| Option | Default | Effect |
|
|
358
393
|
| -------------------- | ----------------- | ---------------------------------------------------------- |
|
|
@@ -399,7 +434,7 @@ For multi-component candidates, each attempt records the other components presen
|
|
|
399
434
|
import { MiproOptimizer, proposeConfiguration } from "textopt/mipro";
|
|
400
435
|
```
|
|
401
436
|
|
|
402
|
-
MIPRO uses the base `Adapter`. `MiproTask` adds `reflect`, `componentOptions`, `renderDatum`, `batchSampler`, `instanceId`, `cache`, and `
|
|
437
|
+
MIPRO uses the base `Adapter`. `MiproTask` adds `reflect`, `componentOptions`, `renderDatum`, `batchSampler`, `instanceId`, `cache`, and `reporters`.
|
|
403
438
|
|
|
404
439
|
| Option | Default | Effect |
|
|
405
440
|
| -------------------------- | ------------------ | ---------------------------------------------------------------------------------- |
|
|
@@ -474,13 +509,18 @@ The paraphrase prompt receives no score or feedback. Compare random search with
|
|
|
474
509
|
```ts
|
|
475
510
|
import { createFileCache } from "textopt/file-cache";
|
|
476
511
|
|
|
477
|
-
const cache = createFileCache({
|
|
512
|
+
const cache = createFileCache({
|
|
513
|
+
path: ".textopt/scores.jsonl",
|
|
514
|
+
namespace: "gpt-4o-mini@t0/scorer-v3",
|
|
515
|
+
});
|
|
478
516
|
```
|
|
479
517
|
|
|
480
518
|
An `EvaluationCache` that outlives the process, as an append-only JSONL log. A long run against a real provider is measured in hours and dollars, and an in-memory cache throws all of it away when the run ends.
|
|
481
519
|
|
|
482
520
|
Append-only rather than rewritten: a score is never invalidated, because the key names the candidate, the instance, and the environment — and a log survives a process killed mid-write, which a file rewritten in place does not. A record that does not parse is dropped rather than fatal, since the last line of an interrupted log is routinely half-written. Later records win, so a re-measured instance replaces its earlier reading.
|
|
483
521
|
|
|
522
|
+
`namespace` is required and scopes every key to the system the scores measure — model id, decoding settings, scorer version. It is not optional the way `cacheNamespace` is, because this log outlives every part of that system a key does not name: an alias the provider upgraded under you, an edited scorer. Entries written under one namespace are never served to a run under another. Change it whenever anything outside the candidate text changes.
|
|
523
|
+
|
|
484
524
|
`maxEntries` (default 1,000,000) bounds what is held in memory; the file itself is never trimmed. `entries()` is deliberately absent — it exists so a checkpoint can carry scores that would otherwise be lost, and these are already on disk.
|
|
485
525
|
|
|
486
526
|
This is the only entry point that imports `node:fs`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const require_demos = require("../demos-
|
|
2
|
+
const require_warnings = require("../warnings-CWRJF-jA.cjs");
|
|
3
|
+
const require_demos = require("../demos-9v5ts7F3.cjs");
|
|
4
4
|
const require_rng = require("../rng-DbA_rPIo.cjs");
|
|
5
5
|
//#region src/bootstrap-search/optimize.ts
|
|
6
6
|
const DEFAULT_CANDIDATES = 16;
|
|
@@ -34,21 +34,31 @@ const DEFAULT_MAX_LABELED_DEMOS = 16;
|
|
|
34
34
|
var BootstrapSearchOptimizer = class {
|
|
35
35
|
#config;
|
|
36
36
|
constructor(config = {}) {
|
|
37
|
+
assertBootstrapSearchConfig(config);
|
|
37
38
|
this.#config = config;
|
|
38
39
|
}
|
|
39
|
-
optimize(task) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
async optimize(task) {
|
|
41
|
+
try {
|
|
42
|
+
return await run({
|
|
43
|
+
config: this.#config,
|
|
44
|
+
task
|
|
45
|
+
});
|
|
46
|
+
} finally {
|
|
47
|
+
await require_warnings.flushReporters(task.reporters ?? []);
|
|
48
|
+
}
|
|
44
49
|
}
|
|
45
50
|
};
|
|
46
51
|
async function run(args) {
|
|
47
52
|
const { config, task } = args;
|
|
48
|
-
const { candidates: shuffledHarvests = DEFAULT_CANDIDATES, maxDemos = DEFAULT_MAX_DEMOS, minDemos = DEFAULT_MIN_DEMOS, maxLabeledDemos = DEFAULT_MAX_LABELED_DEMOS, demoMinScore, stopAtScore, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
|
|
49
|
-
const { seedCandidate, trainingSet, validationSet
|
|
50
|
-
const
|
|
51
|
-
|
|
53
|
+
const { candidates: shuffledHarvests = DEFAULT_CANDIDATES, maxDemos = DEFAULT_MAX_DEMOS, minDemos = DEFAULT_MIN_DEMOS, maxLabeledDemos = DEFAULT_MAX_LABELED_DEMOS, demoMinScore, stopAtScore, concurrency = 1, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
|
|
54
|
+
const { seedCandidate, trainingSet, validationSet: requestedValidationSet, testSet, adapter, demoComponents, renderDemo, goldOutput, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_warnings.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
|
|
55
|
+
const { validationSet, warnings } = require_warnings.resolveValidationSet({
|
|
56
|
+
validationSet: requestedValidationSet,
|
|
57
|
+
trainingSet
|
|
58
|
+
});
|
|
59
|
+
const emit = require_warnings.createEmitter(reporters);
|
|
60
|
+
const deadline = require_warnings.createDeadline({ maxWallClockMs });
|
|
61
|
+
const components = require_warnings.componentNames(seedCandidate);
|
|
52
62
|
if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
|
|
53
63
|
if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
|
|
54
64
|
if (demoComponents.length === 0) throw new Error("optimize requires at least one demoComponent: this search has nothing to put demonstrations in otherwise");
|
|
@@ -61,7 +71,7 @@ async function run(args) {
|
|
|
61
71
|
datum,
|
|
62
72
|
index
|
|
63
73
|
})) ?? [];
|
|
64
|
-
const fingerprint =
|
|
74
|
+
const fingerprint = require_warnings.runFingerprint({
|
|
65
75
|
seedCandidate,
|
|
66
76
|
trainingIds: trainingSet.map((datum, index) => instanceId({
|
|
67
77
|
datum,
|
|
@@ -71,17 +81,17 @@ async function run(args) {
|
|
|
71
81
|
seed,
|
|
72
82
|
...cacheNamespace === void 0 ? {} : { cacheNamespace }
|
|
73
83
|
});
|
|
74
|
-
|
|
84
|
+
require_warnings.assertResumable({
|
|
75
85
|
fingerprint,
|
|
76
86
|
...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
|
|
77
87
|
});
|
|
78
88
|
const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
|
|
79
|
-
const budget =
|
|
89
|
+
const budget = require_warnings.createBudget({
|
|
80
90
|
maxMetricCalls,
|
|
81
91
|
spent: resumeFrom?.metricCalls ?? 0
|
|
82
92
|
});
|
|
83
|
-
const evaluationCache = cache === false ? void 0 : cache ??
|
|
84
|
-
const evaluator =
|
|
93
|
+
const evaluationCache = cache === false ? void 0 : cache ?? require_warnings.createMemoryCache();
|
|
94
|
+
const evaluator = require_warnings.createEvaluator({
|
|
85
95
|
adapter,
|
|
86
96
|
budget,
|
|
87
97
|
...retry === void 0 ? {} : { retry },
|
|
@@ -89,8 +99,9 @@ async function run(args) {
|
|
|
89
99
|
...evaluationCache === void 0 ? {} : { cache: evaluationCache },
|
|
90
100
|
trackOutputs: trackBestOutputs,
|
|
91
101
|
cacheHits: resumeFrom?.cacheHits ?? 0,
|
|
102
|
+
...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
|
|
92
103
|
...signal === void 0 ? {} : { signal },
|
|
93
|
-
onEvaluation: (event) =>
|
|
104
|
+
onEvaluation: (event) => emit({
|
|
94
105
|
type: "evaluation",
|
|
95
106
|
...event
|
|
96
107
|
})
|
|
@@ -100,7 +111,7 @@ async function run(args) {
|
|
|
100
111
|
let drawn = resumeFrom?.drawn ?? 0;
|
|
101
112
|
let bootstrapMetricCalls = resumeFrom?.bootstrapMetricCalls ?? 0;
|
|
102
113
|
let stopReason = "candidatesExhausted";
|
|
103
|
-
|
|
114
|
+
emit({
|
|
104
115
|
type: "start",
|
|
105
116
|
components,
|
|
106
117
|
validationSetSize: validationSet.length
|
|
@@ -116,13 +127,25 @@ async function run(args) {
|
|
|
116
127
|
iteration: evaluated.length
|
|
117
128
|
});
|
|
118
129
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
130
|
+
const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
|
|
131
|
+
const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_warnings.requireMeasuredMean({
|
|
132
|
+
batch: seedEvaluation,
|
|
121
133
|
phase: "seed"
|
|
122
134
|
});
|
|
123
135
|
let best = resumeFrom?.best ?? seedCandidate;
|
|
124
136
|
let bestScore = resumeFrom?.bestScore ?? seedScore;
|
|
125
137
|
let bestOutputs;
|
|
138
|
+
let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
|
|
139
|
+
if (seedEvaluation !== void 0) emit({
|
|
140
|
+
type: "candidateAccepted",
|
|
141
|
+
source: "zeroShot",
|
|
142
|
+
demos: 0,
|
|
143
|
+
candidateId: 0,
|
|
144
|
+
candidate: seedCandidate,
|
|
145
|
+
aggregateScore: seedScore,
|
|
146
|
+
instanceScores: require_warnings.instanceRow(seedEvaluation),
|
|
147
|
+
...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
|
|
148
|
+
});
|
|
126
149
|
function takeSnapshot() {
|
|
127
150
|
const cached = checkpointCache ? evaluationCache?.entries?.() : void 0;
|
|
128
151
|
return {
|
|
@@ -136,6 +159,8 @@ async function run(args) {
|
|
|
136
159
|
metricCalls: budget.spent(),
|
|
137
160
|
bootstrapMetricCalls,
|
|
138
161
|
cacheHits: evaluator.cacheHits(),
|
|
162
|
+
usage: evaluator.usage(),
|
|
163
|
+
acceptedCandidates,
|
|
139
164
|
rngState: rng.state(),
|
|
140
165
|
...cached === void 0 ? {} : { cache: cached }
|
|
141
166
|
};
|
|
@@ -148,12 +173,13 @@ async function run(args) {
|
|
|
148
173
|
shuffledHarvests,
|
|
149
174
|
labeled: goldOutput !== void 0
|
|
150
175
|
});
|
|
151
|
-
|
|
176
|
+
const waveSize = stopAtScore === void 0 ? concurrency : 1;
|
|
177
|
+
while (drawn < plan.length) {
|
|
152
178
|
if (signal?.aborted) {
|
|
153
179
|
stopReason = "aborted";
|
|
154
180
|
break;
|
|
155
181
|
}
|
|
156
|
-
if (
|
|
182
|
+
if (require_warnings.costExhausted({
|
|
157
183
|
usage: evaluator.usage(),
|
|
158
184
|
maxCostUsd
|
|
159
185
|
})) {
|
|
@@ -164,58 +190,95 @@ async function run(args) {
|
|
|
164
190
|
stopReason = "deadlineReached";
|
|
165
191
|
break;
|
|
166
192
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const candidate = withDemos(block);
|
|
174
|
-
let evaluation;
|
|
175
|
-
try {
|
|
176
|
-
evaluation = await sweep(candidate, "validation");
|
|
177
|
-
} catch (err) {
|
|
178
|
-
if (err instanceof require_evaluation.BudgetExhausted) {
|
|
179
|
-
stopReason = "budgetExhausted";
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
if (signal?.aborted) {
|
|
183
|
-
stopReason = "aborted";
|
|
193
|
+
const wave = [];
|
|
194
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
195
|
+
let waveStop;
|
|
196
|
+
while (wave.length < waveSize && drawn + wave.length < plan.length) {
|
|
197
|
+
if (!budget.canAfford(validationSet.length + 1)) {
|
|
198
|
+
waveStop = "budgetExhausted";
|
|
184
199
|
break;
|
|
185
200
|
}
|
|
186
|
-
|
|
201
|
+
const source = plan[drawn + wave.length];
|
|
202
|
+
const block = await buildBlock(source);
|
|
203
|
+
const candidate = withDemos(block);
|
|
204
|
+
const key = require_warnings.candidateHash(candidate);
|
|
205
|
+
const prior = inFlight.get(key);
|
|
206
|
+
const sweeping = prior === void 0 ? settled(sweep(candidate, "validation")) : prior.then(() => settled(sweep(candidate, "validation")));
|
|
207
|
+
inFlight.set(key, sweeping);
|
|
208
|
+
wave.push({
|
|
209
|
+
source,
|
|
210
|
+
candidate,
|
|
211
|
+
block,
|
|
212
|
+
sweep: sweeping
|
|
213
|
+
});
|
|
187
214
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
let waveError;
|
|
216
|
+
let draining = false;
|
|
217
|
+
for (const entry of wave) {
|
|
218
|
+
const outcome = await entry.sweep;
|
|
219
|
+
if (draining) continue;
|
|
220
|
+
drawn += 1;
|
|
221
|
+
if (outcome.failed === true) {
|
|
222
|
+
draining = true;
|
|
223
|
+
if (outcome.err instanceof require_warnings.BudgetExhausted) {
|
|
224
|
+
waveStop = "budgetExhausted";
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (signal?.aborted) {
|
|
228
|
+
waveStop = "aborted";
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
waveError = outcome.err;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const evaluation = outcome.evaluation;
|
|
235
|
+
const score = require_warnings.measuredMean(evaluation);
|
|
236
|
+
if (score === void 0) continue;
|
|
237
|
+
const accepted = score > bestScore;
|
|
238
|
+
evaluated.push({
|
|
239
|
+
candidate: entry.candidate,
|
|
240
|
+
source: entry.source,
|
|
241
|
+
demos: countDemos(entry.block),
|
|
242
|
+
score
|
|
243
|
+
});
|
|
244
|
+
emit({
|
|
245
|
+
type: "candidate",
|
|
246
|
+
index: evaluated.length - 1,
|
|
247
|
+
source: entry.source,
|
|
248
|
+
demos: countDemos(entry.block),
|
|
249
|
+
score,
|
|
250
|
+
accepted
|
|
251
|
+
});
|
|
252
|
+
if (accepted) {
|
|
253
|
+
acceptedCandidates += 1;
|
|
254
|
+
emit({
|
|
255
|
+
type: "candidateAccepted",
|
|
256
|
+
source: entry.source,
|
|
257
|
+
demos: countDemos(entry.block),
|
|
258
|
+
candidateId: acceptedCandidates,
|
|
259
|
+
candidate: entry.candidate,
|
|
260
|
+
aggregateScore: score,
|
|
261
|
+
instanceScores: require_warnings.instanceRow(evaluation),
|
|
262
|
+
...trackBestOutputs ? { outputs: evaluation.outputs } : {}
|
|
263
|
+
});
|
|
264
|
+
best = entry.candidate;
|
|
265
|
+
bestScore = score;
|
|
266
|
+
bestOutputs = evaluation.outputs;
|
|
267
|
+
}
|
|
268
|
+
if (stopAtScore !== void 0 && score >= stopAtScore) {
|
|
269
|
+
waveStop = "scoreReached";
|
|
270
|
+
draining = true;
|
|
271
|
+
}
|
|
209
272
|
}
|
|
273
|
+
if (waveError !== void 0) throw waveError;
|
|
210
274
|
await checkpoint();
|
|
211
|
-
if (
|
|
212
|
-
stopReason =
|
|
213
|
-
drawn += 1;
|
|
275
|
+
if (waveStop !== void 0) {
|
|
276
|
+
stopReason = waveStop;
|
|
214
277
|
break;
|
|
215
278
|
}
|
|
216
279
|
}
|
|
217
280
|
if (signal?.aborted) stopReason = "aborted";
|
|
218
|
-
const
|
|
281
|
+
const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
|
|
219
282
|
candidate: best,
|
|
220
283
|
batch: testSet,
|
|
221
284
|
ids: testIds,
|
|
@@ -224,13 +287,18 @@ async function run(args) {
|
|
|
224
287
|
candidateId: null,
|
|
225
288
|
iteration: evaluated.length,
|
|
226
289
|
charge: false
|
|
227
|
-
})
|
|
228
|
-
|
|
290
|
+
});
|
|
291
|
+
const testScore = heldOut === void 0 ? void 0 : require_warnings.measuredMean(heldOut);
|
|
292
|
+
emit({
|
|
229
293
|
type: "finish",
|
|
230
294
|
reason: stopReason,
|
|
295
|
+
warnings,
|
|
296
|
+
bestCandidateId: acceptedCandidates,
|
|
231
297
|
bestScore,
|
|
232
298
|
metricCalls: budget.spent(),
|
|
233
|
-
...testScore === void 0 ? {} : { testScore }
|
|
299
|
+
...testScore === void 0 ? {} : { testScore },
|
|
300
|
+
...heldOut === void 0 ? {} : { testInstanceScores: require_warnings.instanceRow(heldOut) },
|
|
301
|
+
...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
|
|
234
302
|
});
|
|
235
303
|
return {
|
|
236
304
|
bestCandidate: best,
|
|
@@ -245,8 +313,10 @@ async function run(args) {
|
|
|
245
313
|
...trackBestOutputs && bestOutputs !== void 0 ? { bestOutputs } : {},
|
|
246
314
|
...testScore === void 0 ? {} : {
|
|
247
315
|
testScore,
|
|
248
|
-
testMetricCalls:
|
|
316
|
+
testMetricCalls: evaluator.unchargedCalls(),
|
|
317
|
+
testUsage: evaluator.unchargedUsage()
|
|
249
318
|
},
|
|
319
|
+
warnings,
|
|
250
320
|
stopReason
|
|
251
321
|
};
|
|
252
322
|
function withDemos(block) {
|
|
@@ -260,19 +330,21 @@ async function run(args) {
|
|
|
260
330
|
const requested = source === "unshuffled" ? maxDemos : minDemos + rng.nextInt(Math.max(1, maxDemos - minDemos + 1));
|
|
261
331
|
const affordable = Math.min(trainingSet.length, budget.remaining() - validationSet.length);
|
|
262
332
|
if (affordable < 1) return "";
|
|
263
|
-
const harvest = await require_demos.
|
|
333
|
+
const harvest = await require_demos.harvestFewShotExamples({
|
|
264
334
|
adapter,
|
|
265
335
|
candidate: seedCandidate,
|
|
266
336
|
trainingSet,
|
|
267
337
|
...demoMinScore === void 0 ? {} : { minScore: demoMinScore },
|
|
268
338
|
maxDemos: requested,
|
|
269
339
|
maxMetricCalls: affordable,
|
|
340
|
+
...maxCostUsd === void 0 ? {} : { maxCostUsd: maxCostUsd - evaluator.usage().costUsd },
|
|
270
341
|
...source === "unshuffled" ? {} : { rng },
|
|
271
342
|
...renderDemo === void 0 ? {} : { renderDemo },
|
|
272
343
|
...signal === void 0 ? {} : { signal }
|
|
273
344
|
});
|
|
274
345
|
bootstrapMetricCalls += harvest.metricCalls;
|
|
275
346
|
budget.reserve(harvest.metricCalls);
|
|
347
|
+
evaluator.absorbUsage(harvest.usage);
|
|
276
348
|
return harvest.block;
|
|
277
349
|
}
|
|
278
350
|
function labeledBlock() {
|
|
@@ -298,11 +370,25 @@ function candidatePlan(args) {
|
|
|
298
370
|
...Array.from({ length: shuffledHarvests }, () => "bootstrapped")
|
|
299
371
|
];
|
|
300
372
|
}
|
|
373
|
+
/** Turns a sweep into a value, so a dispatched one never rejects unobserved. */
|
|
374
|
+
function settled(sweeping) {
|
|
375
|
+
return sweeping.then((evaluation) => ({ evaluation }), (err) => ({
|
|
376
|
+
failed: true,
|
|
377
|
+
err
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Range checks on the search knobs, run at construction so a configuration
|
|
382
|
+
* that could never terminate is refused before a task is ever handed to it.
|
|
383
|
+
*/
|
|
384
|
+
function assertBootstrapSearchConfig(config) {
|
|
385
|
+
const { concurrency = 1 } = config;
|
|
386
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
|
|
387
|
+
}
|
|
301
388
|
function countDemos(block) {
|
|
302
389
|
return block.split("<demo>").length - 1;
|
|
303
390
|
}
|
|
304
|
-
function defaultInstanceId(args) {
|
|
305
|
-
return String(args.index);
|
|
306
|
-
}
|
|
307
391
|
//#endregion
|
|
308
392
|
exports.BootstrapSearchOptimizer = BootstrapSearchOptimizer;
|
|
393
|
+
exports.isCandidateAccepted = require_warnings.isCandidateAccepted;
|
|
394
|
+
exports.isRunFinished = require_warnings.isRunFinished;
|