textopt 0.0.0 → 0.1.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 (55) hide show
  1. package/README.md +44 -22
  2. package/dist/bootstrap-search/index.cjs +153 -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 +144 -66
  6. package/dist/{demos-B0pVQjYC.d.mts → demos-1b7JiUue.d.mts} +10 -3
  7. package/dist/{demos-BTuzFNsp.d.cts → demos-CU9dy2oT.d.cts} +10 -3
  8. package/dist/demos-D2o0qSSo.cjs +244 -0
  9. package/dist/demos-DE2oxNWX.mjs +215 -0
  10. package/dist/file-cache.cjs +11 -3
  11. package/dist/file-cache.mjs +11 -3
  12. package/dist/gepa/index.cjs +76 -71
  13. package/dist/gepa/index.d.cts +12 -6
  14. package/dist/gepa/index.d.mts +12 -6
  15. package/dist/gepa/index.mjs +49 -46
  16. package/dist/index.cjs +129 -27
  17. package/dist/index.d.cts +145 -7
  18. package/dist/index.d.mts +145 -7
  19. package/dist/index.mjs +113 -15
  20. package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
  21. package/dist/math-Dqme4rYz.mjs +123 -0
  22. package/dist/mipro/index.cjs +98 -70
  23. package/dist/mipro/index.d.cts +17 -14
  24. package/dist/mipro/index.d.mts +17 -14
  25. package/dist/mipro/index.mjs +84 -58
  26. package/dist/opro/index.cjs +130 -51
  27. package/dist/opro/index.d.cts +17 -9
  28. package/dist/opro/index.d.mts +17 -9
  29. package/dist/opro/index.mjs +115 -38
  30. package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-Bh5DPRMH.d.cts} +50 -4
  31. package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ck6-e_8o.d.mts} +50 -4
  32. package/dist/random-search/index.cjs +93 -49
  33. package/dist/random-search/index.d.cts +15 -13
  34. package/dist/random-search/index.d.mts +15 -13
  35. package/dist/random-search/index.mjs +83 -41
  36. package/dist/{reflection-CQToe-5B.d.cts → reflection-Dt3QrXhM.d.cts} +7 -11
  37. package/dist/{reflection-Cr_upzU0.d.mts → reflection-LRaAZP4e.d.mts} +7 -11
  38. package/dist/{evaluation-OZOp6TB7.cjs → reporting-CNHzbJC-.cjs} +165 -5
  39. package/dist/reporting-DQbAohc9.d.cts +240 -0
  40. package/dist/reporting-DQbAohc9.d.mts +240 -0
  41. package/dist/{evaluation-BV0nSZVx.mjs → reporting-DY-DC4HG.mjs} +124 -6
  42. package/dist/simba/index.cjs +210 -83
  43. package/dist/simba/index.d.cts +32 -11
  44. package/dist/simba/index.d.mts +32 -11
  45. package/dist/simba/index.mjs +200 -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/package.json +4 -3
  51. package/dist/demos-B9BJiNKz.cjs +0 -143
  52. package/dist/demos-Degx6UmP.mjs +0 -126
  53. package/dist/math-DhrDmpFS.mjs +0 -78
  54. package/dist/types-CWv4IQFF.d.cts +0 -129
  55. package/dist/types-CWv4IQFF.d.mts +0 -129
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_evaluation = require("../evaluation-OZOp6TB7.cjs");
2
+ const require_reporting = require("../reporting-CNHzbJC-.cjs");
3
3
  const require_concurrency = require("../concurrency-C-cFzWW2.cjs");
4
4
  const require_text = require("../text-CK_HB3su.cjs");
5
5
  //#region src/random-search/optimize.ts
@@ -33,10 +33,14 @@ var RandomSearchOptimizer = class {
33
33
  this.#config = config;
34
34
  }
35
35
  async optimize(task) {
36
- return runRandomSearch({
37
- config: this.#config,
38
- task
39
- });
36
+ try {
37
+ return await runRandomSearch({
38
+ config: this.#config,
39
+ task
40
+ });
41
+ } finally {
42
+ await require_reporting.flushReporters(task.reporters ?? []);
43
+ }
40
44
  }
