textopt 0.1.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 +24 -6
- package/dist/bootstrap-search/index.cjs +29 -23
- package/dist/bootstrap-search/index.d.cts +3 -3
- package/dist/bootstrap-search/index.d.mts +3 -3
- package/dist/bootstrap-search/index.mjs +9 -3
- package/dist/{demos-D2o0qSSo.cjs → demos-9v5ts7F3.cjs} +4 -4
- package/dist/{demos-1b7JiUue.d.mts → demos-ASsSXYXA.d.mts} +1 -1
- package/dist/{demos-DE2oxNWX.mjs → demos-Brobjfuc.mjs} +1 -1
- package/dist/{demos-CU9dy2oT.d.cts → demos-ByaLZy-Z.d.cts} +1 -1
- package/dist/file-cache.cjs +16 -5
- package/dist/file-cache.d.cts +13 -0
- package/dist/file-cache.d.mts +13 -0
- package/dist/file-cache.mjs +16 -5
- package/dist/gepa/index.cjs +87 -44
- package/dist/gepa/index.d.cts +8 -6
- package/dist/gepa/index.d.mts +8 -6
- package/dist/gepa/index.mjs +55 -12
- package/dist/index.cjs +44 -19
- package/dist/index.d.cts +37 -5
- package/dist/index.d.mts +37 -5
- package/dist/index.mjs +29 -6
- package/dist/mipro/index.cjs +31 -25
- package/dist/mipro/index.d.cts +3 -3
- package/dist/mipro/index.d.mts +3 -3
- package/dist/mipro/index.mjs +9 -3
- package/dist/opro/index.cjs +31 -25
- package/dist/opro/index.d.cts +2 -2
- package/dist/opro/index.d.mts +2 -2
- package/dist/opro/index.mjs +8 -2
- package/dist/{optimizer-Bh5DPRMH.d.cts → optimizer-4Zv-Zt2t.d.cts} +41 -2
- package/dist/{optimizer-Ck6-e_8o.d.mts → optimizer-Ds5mzYjz.d.mts} +41 -2
- package/dist/random-search/index.cjs +27 -21
- package/dist/random-search/index.d.cts +2 -2
- package/dist/random-search/index.d.mts +2 -2
- package/dist/random-search/index.mjs +8 -2
- package/dist/{reflection-LRaAZP4e.d.mts → reflection-CMezGu6u.d.mts} +32 -4
- package/dist/{reflection-Dt3QrXhM.d.cts → reflection-D0A7eahD.d.cts} +32 -4
- package/dist/{reporting-DQbAohc9.d.cts → reporting-bq007_2z.d.cts} +55 -1
- package/dist/{reporting-DQbAohc9.d.mts → reporting-bq007_2z.d.mts} +55 -1
- package/dist/simba/index.cjs +31 -25
- package/dist/simba/index.d.cts +24 -5
- package/dist/simba/index.d.mts +24 -5
- package/dist/simba/index.mjs +9 -3
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.mts +2 -2
- package/dist/{reporting-CNHzbJC-.cjs → warnings-CWRJF-jA.cjs} +63 -0
- package/dist/{reporting-DY-DC4HG.mjs → warnings-OxvDi9kN.mjs} +52 -1
- 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 +3 -2
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 |
|
|
@@ -95,7 +97,7 @@ evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Outp
|
|
|
95
97
|
|
|
96
98
|
**`transient`** marks scores caused by infrastructure failures such as rate limits, 5xx responses, or network errors. Transient scores are not cached.
|
|
97
99
|
|
|
98
|
-
**`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`, 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.
|
|
99
101
|
|
|
100
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.
|
|
101
103
|
|
|
@@ -107,6 +109,10 @@ evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Outp
|
|
|
107
109
|
|
|
108
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.
|
|
109
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.
|
|
115
|
+
|
|
110
116
|
**`TextModel`** is the provider-independent interface `({ prompt, signal }) => Promise<string>`.
|
|
111
117
|
|
|
112
118
|
### Values
|
|
@@ -121,7 +127,7 @@ For Redis, SQLite, or file-backed caching, implement **`EvaluationCache`** with
|
|
|
121
127
|
|
|
122
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.
|
|
123
129
|
|
|
124
|
-
**`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](
|
|
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).
|
|
125
131
|
|
|
126
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.
|
|
127
133
|
|
|
@@ -131,7 +137,11 @@ For Redis, SQLite, or file-backed caching, implement **`EvaluationCache`** with
|
|
|
131
137
|
|
|
132
138
|
**`parseProposedText(text)`** extracts a proposal from a reflection response, including responses with fenced blocks or surrounding commentary.
|
|
133
139
|
|
|
134
|
-
**`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.
|
|
135
145
|
|
|
136
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`.
|
|
137
147
|
|
|
@@ -222,11 +232,14 @@ TypeScript infers component names and the datum type from `seedCandidate` and `t
|
|
|
222
232
|
Extends `Adapter` with what reflection needs:
|
|
223
233
|
|
|
224
234
|
```ts
|
|
235
|
+
evaluate(args: EvaluateArgs<Datum, K>): ReflectiveBatch<Trajectory, Output>
|
|
225
236
|
makeReflectiveDataset(args: MakeReflectiveDatasetArgs<Datum, Trajectory, Output, K>): ReflectiveDataset<K>
|
|
226
237
|
proposeNewTexts?(args: ProposeArgs<K>): ComponentPatch<K> // optional
|
|
227
238
|
```
|
|
228
239
|
|
|
229
|
-
|
|
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.
|
|
230
243
|
|
|
231
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.
|
|
232
245
|
|
|
@@ -248,7 +261,7 @@ When `proposeNewTexts` is implemented, the adapter generates proposals without c
|
|
|
248
261
|
|
|
249
262
|
Each export is a factory. Selector and acceptance interfaces accept custom functions. A `ValEvaluationPolicy` is an object with `selectInstances` and `bestCandidate` methods.
|
|
250
263
|
|
|
251
|
-
`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.
|
|
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.
|
|
252
265
|
|
|
253
266
|
### Reflection prompts
|
|
254
267
|
|
|
@@ -496,13 +509,18 @@ The paraphrase prompt receives no score or feedback. Compare random search with
|
|
|
496
509
|
```ts
|
|
497
510
|
import { createFileCache } from "textopt/file-cache";
|
|
498
511
|
|
|
499
|
-
const cache = createFileCache({
|
|
512
|
+
const cache = createFileCache({
|
|
513
|
+
path: ".textopt/scores.jsonl",
|
|
514
|
+
namespace: "gpt-4o-mini@t0/scorer-v3",
|
|
515
|
+
});
|
|
500
516
|
```
|
|
501
517
|
|
|
502
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.
|
|
503
519
|
|
|
504
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.
|
|
505
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
|
+
|
|
506
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.
|
|
507
525
|
|
|
508
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;
|
|
@@ -44,17 +44,21 @@ var BootstrapSearchOptimizer = class {
|
|
|
44
44
|
task
|
|
45
45
|
});
|
|
46
46
|
} finally {
|
|
47
|
-
await
|
|
47
|
+
await require_warnings.flushReporters(task.reporters ?? []);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
async function run(args) {
|
|
52
52
|
const { config, task } = args;
|
|
53
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
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
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);
|
|
58
62
|
if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
|
|
59
63
|
if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
|
|
60
64
|
if (demoComponents.length === 0) throw new Error("optimize requires at least one demoComponent: this search has nothing to put demonstrations in otherwise");
|
|
@@ -67,7 +71,7 @@ async function run(args) {
|
|
|
67
71
|
datum,
|
|
68
72
|
index
|
|
69
73
|
})) ?? [];
|
|
70
|
-
const fingerprint =
|
|
74
|
+
const fingerprint = require_warnings.runFingerprint({
|
|
71
75
|
seedCandidate,
|
|
72
76
|
trainingIds: trainingSet.map((datum, index) => instanceId({
|
|
73
77
|
datum,
|
|
@@ -77,17 +81,17 @@ async function run(args) {
|
|
|
77
81
|
seed,
|
|
78
82
|
...cacheNamespace === void 0 ? {} : { cacheNamespace }
|
|
79
83
|
});
|
|
80
|
-
|
|
84
|
+
require_warnings.assertResumable({
|
|
81
85
|
fingerprint,
|
|
82
86
|
...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
|
|
83
87
|
});
|
|
84
88
|
const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
|
|
85
|
-
const budget =
|
|
89
|
+
const budget = require_warnings.createBudget({
|
|
86
90
|
maxMetricCalls,
|
|
87
91
|
spent: resumeFrom?.metricCalls ?? 0
|
|
88
92
|
});
|
|
89
|
-
const evaluationCache = cache === false ? void 0 : cache ??
|
|
90
|
-
const evaluator =
|
|
93
|
+
const evaluationCache = cache === false ? void 0 : cache ?? require_warnings.createMemoryCache();
|
|
94
|
+
const evaluator = require_warnings.createEvaluator({
|
|
91
95
|
adapter,
|
|
92
96
|
budget,
|
|
93
97
|
...retry === void 0 ? {} : { retry },
|
|
@@ -124,7 +128,7 @@ async function run(args) {
|
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
|
|
127
|
-
const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore :
|
|
131
|
+
const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_warnings.requireMeasuredMean({
|
|
128
132
|
batch: seedEvaluation,
|
|
129
133
|
phase: "seed"
|
|
130
134
|
});
|
|
@@ -139,7 +143,7 @@ async function run(args) {
|
|
|
139
143
|
candidateId: 0,
|
|
140
144
|
candidate: seedCandidate,
|
|
141
145
|
aggregateScore: seedScore,
|
|
142
|
-
instanceScores:
|
|
146
|
+
instanceScores: require_warnings.instanceRow(seedEvaluation),
|
|
143
147
|
...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
|
|
144
148
|
});
|
|
145
149
|
function takeSnapshot() {
|
|
@@ -175,7 +179,7 @@ async function run(args) {
|
|
|
175
179
|
stopReason = "aborted";
|
|
176
180
|
break;
|
|
177
181
|
}
|
|
178
|
-
if (
|
|
182
|
+
if (require_warnings.costExhausted({
|
|
179
183
|
usage: evaluator.usage(),
|
|
180
184
|
maxCostUsd
|
|
181
185
|
})) {
|
|
@@ -197,7 +201,7 @@ async function run(args) {
|
|
|
197
201
|
const source = plan[drawn + wave.length];
|
|
198
202
|
const block = await buildBlock(source);
|
|
199
203
|
const candidate = withDemos(block);
|
|
200
|
-
const key =
|
|
204
|
+
const key = require_warnings.candidateHash(candidate);
|
|
201
205
|
const prior = inFlight.get(key);
|
|
202
206
|
const sweeping = prior === void 0 ? settled(sweep(candidate, "validation")) : prior.then(() => settled(sweep(candidate, "validation")));
|
|
203
207
|
inFlight.set(key, sweeping);
|
|
@@ -216,7 +220,7 @@ async function run(args) {
|
|
|
216
220
|
drawn += 1;
|
|
217
221
|
if (outcome.failed === true) {
|
|
218
222
|
draining = true;
|
|
219
|
-
if (outcome.err instanceof
|
|
223
|
+
if (outcome.err instanceof require_warnings.BudgetExhausted) {
|
|
220
224
|
waveStop = "budgetExhausted";
|
|
221
225
|
continue;
|
|
222
226
|
}
|
|
@@ -228,7 +232,7 @@ async function run(args) {
|
|
|
228
232
|
continue;
|
|
229
233
|
}
|
|
230
234
|
const evaluation = outcome.evaluation;
|
|
231
|
-
const score =
|
|
235
|
+
const score = require_warnings.measuredMean(evaluation);
|
|
232
236
|
if (score === void 0) continue;
|
|
233
237
|
const accepted = score > bestScore;
|
|
234
238
|
evaluated.push({
|
|
@@ -254,7 +258,7 @@ async function run(args) {
|
|
|
254
258
|
candidateId: acceptedCandidates,
|
|
255
259
|
candidate: entry.candidate,
|
|
256
260
|
aggregateScore: score,
|
|
257
|
-
instanceScores:
|
|
261
|
+
instanceScores: require_warnings.instanceRow(evaluation),
|
|
258
262
|
...trackBestOutputs ? { outputs: evaluation.outputs } : {}
|
|
259
263
|
});
|
|
260
264
|
best = entry.candidate;
|
|
@@ -284,15 +288,16 @@ async function run(args) {
|
|
|
284
288
|
iteration: evaluated.length,
|
|
285
289
|
charge: false
|
|
286
290
|
});
|
|
287
|
-
const testScore = heldOut === void 0 ? void 0 :
|
|
291
|
+
const testScore = heldOut === void 0 ? void 0 : require_warnings.measuredMean(heldOut);
|
|
288
292
|
emit({
|
|
289
293
|
type: "finish",
|
|
290
294
|
reason: stopReason,
|
|
295
|
+
warnings,
|
|
291
296
|
bestCandidateId: acceptedCandidates,
|
|
292
297
|
bestScore,
|
|
293
298
|
metricCalls: budget.spent(),
|
|
294
299
|
...testScore === void 0 ? {} : { testScore },
|
|
295
|
-
...heldOut === void 0 ? {} : { testInstanceScores:
|
|
300
|
+
...heldOut === void 0 ? {} : { testInstanceScores: require_warnings.instanceRow(heldOut) },
|
|
296
301
|
...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
|
|
297
302
|
});
|
|
298
303
|
return {
|
|
@@ -311,6 +316,7 @@ async function run(args) {
|
|
|
311
316
|
testMetricCalls: evaluator.unchargedCalls(),
|
|
312
317
|
testUsage: evaluator.unchargedUsage()
|
|
313
318
|
},
|
|
319
|
+
warnings,
|
|
314
320
|
stopReason
|
|
315
321
|
};
|
|
316
322
|
function withDemos(block) {
|
|
@@ -384,5 +390,5 @@ function countDemos(block) {
|
|
|
384
390
|
}
|
|
385
391
|
//#endregion
|
|
386
392
|
exports.BootstrapSearchOptimizer = BootstrapSearchOptimizer;
|
|
387
|
-
exports.isCandidateAccepted =
|
|
388
|
-
exports.isRunFinished =
|
|
393
|
+
exports.isCandidateAccepted = require_warnings.isCandidateAccepted;
|
|
394
|
+
exports.isRunFinished = require_warnings.isRunFinished;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as RunFinished,
|
|
1
|
+
import { S as UsageTotals, a as RunFinished, f as Adapter, i as Reporter, n as OptimizerEvent, o as isCandidateAccepted, p as Candidate, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted } from "../reporting-bq007_2z.cjs";
|
|
2
2
|
import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.cjs";
|
|
3
|
-
import { r as DemoRenderer } from "../demos-
|
|
4
|
-
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-
|
|
3
|
+
import { r as DemoRenderer } from "../demos-ByaLZy-Z.cjs";
|
|
4
|
+
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-4Zv-Zt2t.cjs";
|
|
5
5
|
//#region src/bootstrap-search/optimize.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Where a candidate's demo block came from. `zeroShot` holds no demos at all,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as RunFinished,
|
|
1
|
+
import { S as UsageTotals, a as RunFinished, f as Adapter, i as Reporter, n as OptimizerEvent, o as isCandidateAccepted, p as Candidate, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted } from "../reporting-bq007_2z.mjs";
|
|
2
2
|
import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.mjs";
|
|
3
|
-
import { r as DemoRenderer } from "../demos-
|
|
4
|
-
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-
|
|
3
|
+
import { r as DemoRenderer } from "../demos-ASsSXYXA.mjs";
|
|
4
|
+
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-Ds5mzYjz.mjs";
|
|
5
5
|
//#region src/bootstrap-search/optimize.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Where a candidate's demo block came from. `zeroShot` holds no demos at all,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { n as harvestFewShotExamples, t as formatDemos } from "../demos-
|
|
1
|
+
import { S as createBudget, _ as componentNames, a as instanceRow, b as defaultInstanceId, c as BudgetExhausted, d as measuredMean, f as requireMeasuredMean, g as runFingerprint, i as flushReporters, l as costExhausted, m as assertResumable, o as isCandidateAccepted, p as createDeadline, r as createEmitter, s as isRunFinished, t as resolveValidationSet, u as createEvaluator, v as candidateHash, y as createMemoryCache } from "../warnings-OxvDi9kN.mjs";
|
|
2
|
+
import { n as harvestFewShotExamples, t as formatDemos } from "../demos-Brobjfuc.mjs";
|
|
3
3
|
import { t as createSeededRng } from "../rng-Dtc5eZ_W.mjs";
|
|
4
4
|
//#region src/bootstrap-search/optimize.ts
|
|
5
5
|
const DEFAULT_CANDIDATES = 16;
|
|
@@ -50,7 +50,11 @@ var BootstrapSearchOptimizer = class {
|
|
|
50
50
|
async function run(args) {
|
|
51
51
|
const { config, task } = args;
|
|
52
52
|
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;
|
|
53
|
-
const { seedCandidate, trainingSet, validationSet
|
|
53
|
+
const { seedCandidate, trainingSet, validationSet: requestedValidationSet, testSet, adapter, demoComponents, renderDemo, goldOutput, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
|
|
54
|
+
const { validationSet, warnings } = resolveValidationSet({
|
|
55
|
+
validationSet: requestedValidationSet,
|
|
56
|
+
trainingSet
|
|
57
|
+
});
|
|
54
58
|
const emit = createEmitter(reporters);
|
|
55
59
|
const deadline = createDeadline({ maxWallClockMs });
|
|
56
60
|
const components = componentNames(seedCandidate);
|
|
@@ -287,6 +291,7 @@ async function run(args) {
|
|
|
287
291
|
emit({
|
|
288
292
|
type: "finish",
|
|
289
293
|
reason: stopReason,
|
|
294
|
+
warnings,
|
|
290
295
|
bestCandidateId: acceptedCandidates,
|
|
291
296
|
bestScore,
|
|
292
297
|
metricCalls: budget.spent(),
|
|
@@ -310,6 +315,7 @@ async function run(args) {
|
|
|
310
315
|
testMetricCalls: evaluator.unchargedCalls(),
|
|
311
316
|
testUsage: evaluator.unchargedUsage()
|
|
312
317
|
},
|
|
318
|
+
warnings,
|
|
313
319
|
stopReason
|
|
314
320
|
};
|
|
315
321
|
function withDemos(block) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_warnings = require("./warnings-CWRJF-jA.cjs");
|
|
2
2
|
//#region src/harvest.ts
|
|
3
3
|
/**
|
|
4
4
|
* Run a candidate over data and keep the rollouts the metric rewarded.
|
|
@@ -17,8 +17,8 @@ const require_reporting = require("./reporting-CNHzbJC-.cjs");
|
|
|
17
17
|
async function harvestRollouts(args) {
|
|
18
18
|
const { adapter, candidate, data, minScore, maxRollouts = Number.POSITIVE_INFINITY, batchSize = Math.min(maxRollouts, data.length), maxMetricCalls = data.length, maxCostUsd, rng, signal } = args;
|
|
19
19
|
if (data.length === 0) throw new Error("harvestRollouts requires non-empty data");
|
|
20
|
-
const budget =
|
|
21
|
-
const evaluator =
|
|
20
|
+
const budget = require_warnings.createBudget({ maxMetricCalls });
|
|
21
|
+
const evaluator = require_warnings.createEvaluator({
|
|
22
22
|
adapter,
|
|
23
23
|
budget,
|
|
24
24
|
...signal === void 0 ? {} : { signal }
|
|
@@ -28,7 +28,7 @@ async function harvestRollouts(args) {
|
|
|
28
28
|
let attempted = 0;
|
|
29
29
|
for (let start = 0; start < order.length; start += batchSize) {
|
|
30
30
|
if (rollouts.length >= maxRollouts || signal?.aborted) break;
|
|
31
|
-
if (
|
|
31
|
+
if (require_warnings.costExhausted({
|
|
32
32
|
usage: evaluator.usage(),
|
|
33
33
|
maxCostUsd
|
|
34
34
|
})) break;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as createBudget, l as costExhausted, u as createEvaluator } from "./warnings-OxvDi9kN.mjs";
|
|
2
2
|
//#region src/harvest.ts
|
|
3
3
|
/**
|
|
4
4
|
* Run a candidate over data and keep the rollouts the metric rewarded.
|
package/dist/file-cache.cjs
CHANGED
|
@@ -13,22 +13,33 @@ let node_path = require("node:path");
|
|
|
13
13
|
* Append-only rather than rewritten: a score is never invalidated (the key
|
|
14
14
|
* names the candidate, the instance, and the environment), and a log survives
|
|
15
15
|
* a process killed mid-write, which a file rewritten in place does not.
|
|
16
|
+
*
|
|
17
|
+
* `namespace` is what makes that invariant true. A cached score measures a
|
|
18
|
+
* whole system, not a candidate, and this log outlives every part of that
|
|
19
|
+
* system a run does not pass through the key: the model id behind an alias the
|
|
20
|
+
* provider upgraded, the decoding settings, the scorer's own version. It is
|
|
21
|
+
* required rather than optional because the failure it prevents is silent —
|
|
22
|
+
* scores from one system served to a run of another, with a normal-looking
|
|
23
|
+
* result and no way to read afterwards that it happened.
|
|
16
24
|
*/
|
|
17
25
|
function createFileCache(args) {
|
|
18
|
-
const { path, maxEntries = 1e6 } = args;
|
|
26
|
+
const { path, namespace, maxEntries = 1e6 } = args;
|
|
27
|
+
if (namespace.trim() === "") throw new Error("createFileCache requires a non-empty namespace naming the system these scores measure");
|
|
19
28
|
(0, node_fs.mkdirSync)((0, node_path.dirname)(path), { recursive: true });
|
|
20
29
|
const log = readLog(path);
|
|
21
30
|
const entries = log.entries;
|
|
31
|
+
const scope = (key) => `${namespace}\u0000${key}`;
|
|
22
32
|
if (log.unterminated) (0, node_fs.appendFileSync)(path, "\n");
|
|
23
33
|
return {
|
|
24
|
-
get: (key) => entries.get(key),
|
|
34
|
+
get: (key) => entries.get(scope(key)),
|
|
25
35
|
set: (key, cached) => {
|
|
26
|
-
|
|
36
|
+
const scoped = scope(key);
|
|
37
|
+
if (entries.size >= maxEntries && !entries.has(scoped)) {
|
|
27
38
|
const oldest = entries.keys().next();
|
|
28
39
|
if (!oldest.done) entries.delete(oldest.value);
|
|
29
40
|
}
|
|
30
|
-
entries.set(
|
|
31
|
-
(0, node_fs.appendFileSync)(path, `${JSON.stringify([
|
|
41
|
+
entries.set(scoped, cached);
|
|
42
|
+
(0, node_fs.appendFileSync)(path, `${JSON.stringify([scoped, cached])}\n`);
|
|
32
43
|
}
|
|
33
44
|
};
|
|
34
45
|
}
|
package/dist/file-cache.d.cts
CHANGED
|
@@ -11,9 +11,22 @@ import { n as EvaluationCache } from "./cache-CuSo0NJ8.cjs";
|
|
|
11
11
|
* Append-only rather than rewritten: a score is never invalidated (the key
|
|
12
12
|
* names the candidate, the instance, and the environment), and a log survives
|
|
13
13
|
* a process killed mid-write, which a file rewritten in place does not.
|
|
14
|
+
*
|
|
15
|
+
* `namespace` is what makes that invariant true. A cached score measures a
|
|
16
|
+
* whole system, not a candidate, and this log outlives every part of that
|
|
17
|
+
* system a run does not pass through the key: the model id behind an alias the
|
|
18
|
+
* provider upgraded, the decoding settings, the scorer's own version. It is
|
|
19
|
+
* required rather than optional because the failure it prevents is silent —
|
|
20
|
+
* scores from one system served to a run of another, with a normal-looking
|
|
21
|
+
* result and no way to read afterwards that it happened.
|
|
14
22
|
*/
|
|
15
23
|
declare function createFileCache(args: {
|
|
16
24
|
path: string;
|
|
25
|
+
/**
|
|
26
|
+
* Names the system these scores measure — model id, decoding settings,
|
|
27
|
+
* scorer version. Change it whenever any of them changes.
|
|
28
|
+
*/
|
|
29
|
+
namespace: string;
|
|
17
30
|
/** Entries kept in memory. The file itself is never trimmed. */
|
|
18
31
|
maxEntries?: number;
|
|
19
32
|
}): EvaluationCache;
|
package/dist/file-cache.d.mts
CHANGED
|
@@ -11,9 +11,22 @@ import { n as EvaluationCache } from "./cache-CuSo0NJ8.mjs";
|
|
|
11
11
|
* Append-only rather than rewritten: a score is never invalidated (the key
|
|
12
12
|
* names the candidate, the instance, and the environment), and a log survives
|
|
13
13
|
* a process killed mid-write, which a file rewritten in place does not.
|
|
14
|
+
*
|
|
15
|
+
* `namespace` is what makes that invariant true. A cached score measures a
|
|
16
|
+
* whole system, not a candidate, and this log outlives every part of that
|
|
17
|
+
* system a run does not pass through the key: the model id behind an alias the
|
|
18
|
+
* provider upgraded, the decoding settings, the scorer's own version. It is
|
|
19
|
+
* required rather than optional because the failure it prevents is silent —
|
|
20
|
+
* scores from one system served to a run of another, with a normal-looking
|
|
21
|
+
* result and no way to read afterwards that it happened.
|
|
14
22
|
*/
|
|
15
23
|
declare function createFileCache(args: {
|
|
16
24
|
path: string;
|
|
25
|
+
/**
|
|
26
|
+
* Names the system these scores measure — model id, decoding settings,
|
|
27
|
+
* scorer version. Change it whenever any of them changes.
|
|
28
|
+
*/
|
|
29
|
+
namespace: string;
|
|
17
30
|
/** Entries kept in memory. The file itself is never trimmed. */
|
|
18
31
|
maxEntries?: number;
|
|
19
32
|
}): EvaluationCache;
|
package/dist/file-cache.mjs
CHANGED
|
@@ -12,22 +12,33 @@ import { dirname } from "node:path";
|
|
|
12
12
|
* Append-only rather than rewritten: a score is never invalidated (the key
|
|
13
13
|
* names the candidate, the instance, and the environment), and a log survives
|
|
14
14
|
* a process killed mid-write, which a file rewritten in place does not.
|
|
15
|
+
*
|
|
16
|
+
* `namespace` is what makes that invariant true. A cached score measures a
|
|
17
|
+
* whole system, not a candidate, and this log outlives every part of that
|
|
18
|
+
* system a run does not pass through the key: the model id behind an alias the
|
|
19
|
+
* provider upgraded, the decoding settings, the scorer's own version. It is
|
|
20
|
+
* required rather than optional because the failure it prevents is silent —
|
|
21
|
+
* scores from one system served to a run of another, with a normal-looking
|
|
22
|
+
* result and no way to read afterwards that it happened.
|
|
15
23
|
*/
|
|
16
24
|
function createFileCache(args) {
|
|
17
|
-
const { path, maxEntries = 1e6 } = args;
|
|
25
|
+
const { path, namespace, maxEntries = 1e6 } = args;
|
|
26
|
+
if (namespace.trim() === "") throw new Error("createFileCache requires a non-empty namespace naming the system these scores measure");
|
|
18
27
|
mkdirSync(dirname(path), { recursive: true });
|
|
19
28
|
const log = readLog(path);
|
|
20
29
|
const entries = log.entries;
|
|
30
|
+
const scope = (key) => `${namespace}\u0000${key}`;
|
|
21
31
|
if (log.unterminated) appendFileSync(path, "\n");
|
|
22
32
|
return {
|
|
23
|
-
get: (key) => entries.get(key),
|
|
33
|
+
get: (key) => entries.get(scope(key)),
|
|
24
34
|
set: (key, cached) => {
|
|
25
|
-
|
|
35
|
+
const scoped = scope(key);
|
|
36
|
+
if (entries.size >= maxEntries && !entries.has(scoped)) {
|
|
26
37
|
const oldest = entries.keys().next();
|
|
27
38
|
if (!oldest.done) entries.delete(oldest.value);
|
|
28
39
|
}
|
|
29
|
-
entries.set(
|
|
30
|
-
appendFileSync(path, `${JSON.stringify([
|
|
40
|
+
entries.set(scoped, cached);
|
|
41
|
+
appendFileSync(path, `${JSON.stringify([scoped, cached])}\n`);
|
|
31
42
|
}
|
|
32
43
|
};
|
|
33
44
|
}
|