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.
Files changed (65) hide show
  1. package/README.md +65 -25
  2. package/dist/bootstrap-search/index.cjs +159 -73
  3. package/dist/bootstrap-search/index.d.cts +32 -10
  4. package/dist/bootstrap-search/index.d.mts +32 -10
  5. package/dist/bootstrap-search/index.mjs +150 -66
  6. package/dist/demos-9v5ts7F3.cjs +244 -0
  7. package/dist/{demos-B0pVQjYC.d.mts → demos-ASsSXYXA.d.mts} +10 -3
  8. package/dist/demos-Brobjfuc.mjs +215 -0
  9. package/dist/{demos-BTuzFNsp.d.cts → demos-ByaLZy-Z.d.cts} +10 -3
  10. package/dist/file-cache.cjs +27 -8
  11. package/dist/file-cache.d.cts +13 -0
  12. package/dist/file-cache.d.mts +13 -0
  13. package/dist/file-cache.mjs +27 -8
  14. package/dist/gepa/index.cjs +128 -80
  15. package/dist/gepa/index.d.cts +15 -7
  16. package/dist/gepa/index.d.mts +15 -7
  17. package/dist/gepa/index.mjs +101 -55
  18. package/dist/index.cjs +157 -30
  19. package/dist/index.d.cts +177 -7
  20. package/dist/index.d.mts +177 -7
  21. package/dist/index.mjs +139 -18
  22. package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
  23. package/dist/math-Dqme4rYz.mjs +123 -0
  24. package/dist/mipro/index.cjs +104 -70
  25. package/dist/mipro/index.d.cts +17 -14
  26. package/dist/mipro/index.d.mts +17 -14
  27. package/dist/mipro/index.mjs +90 -58
  28. package/dist/opro/index.cjs +136 -51
  29. package/dist/opro/index.d.cts +17 -9
  30. package/dist/opro/index.d.mts +17 -9
  31. package/dist/opro/index.mjs +121 -38
  32. package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-4Zv-Zt2t.d.cts} +90 -5
  33. package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ds5mzYjz.d.mts} +90 -5
  34. package/dist/random-search/index.cjs +99 -49
  35. package/dist/random-search/index.d.cts +15 -13
  36. package/dist/random-search/index.d.mts +15 -13
  37. package/dist/random-search/index.mjs +89 -41
  38. package/dist/{reflection-Cr_upzU0.d.mts → reflection-CMezGu6u.d.mts} +38 -14
  39. package/dist/{reflection-CQToe-5B.d.cts → reflection-D0A7eahD.d.cts} +38 -14
  40. package/dist/reporting-bq007_2z.d.cts +294 -0
  41. package/dist/reporting-bq007_2z.d.mts +294 -0
  42. package/dist/simba/index.cjs +216 -83
  43. package/dist/simba/index.d.cts +53 -13
  44. package/dist/simba/index.d.mts +53 -13
  45. package/dist/simba/index.mjs +206 -75
  46. package/dist/testing.cjs +1 -0
  47. package/dist/testing.d.cts +5 -3
  48. package/dist/testing.d.mts +5 -3
  49. package/dist/testing.mjs +1 -1
  50. package/dist/{evaluation-OZOp6TB7.cjs → warnings-CWRJF-jA.cjs} +228 -5
  51. package/dist/{evaluation-BV0nSZVx.mjs → warnings-OxvDi9kN.mjs} +175 -6
  52. package/docs/adapters.md +169 -0
  53. package/docs/benchmark.md +90 -0
  54. package/docs/data-prep.md +113 -0
  55. package/docs/distillation.md +128 -0
  56. package/docs/evaluation.md +87 -0
  57. package/docs/metric-preflight.md +132 -0
  58. package/docs/optimizers.md +293 -0
  59. package/docs/tuning.md +130 -0
  60. package/package.json +6 -4
  61. package/dist/demos-B9BJiNKz.cjs +0 -143
  62. package/dist/demos-Degx6UmP.mjs +0 -126
  63. package/dist/math-DhrDmpFS.mjs +0 -78
  64. package/dist/types-CWv4IQFF.d.cts +0 -129
  65. package/dist/types-CWv4IQFF.d.mts +0 -129