41
45
  };
42
46
  /**
@@ -63,9 +67,10 @@ function buildParaphrasePrompt(args) {
63
67
  async function runRandomSearch(args) {
64
68
  const { config, task } = args;
65
69
  const { variants = DEFAULT_VARIANTS, concurrency = 1, maxRounds = Number.POSITIVE_INFINITY, buildPrompt = buildParaphrasePrompt, trackBestOutputs = false, checkpointCache = true } = config;
66
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
67
- const deadline = require_evaluation.createDeadline({ maxWallClockMs });
68
- const components = require_evaluation.componentNames(seedCandidate);
70
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_reporting.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
71
+ const emit = require_reporting.createEmitter(reporters);
72
+ const deadline = require_reporting.createDeadline({ maxWallClockMs });
73
+ const components = require_reporting.componentNames(seedCandidate);
69
74
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
70
75
  if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
71
76
  if (components.length === 0) throw new Error("optimize requires a seed candidate with at least one component");
@@ -78,7 +83,7 @@ async function runRandomSearch(args) {
78
83
  datum,
79
84
  index
80
85
  })) ?? [];
81
- const fingerprint = require_evaluation.runFingerprint({
86
+ const fingerprint = require_reporting.runFingerprint({
82
87
  seedCandidate,
83
88
  trainingIds: trainingSet.map((datum, index) => instanceId({
84
89
  datum,
@@ -87,16 +92,16 @@ async function runRandomSearch(args) {
87
92
  validationIds,
88
93
  ...cacheNamespace === void 0 ? {} : { cacheNamespace }
89
94
  });
90
- require_evaluation.assertResumable({
95
+ require_reporting.assertResumable({
91
96
  fingerprint,
92
97
  ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
93
98
  });
94
- const budget = require_evaluation.createBudget({
99
+ const budget = require_reporting.createBudget({
95
100
  maxMetricCalls,
96
101
  spent: resumeFrom?.metricCalls ?? 0
97
102
  });
98
- const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
99
- const evaluator = require_evaluation.createEvaluator({
103
+ const evaluationCache = cache === false ? void 0 : cache ?? require_reporting.createMemoryCache();
104
+ const evaluator = require_reporting.createEvaluator({
100
105
  adapter,
101
106
  budget,
102
107
  ...retry === void 0 ? {} : { retry },
@@ -104,8 +109,9 @@ async function runRandomSearch(args) {
104
109
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
105
110
  trackOutputs: trackBestOutputs,
106
111
  cacheHits: resumeFrom?.cacheHits ?? 0,
112
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
107
113
  ...signal === void 0 ? {} : { signal },
108
- onEvaluation: (event) => onEvent?.({
114
+ onEvaluation: (event) => emit({
109
115
  type: "evaluation",
110
116
  ...event
111
117
  })
@@ -123,7 +129,7 @@ async function runRandomSearch(args) {
123
129
  * no budget here bounds.
124
130
  */
125
131
  let stalledRounds = 0;