@@ -1,7 +1,7 @@
1
- import { n as Candidate, t as Adapter } from "../types-CWv4IQFF.cjs";
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-BTuzFNsp.cjs";
4
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-B7SpRwl7.cjs";
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,
@@ -35,6 +35,18 @@ interface BootstrapSearchConfig {
35
35
  * every candidate, which is the reliable reading and the expensive one.
36
36
  */
37
37
  stopAtScore?: number;
38
+ /**
39
+ * How many candidates may be swept at once. Default 1.
40
+ *
41
+ * Harvesting stays in plan order however this is set — every harvest draws
42
+ * from the same random stream, and reordering them would make a seeded run
43
+ * unreproducible — so what overlaps is a sweep with the harvest of the
44
+ * candidates behind it. Two costs come with raising it: a checkpoint is
45
+ * taken per wave rather than per candidate, so a killed run loses up to this
46
+ * many candidates instead of one, and `stopAtScore` is honoured by sweeping
47
+ * one at a time, since a wave cannot know it has already passed the target.
48
+ */
49
+ concurrency?: number;
38
50
  seed?: number;
39
51
  trackBestOutputs?: boolean;
40
52
  /**
@@ -61,6 +73,13 @@ interface BootstrapSearchSnapshot {
61
73
  metricCalls: number;
62
74
  bootstrapMetricCalls: number;
63
75
  cacheHits: number;
76
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
77
+ usage?: UsageTotals;
78
+ /**
79
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
80
+ * at zero makes a resumed run collide with the run it continues.
81
+ */
82
+ acceptedCandidates?: number;
64
83
  rngState: number;
65
84
  cache?: [string, CachedScore][];
66
85
  }
@@ -92,7 +111,8 @@ interface BootstrapSearchTask<Datum, Trajectory = unknown, Output = unknown, K e
92
111
  }) => string;
93
112
  /** Pass `false` to disable caching entirely. */
94
113
  cache?: EvaluationCache | false;
95
- onEvent?: (event: BootstrapSearchEvent<NoInfer<K>>) => void;
114
+ /** Observers of the run. Every one sees every event; none can fail it. */
115
+ reporters?: readonly Reporter<BootstrapSearchEvent<NoInfer<K>>>[];
96
116
  /** Called with a resumable snapshot after every candidate is scored. */
97
117
  onCheckpoint?: (snapshot: BootstrapSearchSnapshot) => void | Promise<void>;
98
118
  /** Snapshot to continue from. */
@@ -112,13 +132,15 @@ type BootstrapSearchEvent<K extends string = string> = {
112
132
  demos: number;
113
133
  score: number;
114
134
  accepted: boolean;
115
- } | {
135
+ } | ({
136
+ type: "candidateAccepted";
137
+ /** Which construction produced the demo block that won. */
138
+ source: DemoSource;
139
+ demos: number;
140
+ } & CandidateAccepted<K>) | ({
116
141
  type: "finish";
117
142
  reason: BootstrapSearchStopReason;
118
- bestScore: number;
119
- metricCalls: number;
120
- testScore?: number;
121
- };
143
+ } & RunFinished);
122
144
  interface BootstrapSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, BootstrapSearchStopReason, Output> {
123
145
  /** The seed's score, so the lift the demos bought is readable directly. */
124
146
  seedScore: number;
@@ -159,4 +181,4 @@ declare class BootstrapSearchOptimizer implements Optimizer<BootstrapSearchStopR
159
181
  optimize<Datum, Trajectory = unknown, Output = unknown, const K extends string = string>(task: BootstrapSearchTask<Datum, Trajectory, Output, K>): Promise<BootstrapSearchResult<K, Output>>;
160
182
  }
161
183
  //#endregion
162
- export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type DemoSource };
184
+ export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type CandidateAccepted, type DemoSource, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, isCandidateAccepted, isRunFinished };
@@ -1,7 +1,7 @@
1
- import { n as Candidate, t as Adapter } from "../types-CWv4IQFF.mjs";
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-B0pVQjYC.mjs";
4
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-DqCoth_w.mjs";
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,
@@ -35,6 +35,18 @@ interface BootstrapSearchConfig {
35
35
  * every candidate, which is the reliable reading and the expensive one.
36
36
  */
37
37
  stopAtScore?: number;
38
+ /**
39
+ * How many candidates may be swept at once. Default 1.
40
+ *
41
+ * Harvesting stays in plan order however this is set — every harvest draws
42
+ * from the same random stream, and reordering them would make a seeded run
43
+ * unreproducible — so what overlaps is a sweep with the harvest of the
44
+ * candidates behind it. Two costs come with raising it: a checkpoint is
45
+ * taken per wave rather than per candidate, so a killed run loses up to this
46
+ * many candidates instead of one, and `stopAtScore` is honoured by sweeping
47
+ * one at a time, since a wave cannot know it has already passed the target.
48
+ */
49
+ concurrency?: number;
38
50
  seed?: number;
39
51
  trackBestOutputs?: boolean;
40
52
  /**
@@ -61,6 +73,13 @@ interface BootstrapSearchSnapshot {
61
73
  metricCalls: number;
62
74
  bootstrapMetricCalls: number;
63
75
  cacheHits: number;
76
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
77
+ usage?: UsageTotals;
78
+ /**
79
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
80
+ * at zero makes a resumed run collide with the run it continues.
81
+ */
82
+ acceptedCandidates?: number;
64
83
  rngState: number;
65
84
  cache?: [string, CachedScore][];
66
85
  }
@@ -92,7 +111,8 @@ interface BootstrapSearchTask<Datum, Trajectory = unknown, Output = unknown, K e
92
111
  }) => string;
93
112
  /** Pass `false` to disable caching entirely. */
94
113
  cache?: EvaluationCache | false;
95
- onEvent?: (event: BootstrapSearchEvent<NoInfer<K>>) => void;
114
+ /** Observers of the run. Every one sees every event; none can fail it. */
115
+ reporters?: readonly Reporter<BootstrapSearchEvent<NoInfer<K>>>[];
96
116
  /** Called with a resumable snapshot after every candidate is scored. */
97
117
  onCheckpoint?: (snapshot: BootstrapSearchSnapshot) => void | Promise<void>;
98
118
  /** Snapshot to continue from. */
@@ -112,13 +132,15 @@ type BootstrapSearchEvent<K extends string = string> = {
112
132
  demos: number;
113
133
  score: number;
114
134
  accepted: boolean;
115
- } | {
135
+ } | ({
136
+ type: "candidateAccepted";
137
+ /** Which construction produced the demo block that won. */
138
+ source: DemoSource;
139
+ demos: number;
140
+ } & CandidateAccepted<K>) | ({
116
141
  type: "finish";
117
142
  reason: BootstrapSearchStopReason;
118
- bestScore: number;
119
- metricCalls: number;
120
- testScore?: number;
121
- };
143
+ } & RunFinished);
122
144
  interface BootstrapSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, BootstrapSearchStopReason, Output> {
123
145
  /** The seed's score, so the lift the demos bought is readable directly. */
124
146
  seedScore: number;
@@ -159,4 +181,4 @@ declare class BootstrapSearchOptimizer implements Optimizer<BootstrapSearchStopR
159
181
  optimize<Datum, Trajectory = unknown, Output = unknown, const K extends string = string>(task: BootstrapSearchTask<Datum, Trajectory, Output, K>): Promise<BootstrapSearchResult<K, Output>>;
160
182
  }
161
183
  //#endregion