126
- onEvent?.({
132
+ emit({
127
133
  type: "start",
128
134
  components,
129
135
  validationSetSize: validationSet.length
@@ -152,6 +158,8 @@ async function runRandomSearch(args) {
152
158
  reflectionCalls,
153
159
  metricCalls: budget.spent(),
154
160
  cacheHits: evaluator.cacheHits(),
161
+ usage: evaluator.usage(),
162
+ acceptedCandidates,
155
163
  ...cached === void 0 ? {} : { cache: cached }
156
164
  };
157
165
  }
@@ -163,21 +171,31 @@ async function runRandomSearch(args) {
163
171
  candidate: seedCandidate,
164
172
  phase: "seed"
165
173
  }) : void 0;
166
- const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_evaluation.requireMeasuredMean({
174
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_reporting.requireMeasuredMean({
167
175
  batch: seedEvaluation,
168
176
  phase: "seed"
169
177
  });
178
+ if (seedEvaluation !== void 0) emit({
179
+ type: "candidateAccepted",
180
+ round: 0,
181
+ candidateId: 0,
182
+ candidate: seedCandidate,
183
+ aggregateScore: seedScore,
184
+ instanceScores: require_reporting.instanceRow(seedEvaluation),
185
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
186
+ });
170
187
  let best = resumeFrom?.best ?? seedCandidate;
171
188
  let bestScore = resumeFrom?.bestScore ?? seedScore;
172
189
  /** Absent on a resumed run until a variant wins: outputs are not checkpointed. */
173
190
  let bestOutputs = seedEvaluation?.outputs;
191
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
174
192
  await checkpoint();
175
193
  while (round < maxRounds) {
176
194
  if (signal?.aborted) {
177
195
  stopReason = "aborted";
178
196
  break;
179
197
  }
180
- if (require_evaluation.costExhausted({
198
+ if (require_reporting.costExhausted({
181
199
  usage: evaluator.usage(),
182
200
  maxCostUsd
183
201
  })) {
@@ -188,12 +206,12 @@ async function runRandomSearch(args) {
188
206
  stopReason = "deadlineReached";
189
207
  break;
190
208
  }
191
- if (!budget.canAfford(variants * validationSet.length)) {
209
+ if (!budget.canAfford(validationSet.length)) {
192
210
  stopReason = "budgetExhausted";
193
211
  break;
194
212
  }
195
213
  const component = components[round % components.length];
196
- onEvent?.({
214
+ emit({
197
215
  type: "roundStart",
198
216
  round,
199
217
  component
@@ -220,41 +238,64 @@ async function runRandomSearch(args) {
220
238
  });
221
239
  const unique = [...new Set(drawn)].filter((text) => text.length > 0 && text !== currentText);
222
240
  let roundStop;
241
+ const scheduled = [];
242
+ let owed = 0;
223
243
  for (const text of unique) {
224
244
  const candidate = {
225
245
  ...best,
226
246
  [component]: text
227
247
  };
228
- let evaluation;
229
- try {
230
- evaluation = await sweep({
231
- candidate,
232
- phase: "validation"
233
- });
234
- } catch (err) {
235
- if (err instanceof require_evaluation.BudgetExhausted) {
236
- roundStop = "budgetExhausted";
237
- break;
238
- }
239
- if (signal?.aborted) {
240
- roundStop = "aborted";
241
- break;
248
+ const uncached = evaluator.countUncached({
249
+ candidate,
250
+ ids: validationIds,
251
+ split: "val"
252
+ });
253
+ if (!budget.canAfford(owed + uncached)) {
254
+ roundStop = "budgetExhausted";
255
+ break;
256
+ }
257
+ owed += uncached;
258
+ scheduled.push(candidate);
259
+ }
260
+ const swept = await require_concurrency.mapWithConcurrency({
261
+ items: scheduled,
262
+ limit: concurrency,
263
+ task: async (candidate) => {
264
+ if (signal?.aborted) return { stop: "aborted" };
265
+ try {
266
+ return { evaluation: await sweep({
267
+ candidate,
268
+ phase: "validation"
269
+ }) };
270
+ } catch (err) {
271
+ if (err instanceof require_reporting.BudgetExhausted) return { stop: "budgetExhausted" };
272
+ if (signal?.aborted) return { stop: "aborted" };
273
+ throw err;
242
274
  }
243
- throw err;
275
+ }
276
+ });
277
+ for (const [index, outcome] of swept.entries()) {
278
+ if (outcome.stop !== void 0) {
279
+ roundStop = outcome.stop;
280
+ break;
244
281
  }
245
282
  variantsEvaluated += 1;
246
- const score = require_evaluation.measuredMean(evaluation);
283
+ const score = require_reporting.measuredMean(outcome.evaluation);
247
284
  if (score !== void 0 && score > bestScore) {
248
- onEvent?.({
285
+ const candidate = scheduled[index];
286
+ acceptedCandidates += 1;
287
+ emit({
249
288
  type: "candidateAccepted",
250
289
  round,
251
- component,
252
- score,
253
- previousScore: bestScore
290
+ candidateId: acceptedCandidates,
291
+ candidate,
292
+ aggregateScore: score,
293
+ instanceScores: require_reporting.instanceRow(outcome.evaluation),
294
+ ...trackBestOutputs ? { outputs: outcome.evaluation.outputs } : {}
254
295
  });
255
296
  best = candidate;
256
297
  bestScore = score;
257
- bestOutputs = evaluation.outputs;
298
+ bestOutputs = outcome.evaluation.outputs;
258
299
  }
259
300
  }
260
301
  round += 1;
@@ -270,7 +311,7 @@ async function runRandomSearch(args) {
270
311
  }
271
312
  }
272
313
  if (signal?.aborted) stopReason = "aborted";
273
- const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
314
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
274
315
  candidate: best,
275
316
  batch: testSet,
276
317
  ids: testIds,
@@ -279,13 +320,17 @@ async function runRandomSearch(args) {
279
320
  candidateId: null,
280
321
  iteration: round,
281
322
  charge: false
282
- }));
283
- onEvent?.({
323
+ });
324
+ const testScore = heldOut === void 0 ? void 0 : require_reporting.measuredMean(heldOut);
325
+ emit({
284
326
  type: "finish",
285
327
  reason: stopReason,
328
+ bestCandidateId: acceptedCandidates,
286
329
  bestScore,
287
330
  metricCalls: budget.spent(),
288
- ...testScore === void 0 ? {} : { testScore }
331
+ ...testScore === void 0 ? {} : { testScore },
332
+ ...heldOut === void 0 ? {} : { testInstanceScores: require_reporting.instanceRow(heldOut) },
333
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
289
334
  });
290
335
  return {
291
336
  bestCandidate: best,
@@ -296,7 +341,8 @@ async function runRandomSearch(args) {
296
341
  ...trackBestOutputs ? { bestOutputs } : {},
297
342
  ...testScore === void 0 ? {} : {
298
343
  testScore,
299
- testMetricCalls: evaluator.unchargedCalls()
344
+ testMetricCalls: evaluator.unchargedCalls(),
345
+ testUsage: evaluator.unchargedUsage()
300
346
  },
301
347
  rounds: round,
302
348
  variantsEvaluated,
@@ -312,10 +358,8 @@ function assertConfig(config) {
312
358
  if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
313
359
  if (maxRounds !== void 0 && (!Number.isInteger(maxRounds) || maxRounds < 1)) throw new Error(`maxRounds must be a positive integer, received ${maxRounds}`);
314
360
  }
315
- function defaultInstanceId(args) {
316
- const hash = require_evaluation.stableHash(args.datum);
317
- return hash === "" ? String(args.index) : hash;
318
- }
319
361
  //#endregion
320
362
  exports.RandomSearchOptimizer = RandomSearchOptimizer;
321
363
  exports.buildParaphrasePrompt = buildParaphrasePrompt;
364
+ exports.isCandidateAccepted = require_reporting.isCandidateAccepted;
365
+ exports.isRunFinished = require_reporting.isRunFinished;
@@ -1,6 +1,6 @@
1
- import { n as Candidate, t as Adapter, u as TextModel } from "../types-CWv4IQFF.cjs";
1
+ import { _ as TextModel, a as RunFinished, c as Adapter, i as Reporter, l as Candidate, n as OptimizerEvent, o as isCandidateAccepted, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted, v as UsageTotals } from "../reporting-DQbAohc9.cjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.cjs";
3
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-B7SpRwl7.cjs";
3
+ import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-Bh5DPRMH.cjs";
4
4
  //#region src/random-search/optimize.d.ts
5
5
  /** Builds the prompt one variant is drawn from. */
6
6
  type ParaphrasePromptBuilder = (args: {
@@ -54,6 +54,13 @@ interface RandomSearchSnapshot {
54
54
  reflectionCalls: number;
55
55
  metricCalls: number;
56
56
  cacheHits: number;
57
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
58
+ usage?: UsageTotals;
59
+ /**
60
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
61
+ * at zero makes a resumed run collide with the run it continues.
62
+ */
63
+ acceptedCandidates?: number;
57
64
  /** Cached instance scores, when the cache can enumerate them. */
58
65
  cache?: [string, CachedScore][];
59
66
  }
@@ -71,7 +78,8 @@ interface RandomSearchTask<Datum, Trajectory = unknown, Output = unknown, K exte
71
78
  }) => string;
72
79
  /** Pass `false` to disable caching entirely. */
73
80
  cache?: EvaluationCache | false;
74
- onEvent?: (event: RandomSearchEvent<NoInfer<K>>) => void;
81
+ /** Observers of the run. Every one sees every event; none can fail it. */
82
+ reporters?: readonly Reporter<RandomSearchEvent<NoInfer<K>>>[];
75
83
  /**
76
84
  * Called with a resumable snapshot after the seed is scored and after every
77
85
  * round. Persist it and a killed run costs the last round, not all of them.
@@ -91,19 +99,13 @@ type RandomSearchEvent<K extends string = string> = {
91
99
  component: K;
92
100
  } | ({
93
101
  type: "evaluation";
94
- } & EvaluationEvent) | {
102
+ } & EvaluationEvent) | ({
95
103
  type: "candidateAccepted";
96
104
  round: number;
97
- component: K;
98
- score: number;
99
- previousScore: number;
100
- } | {
105
+ } & CandidateAccepted<K>) | ({
101
106
  type: "finish";
102
107
  reason: RandomSearchStopReason;
103
- bestScore: number;
104
- metricCalls: number;
105
- testScore?: number;
106
- };
108
+ } & RunFinished);
107
109
  interface RandomSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, RandomSearchStopReason, Output> {
108
110
  /** The seed's score, so the lift the search bought is readable directly. */
109
111
  seedScore: number;
@@ -153,4 +155,4 @@ declare function buildParaphrasePrompt(args: {
153
155
  attempt: number;
154
156
  }): string;
155
157
  //#endregion
156
- export { type ParaphrasePromptBuilder, type RandomSearchConfig, type RandomSearchEvent, RandomSearchOptimizer, type RandomSearchResult, type RandomSearchSnapshot, type RandomSearchStopReason, type RandomSearchTask, buildParaphrasePrompt };
158
+ export { type CandidateAccepted, type OptimizerEvent, type ParaphrasePromptBuilder, type RandomSearchConfig, type RandomSearchEvent, RandomSearchOptimizer, type RandomSearchResult, type RandomSearchSnapshot, type RandomSearchStopReason, type RandomSearchTask, type ReportableEvent, type Reporter, type RunFinished, buildParaphrasePrompt, isCandidateAccepted, isRunFinished };
@@ -1,6 +1,6 @@
1
- import { n as Candidate, t as Adapter, u as TextModel } from "../types-CWv4IQFF.mjs";
1
+ import { _ as TextModel, a as RunFinished, c as Adapter, i as Reporter, l as Candidate, n as OptimizerEvent, o as isCandidateAccepted, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted, v as UsageTotals } from "../reporting-DQbAohc9.mjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.mjs";
3
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-DqCoth_w.mjs";
3
+ import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-Ck6-e_8o.mjs";
4
4
  //#region src/random-search/optimize.d.ts
5
5
  /** Builds the prompt one variant is drawn from. */
6
6
  type ParaphrasePromptBuilder = (args: {
@@ -54,6 +54,13 @@ interface RandomSearchSnapshot {
54
54
  reflectionCalls: number;
55
55
  metricCalls: number;
56
56
  cacheHits: number;
57
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
58
+ usage?: UsageTotals;
59
+ /**
60
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
61
+ * at zero makes a resumed run collide with the run it continues.
62
+ */
63
+ acceptedCandidates?: number;
57
64
  /** Cached instance scores, when the cache can enumerate them. */
58
65
  cache?: [string, CachedScore][];
59
66
  }
@@ -71,7 +78,8 @@ interface RandomSearchTask<Datum, Trajectory = unknown, Output = unknown, K exte
71
78
  }) => string;
72
79
  /** Pass `false` to disable caching entirely. */
73
80
  cache?: EvaluationCache | false;
74
- onEvent?: (event: RandomSearchEvent<NoInfer<K>>) => void;
81
+ /** Observers of the run. Every one sees every event; none can fail it. */
82
+ reporters?: readonly Reporter<RandomSearchEvent<NoInfer<K>>>[];
75
83
  /**
76
84
  * Called with a resumable snapshot after the seed is scored and after every
77
85
  * round. Persist it and a killed run costs the last round, not all of them.
@@ -91,19 +99,13 @@ type RandomSearchEvent<K extends string = string> = {
91
99
  component: K;
92
100
  } | ({
93
101
  type: "evaluation";
94
- } & EvaluationEvent) | {
102
+ } & EvaluationEvent) | ({
95
103
  type: "candidateAccepted";
96
104
  round: number;
97
- component: K;
98
- score: number;
99
- previousScore: number;
100
- } | {
105
+ } & CandidateAccepted<K>) | ({
101
106
  type: "finish";
102
107
  reason: RandomSearchStopReason;
103
- bestScore: number;
104
- metricCalls: number;
105
- testScore?: number;
106
- };
108
+ } & RunFinished);
107
109
  interface RandomSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, RandomSearchStopReason, Output> {
108
110
  /** The seed's score, so the lift the search bought is readable directly. */
109
111
  seedScore: number;
@@ -153,4 +155,4 @@ declare function buildParaphrasePrompt(args: {
153
155
  attempt: number;
154
156
  }): string;
155
157
  //#endregion
156
- export { type ParaphrasePromptBuilder, type RandomSearchConfig, type RandomSearchEvent, RandomSearchOptimizer, type RandomSearchResult, type RandomSearchSnapshot, type RandomSearchStopReason, type RandomSearchTask, buildParaphrasePrompt };
158
+ export { type CandidateAccepted, type OptimizerEvent, type ParaphrasePromptBuilder, type RandomSearchConfig, type RandomSearchEvent, RandomSearchOptimizer, type RandomSearchResult, type RandomSearchSnapshot, type RandomSearchStopReason, type RandomSearchTask, type ReportableEvent, type Reporter, type RunFinished, buildParaphrasePrompt, isCandidateAccepted, isRunFinished };
@@ -1,4 +1,4 @@
1
- import { a as requireMeasuredMean, d as createMemoryCache, f as stableHash, 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";
1
+ import { _ as createMemoryCache, a as isRunFinished, b as createBudget, c as createEvaluator, d as createDeadline, f as assertResumable, h as componentNames, i as isCandidateAccepted, l as measuredMean, m as runFingerprint, n as flushReporters, o as BudgetExhausted, r as instanceRow, s as costExhausted, t as createEmitter, u as requireMeasuredMean, v as defaultInstanceId } from "../reporting-DY-DC4HG.mjs";
2
2
  import { t as mapWithConcurrency } from "../concurrency-D58PWeSk.mjs";
3
3
  import { t as parseProposedText } from "../text--v4Ffbus.mjs";
4
4
  //#region src/random-search/optimize.ts
@@ -32,10 +32,14 @@ var RandomSearchOptimizer = class {
32
32
  this.#config = config;
33
33
  }
34
34
  async optimize(task) {
35
- return runRandomSearch({
36
- config: this.#config,
37
- task
38
- });
35
+ try {
36
+ return await runRandomSearch({
37
+ config: this.#config,
38
+ task
39
+ });
40
+ } finally {
41
+ await flushReporters(task.reporters ?? []);
42
+ }
39
43
  }
40
44
  };
41
45
  /**
@@ -62,7 +66,8 @@ function buildParaphrasePrompt(args) {
62
66
  async function runRandomSearch(args) {
63
67
  const { config, task } = args;
64
68
  const { variants = DEFAULT_VARIANTS, concurrency = 1, maxRounds = Number.POSITIVE_INFINITY, buildPrompt = buildParaphrasePrompt, trackBestOutputs = false, checkpointCache = true } = config;
65
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
69
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
70
+ const emit = createEmitter(reporters);
66
71
  const deadline = createDeadline({ maxWallClockMs });
67
72
  const components = componentNames(seedCandidate);
68
73
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
@@ -103,8 +108,9 @@ async function runRandomSearch(args) {
103
108
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
104
109
  trackOutputs: trackBestOutputs,
105
110
  cacheHits: resumeFrom?.cacheHits ?? 0,
111
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
106
112
  ...signal === void 0 ? {} : { signal },
107
- onEvaluation: (event) => onEvent?.({
113
+ onEvaluation: (event) => emit({
108
114
  type: "evaluation",
109
115
  ...event
110
116
  })
@@ -122,7 +128,7 @@ async function runRandomSearch(args) {
122
128
  * no budget here bounds.
123
129
  */
124
130
  let stalledRounds = 0;
125
- onEvent?.({
131
+ emit({
126
132
  type: "start",
127
133
  components,
128
134
  validationSetSize: validationSet.length
@@ -151,6 +157,8 @@ async function runRandomSearch(args) {
151
157
  reflectionCalls,
152
158
  metricCalls: budget.spent(),
153
159
  cacheHits: evaluator.cacheHits(),
160
+ usage: evaluator.usage(),
161
+ acceptedCandidates,
154
162
  ...cached === void 0 ? {} : { cache: cached }
155
163
  };
156
164
  }
@@ -166,10 +174,20 @@ async function runRandomSearch(args) {
166
174
  batch: seedEvaluation,
167
175
  phase: "seed"
168
176
  });
177
+ if (seedEvaluation !== void 0) emit({
178
+ type: "candidateAccepted",
179
+ round: 0,
180
+ candidateId: 0,
181
+ candidate: seedCandidate,
182
+ aggregateScore: seedScore,
183
+ instanceScores: instanceRow(seedEvaluation),
184
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
185
+ });
169
186
  let best = resumeFrom?.best ?? seedCandidate;
170
187
  let bestScore = resumeFrom?.bestScore ?? seedScore;
171
188
  /** Absent on a resumed run until a variant wins: outputs are not checkpointed. */
172
189
  let bestOutputs = seedEvaluation?.outputs;
190
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
173
191
  await checkpoint();
174
192
  while (round < maxRounds) {
175
193
  if (signal?.aborted) {
@@ -187,12 +205,12 @@ async function runRandomSearch(args) {
187
205
  stopReason = "deadlineReached";
188
206
  break;
189
207
  }
190
- if (!budget.canAfford(variants * validationSet.length)) {
208
+ if (!budget.canAfford(validationSet.length)) {
191
209
  stopReason = "budgetExhausted";
192
210
  break;
193
211
  }
194
212
  const component = components[round % components.length];
195
- onEvent?.({
213
+ emit({
196
214
  type: "roundStart",
197
215
  round,
198
216
  component
@@ -219,41 +237,64 @@ async function runRandomSearch(args) {
219
237
  });
220
238
  const unique = [...new Set(drawn)].filter((text) => text.length > 0 && text !== currentText);
221
239
  let roundStop;
240
+ const scheduled = [];
241
+ let owed = 0;
222
242
  for (const text of unique) {
223
243
  const candidate = {
224
244
  ...best,
225
245
  [component]: text
226
246
  };
227
- let evaluation;
228
- try {
229
- evaluation = await sweep({
230
- candidate,
231
- phase: "validation"
232
- });
233
- } catch (err) {
234
- if (err instanceof BudgetExhausted) {
235
- roundStop = "budgetExhausted";
236
- break;
237
- }
238
- if (signal?.aborted) {
239
- roundStop = "aborted";
240
- break;
247
+ const uncached = evaluator.countUncached({
248
+ candidate,
249
+ ids: validationIds,
250
+ split: "val"
251
+ });
252
+ if (!budget.canAfford(owed + uncached)) {
253
+ roundStop = "budgetExhausted";
254
+ break;
255
+ }
256
+ owed += uncached;
257
+ scheduled.push(candidate);
258
+ }
259
+ const swept = await mapWithConcurrency({
260
+ items: scheduled,
261
+ limit: concurrency,
262
+ task: async (candidate) => {
263
+ if (signal?.aborted) return { stop: "aborted" };
264
+ try {
265
+ return { evaluation: await sweep({
266
+ candidate,
267
+ phase: "validation"
268
+ }) };
269
+ } catch (err) {
270
+ if (err instanceof BudgetExhausted) return { stop: "budgetExhausted" };
271
+ if (signal?.aborted) return { stop: "aborted" };
272
+ throw err;
241
273
  }
242
- throw err;
274
+ }
275
+ });
276
+ for (const [index, outcome] of swept.entries()) {
277
+ if (outcome.stop !== void 0) {
278
+ roundStop = outcome.stop;
279
+ break;
243
280
  }
244
281
  variantsEvaluated += 1;
245
- const score = measuredMean(evaluation);
282
+ const score = measuredMean(outcome.evaluation);
246
283
  if (score !== void 0 && score > bestScore) {
247
- onEvent?.({
284
+ const candidate = scheduled[index];
285
+ acceptedCandidates += 1;
286
+ emit({
248
287
  type: "candidateAccepted",
249
288
  round,
250
- component,
251
- score,
252
- previousScore: bestScore
289
+ candidateId: acceptedCandidates,
290
+ candidate,
291
+ aggregateScore: score,
292
+ instanceScores: instanceRow(outcome.evaluation),
293
+ ...trackBestOutputs ? { outputs: outcome.evaluation.outputs } : {}
253
294
  });
254
295
  best = candidate;
255
296
  bestScore = score;
256
- bestOutputs = evaluation.outputs;
297
+ bestOutputs = outcome.evaluation.outputs;
257
298
  }
258
299
  }
259
300
  round += 1;
@@ -269,7 +310,7 @@ async function runRandomSearch(args) {
269
310
  }
270
311
  }
271
312
  if (signal?.aborted) stopReason = "aborted";
272
- const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
313
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
273
314
  candidate: best,
274
315
  batch: testSet,
275
316
  ids: testIds,
@@ -278,13 +319,17 @@ async function runRandomSearch(args) {
278
319
  candidateId: null,
279
320
  iteration: round,
280
321
  charge: false
281
- }));
282
- onEvent?.({
322
+ });
323
+ const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
324
+ emit({
283
325
  type: "finish",
284
326
  reason: stopReason,
327
+ bestCandidateId: acceptedCandidates,
285
328
  bestScore,
286
329
  metricCalls: budget.spent(),
287
- ...testScore === void 0 ? {} : { testScore }
330
+ ...testScore === void 0 ? {} : { testScore },
331
+ ...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
332
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
288
333
  });
289
334
  return {
290
335
  bestCandidate: best,
@@ -295,7 +340,8 @@ async function runRandomSearch(args) {
295
340
  ...trackBestOutputs ? { bestOutputs } : {},
296
341
  ...testScore === void 0 ? {} : {
297
342
  testScore,
298
- testMetricCalls: evaluator.unchargedCalls()
343
+ testMetricCalls: evaluator.unchargedCalls(),
344
+ testUsage: evaluator.unchargedUsage()
299
345
  },
300
346
  rounds: round,
301
347
  variantsEvaluated,
@@ -311,9 +357,5 @@ function assertConfig(config) {
311
357
  if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
312
358
  if (maxRounds !== void 0 && (!Number.isInteger(maxRounds) || maxRounds < 1)) throw new Error(`maxRounds must be a positive integer, received ${maxRounds}`);
313
359
  }
314
- function defaultInstanceId(args) {
315
- const hash = stableHash(args.datum);
316
- return hash === "" ? String(args.index) : hash;
317
- }
318
360
  //#endregion
319
- export { RandomSearchOptimizer, buildParaphrasePrompt };
361
+ export { RandomSearchOptimizer, buildParaphrasePrompt, isCandidateAccepted, isRunFinished };