162
- export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type DemoSource };
184
+ export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type CandidateAccepted, type DemoSource, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, isCandidateAccepted, isRunFinished };
@@ -1,5 +1,5 @@
1
- import { a as requireMeasuredMean, d as createMemoryCache, i as measuredMean, l as runFingerprint, n as costExhausted, o as createDeadline, p as createBudget, r as createEvaluator, s as assertResumable, t as BudgetExhausted, u as componentNames } from "../evaluation-BV0nSZVx.mjs";
2
- import { n as formatDemos, t as bootstrapDemos } from "../demos-Degx6UmP.mjs";
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;
@@ -33,19 +33,29 @@ const DEFAULT_MAX_LABELED_DEMOS = 16;
33
33
  var BootstrapSearchOptimizer = class {
34
34
  #config;
35
35
  constructor(config = {}) {
36
+ assertBootstrapSearchConfig(config);
36
37
  this.#config = config;
37
38
  }
38
- optimize(task) {
39
- return run({
40
- config: this.#config,
41
- task
42
- });
39
+ async optimize(task) {
40
+ try {
41
+ return await run({
42
+ config: this.#config,
43
+ task
44
+ });
45
+ } finally {
46
+ await flushReporters(task.reporters ?? []);
47
+ }
43
48
  }
44
49
  };
45
50
  async function run(args) {
46
51
  const { config, task } = args;
47
- 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;
48
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, demoComponents, renderDemo, goldOutput, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
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: 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
+ });
58
+ const emit = createEmitter(reporters);
49
59
  const deadline = createDeadline({ maxWallClockMs });
50
60
  const components = componentNames(seedCandidate);
51
61
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
@@ -88,8 +98,9 @@ async function run(args) {
88
98
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
89
99
  trackOutputs: trackBestOutputs,
90
100
  cacheHits: resumeFrom?.cacheHits ?? 0,
101
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
91
102
  ...signal === void 0 ? {} : { signal },
92
- onEvaluation: (event) => onEvent?.({
103
+ onEvaluation: (event) => emit({
93
104
  type: "evaluation",
94
105
  ...event
95
106
  })
@@ -99,7 +110,7 @@ async function run(args) {
99
110
  let drawn = resumeFrom?.drawn ?? 0;
100
111
  let bootstrapMetricCalls = resumeFrom?.bootstrapMetricCalls ?? 0;
101
112
  let stopReason = "candidatesExhausted";
102
- onEvent?.({
113
+ emit({
103
114
  type: "start",
104
115
  components,
105
116
  validationSetSize: validationSet.length
@@ -115,13 +126,25 @@ async function run(args) {
115
126
  iteration: evaluated.length
116
127
  });
117
128
  }
118
- const seedScore = resumeFrom?.seedScore ?? requireMeasuredMean({
119
- batch: await sweep(seedCandidate, "seed"),
129
+ const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
130
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : requireMeasuredMean({
131
+ batch: seedEvaluation,
120
132
  phase: "seed"
121
133
  });
122
134
  let best = resumeFrom?.best ?? seedCandidate;
123
135
  let bestScore = resumeFrom?.bestScore ?? seedScore;
124
136
  let bestOutputs;
137
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
138
+ if (seedEvaluation !== void 0) emit({
139
+ type: "candidateAccepted",
140
+ source: "zeroShot",
141
+ demos: 0,
142
+ candidateId: 0,
143
+ candidate: seedCandidate,
144
+ aggregateScore: seedScore,
145
+ instanceScores: instanceRow(seedEvaluation),
146
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
147
+ });
125
148
  function takeSnapshot() {
126
149
  const cached = checkpointCache ? evaluationCache?.entries?.() : void 0;
127
150
  return {
@@ -135,6 +158,8 @@ async function run(args) {
135
158
  metricCalls: budget.spent(),
136
159
  bootstrapMetricCalls,
137
160
  cacheHits: evaluator.cacheHits(),
161
+ usage: evaluator.usage(),
162
+ acceptedCandidates,
138
163
  rngState: rng.state(),
139
164
  ...cached === void 0 ? {} : { cache: cached }
140
165
  };
@@ -147,7 +172,8 @@ async function run(args) {
147
172
  shuffledHarvests,
148
173
  labeled: goldOutput !== void 0
149
174
  });
150
- for (; drawn < plan.length; drawn += 1) {
175
+ const waveSize = stopAtScore === void 0 ? concurrency : 1;
176
+ while (drawn < plan.length) {
151
177
  if (signal?.aborted) {
152
178
  stopReason = "aborted";
153
179
  break;
@@ -163,58 +189,95 @@ async function run(args) {
163
189
  stopReason = "deadlineReached";
164
190
  break;
165
191
  }
166
- if (!budget.canAfford(validationSet.length + 1)) {
167
- stopReason = "budgetExhausted";
168
- break;
169
- }
170
- const source = plan[drawn];
171
- const block = await buildBlock(source);
172
- const candidate = withDemos(block);
173
- let evaluation;
174
- try {
175
- evaluation = await sweep(candidate, "validation");
176
- } catch (err) {
177
- if (err instanceof BudgetExhausted) {
178
- stopReason = "budgetExhausted";
179
- break;
180
- }
181
- if (signal?.aborted) {
182
- stopReason = "aborted";
192
+ const wave = [];
193
+ const inFlight = /* @__PURE__ */ new Map();
194
+ let waveStop;
195
+ while (wave.length < waveSize && drawn + wave.length < plan.length) {
196
+ if (!budget.canAfford(validationSet.length + 1)) {
197
+ waveStop = "budgetExhausted";
183
198
  break;
184
199
  }
185
- throw err;
200
+ const source = plan[drawn + wave.length];
201
+ const block = await buildBlock(source);
202
+ const candidate = withDemos(block);
203
+ const key = candidateHash(candidate);
204
+ const prior = inFlight.get(key);
205
+ const sweeping = prior === void 0 ? settled(sweep(candidate, "validation")) : prior.then(() => settled(sweep(candidate, "validation")));
206
+ inFlight.set(key, sweeping);
207
+ wave.push({
208
+ source,
209
+ candidate,
210
+ block,
211
+ sweep: sweeping
212
+ });
186
213
  }
187
- const score = measuredMean(evaluation);
188
- if (score === void 0) continue;
189
- const accepted = score > bestScore;
190
- evaluated.push({
191
- candidate,
192
- source,
193
- demos: countDemos(block),
194
- score
195
- });
196
- onEvent?.({
197
- type: "candidate",
198
- index: evaluated.length - 1,
199
- source,
200
- demos: countDemos(block),
201
- score,
202
- accepted
203
- });
204
- if (accepted) {
205
- best = candidate;
206
- bestScore = score;
207
- bestOutputs = evaluation.outputs;
214
+ let waveError;
215
+ let draining = false;
216
+ for (const entry of wave) {
217
+ const outcome = await entry.sweep;
218
+ if (draining) continue;
219
+ drawn += 1;
220
+ if (outcome.failed === true) {
221
+ draining = true;
222
+ if (outcome.err instanceof BudgetExhausted) {
223
+ waveStop = "budgetExhausted";
224
+ continue;
225
+ }
226
+ if (signal?.aborted) {
227
+ waveStop = "aborted";
228
+ continue;
229
+ }
230
+ waveError = outcome.err;
231
+ continue;
232
+ }
233
+ const evaluation = outcome.evaluation;
234
+ const score = measuredMean(evaluation);
235
+ if (score === void 0) continue;
236
+ const accepted = score > bestScore;
237
+ evaluated.push({
238
+ candidate: entry.candidate,
239
+ source: entry.source,
240
+ demos: countDemos(entry.block),
241
+ score
242
+ });
243
+ emit({
244
+ type: "candidate",
245
+ index: evaluated.length - 1,
246
+ source: entry.source,
247
+ demos: countDemos(entry.block),
248
+ score,
249
+ accepted
250
+ });
251
+ if (accepted) {
252
+ acceptedCandidates += 1;
253
+ emit({
254
+ type: "candidateAccepted",
255
+ source: entry.source,
256
+ demos: countDemos(entry.block),
257
+ candidateId: acceptedCandidates,
258
+ candidate: entry.candidate,
259
+ aggregateScore: score,
260
+ instanceScores: instanceRow(evaluation),
261
+ ...trackBestOutputs ? { outputs: evaluation.outputs } : {}
262
+ });
263
+ best = entry.candidate;
264
+ bestScore = score;
265
+ bestOutputs = evaluation.outputs;
266
+ }
267
+ if (stopAtScore !== void 0 && score >= stopAtScore) {
268
+ waveStop = "scoreReached";
269
+ draining = true;
270
+ }
208
271
  }
272
+ if (waveError !== void 0) throw waveError;
209
273
  await checkpoint();
210
- if (stopAtScore !== void 0 && score >= stopAtScore) {
211
- stopReason = "scoreReached";
212
- drawn += 1;
274
+ if (waveStop !== void 0) {
275
+ stopReason = waveStop;
213
276
  break;
214
277
  }
215
278
  }
216
279
  if (signal?.aborted) stopReason = "aborted";
217
- const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
280
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
218
281
  candidate: best,
219
282
  batch: testSet,
220
283
  ids: testIds,
@@ -223,13 +286,18 @@ async function run(args) {
223
286
  candidateId: null,
224
287
  iteration: evaluated.length,
225
288
  charge: false
226
- }));
227
- onEvent?.({
289
+ });
290
+ const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
291
+ emit({
228
292
  type: "finish",
229
293
  reason: stopReason,
294
+ warnings,
295
+ bestCandidateId: acceptedCandidates,
230
296
  bestScore,
231
297
  metricCalls: budget.spent(),
232
- ...testScore === void 0 ? {} : { testScore }
298
+ ...testScore === void 0 ? {} : { testScore },
299
+ ...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
300
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
233
301
  });
234
302
  return {
235
303
  bestCandidate: best,
@@ -244,8 +312,10 @@ async function run(args) {
244
312
  ...trackBestOutputs && bestOutputs !== void 0 ? { bestOutputs } : {},
245
313
  ...testScore === void 0 ? {} : {
246
314
  testScore,
247
- testMetricCalls: testSet?.length ?? 0
315
+ testMetricCalls: evaluator.unchargedCalls(),
316
+ testUsage: evaluator.unchargedUsage()
248
317
  },
318
+ warnings,
249
319
  stopReason
250
320
  };
251
321
  function withDemos(block) {
@@ -259,19 +329,21 @@ async function run(args) {
259
329
  const requested = source === "unshuffled" ? maxDemos : minDemos + rng.nextInt(Math.max(1, maxDemos - minDemos + 1));
260
330
  const affordable = Math.min(trainingSet.length, budget.remaining() - validationSet.length);
261
331
  if (affordable < 1) return "";
262
- const harvest = await bootstrapDemos({
332
+ const harvest = await harvestFewShotExamples({
263
333
  adapter,
264
334
  candidate: seedCandidate,
265
335
  trainingSet,
266
336
  ...demoMinScore === void 0 ? {} : { minScore: demoMinScore },
267
337
  maxDemos: requested,
268
338
  maxMetricCalls: affordable,
339
+ ...maxCostUsd === void 0 ? {} : { maxCostUsd: maxCostUsd - evaluator.usage().costUsd },
269
340
  ...source === "unshuffled" ? {} : { rng },
270
341
  ...renderDemo === void 0 ? {} : { renderDemo },
271
342
  ...signal === void 0 ? {} : { signal }
272
343
  });
273
344
  bootstrapMetricCalls += harvest.metricCalls;
274
345
  budget.reserve(harvest.metricCalls);
346
+ evaluator.absorbUsage(harvest.usage);
275
347
  return harvest.block;
276
348
  }
277
349
  function labeledBlock() {
@@ -297,11 +369,23 @@ function candidatePlan(args) {
297
369
  ...Array.from({ length: shuffledHarvests }, () => "bootstrapped")
298
370
  ];
299
371
  }
372
+ /** Turns a sweep into a value, so a dispatched one never rejects unobserved. */
373
+ function settled(sweeping) {
374
+ return sweeping.then((evaluation) => ({ evaluation }), (err) => ({
375
+ failed: true,
376
+ err
377
+ }));
378
+ }
379
+ /**
380
+ * Range checks on the search knobs, run at construction so a configuration
381
+ * that could never terminate is refused before a task is ever handed to it.
382
+ */
383
+ function assertBootstrapSearchConfig(config) {
384
+ const { concurrency = 1 } = config;
385
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
386
+ }
300
387
  function countDemos(block) {
301
388
  return block.split("<demo>").length - 1;
302
389
  }
303
- function defaultInstanceId(args) {
304
- return String(args.index);
305
- }
306
390
  //#endregion
307
- export { BootstrapSearchOptimizer };
391
+ export { BootstrapSearchOptimizer, isCandidateAccepted, isRunFinished };