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,7 +1,7 @@
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
- import { n as mean } from "../math-DhrDmpFS.mjs";
4
- import { n as formatDemos, t as bootstrapDemos } from "../demos-Degx6UmP.mjs";
3
+ import { r as mean } from "../math-Dqme4rYz.mjs";
4
+ import { n as harvestFewShotExamples, t as formatDemos } from "../demos-DE2oxNWX.mjs";
5
5
  import { t as parseProposedText } from "../text--v4Ffbus.mjs";
6
6
  import { t as createSeededRng } from "../rng-Dtc5eZ_W.mjs";
7
7
  import { t as createEpochShuffledSampler } from "../sampling-CfHt7Gue.mjs";
@@ -211,10 +211,14 @@ var MiproOptimizer = class {
211
211
  this.#config = config;
212
212
  }
213
213
  async optimize(task) {
214
- return runMipro({
215
- config: this.#config,
216
- task
217
- });
214
+ try {
215
+ return await runMipro({
216
+ config: this.#config,
217
+ task
218
+ });
219
+ } finally {
220
+ await flushReporters(task.reporters ?? []);
221
+ }
218
222
  }
219
223
  };
220
224
  function buildMiproPrompt(args) {
@@ -273,7 +277,8 @@ function buildDatasetSummaryPrompt(examples) {
273
277
  async function runMipro(args) {
274
278
  const { config, task } = args;
275
279
  const { instructionsPerComponent = DEFAULT_INSTRUCTIONS, minibatchSize = DEFAULT_MINIBATCH_SIZE, maxTrials = DEFAULT_MAX_TRIALS, startupTrials, gamma, surrogateSamples, multivariate, fullEvalInterval = DEFAULT_FULL_EVAL_INTERVAL, demoSets = DEFAULT_DEMO_SETS, maxDemos = DEFAULT_MAX_DEMOS, demoMinScore, exemplars = DEFAULT_EXEMPLARS, datasetSummary = true, summaryExamples = DEFAULT_SUMMARY_EXAMPLES, concurrency = 1, seed = 0, buildPrompt = buildMiproPrompt, tips = DEFAULT_TIPS, trackBestOutputs = false, checkpointCache = true } = config;
276
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, componentOptions, demoComponents, renderDemo, goldOutput, maxMetricCalls, renderDatum = renderDefault, batchSampler = createEpochShuffledSampler({ minibatchSize }), cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
280
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, componentOptions, demoComponents, renderDemo, goldOutput, maxMetricCalls, renderDatum = renderDefault, batchSampler = createEpochShuffledSampler({ minibatchSize }), cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
281
+ const emit = createEmitter(reporters);
277
282
  const deadline = createDeadline({ maxWallClockMs });
278
283
  const components = componentNames(seedCandidate);
279
284
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
@@ -318,15 +323,16 @@ async function runMipro(args) {
318
323
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
319
324
  trackOutputs: trackBestOutputs,
320
325
  cacheHits: resumeFrom?.cacheHits ?? 0,
326
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
321
327
  ...signal === void 0 ? {} : { signal },
322
- onEvaluation: (event) => onEvent?.({
328
+ onEvaluation: (event) => emit({
323
329
  type: "evaluation",
324
330
  ...event
325
331
  })
326
332
  });
327
333
  evaluator.restore(resumeFrom?.cache ?? []);
328
334
  if (resumeFrom?.sampler !== void 0) batchSampler.restore?.(resumeFrom.sampler);
329
- onEvent?.({
335
+ emit({
330
336
  type: "start",
331
337
  components,
332
338
  validationSetSize: validationSet.length
@@ -373,20 +379,26 @@ async function runMipro(args) {
373
379
  for (let index = 0; index < demoSets; index += 1) {
374
380
  const affordable = Math.min(trainingSet.length, budget.remaining() - validationSet.length);
375
381
  if (affordable < 1) break;
382
+ if (costExhausted({
383
+ usage: evaluator.usage(),
384
+ maxCostUsd
385
+ })) break;
376
386
  const requested = demoSets === 1 ? maxDemos : Math.round(1 + index * (maxDemos - 1) / (demoSets - 1));
377
- const harvest = await bootstrapDemos({
387
+ const harvest = await harvestFewShotExamples({
378
388
  adapter,
379
389
  candidate: seedCandidate,
380
390
  trainingSet,
381
391
  ...demoMinScore === void 0 ? {} : { minScore: demoMinScore },
382
392
  maxDemos: requested,
383
393
  maxMetricCalls: affordable,
394
+ ...maxCostUsd === void 0 ? {} : { maxCostUsd: maxCostUsd - evaluator.usage().costUsd },
384
395
  rng,
385
396
  ...renderDemo === void 0 ? {} : { renderDemo },
386
397
  ...signal === void 0 ? {} : { signal }
387
398
  });
388
399
  bootstrapMetricCalls += harvest.metricCalls;
389
400
  budget.reserve(harvest.metricCalls);
401
+ evaluator.absorbUsage(harvest.usage);
390
402
  if (harvest.demos.length > 0) blocks.push(formatDemos(harvest.demos, renderDemo === void 0 ? {} : { render: renderDemo }));
391
403
  }
392
404
  return [.../* @__PURE__ */ new Set([
@@ -431,7 +443,7 @@ async function runMipro(args) {
431
443
  });
432
444
  menu[name] = [seedCandidate[name], ...drawn.filter((text) => text.length > 0)];
433
445
  }
434
- onEvent?.({
446
+ emit({
435
447
  type: "menu",
436
448
  menu,
437
449
  reflectionCalls
@@ -466,6 +478,15 @@ async function runMipro(args) {
466
478
  phase: "seed"
467
479
  });
468
480
  if (seedEvaluation !== void 0) fullEvaluations += 1;
481
+ if (seedEvaluation !== void 0) emit({
482
+ type: "candidateAccepted",
483
+ trial: 0,
484
+ candidateId: 0,
485
+ candidate: seedCandidate,
486
+ aggregateScore: seedScore,
487
+ instanceScores: instanceRow(seedEvaluation),
488
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
489
+ });
469
490
  if (resumeFrom === void 0) surrogateInput.push({
470
491
  choices: menuSizes.map(() => 0),
471
492
  score: seedScore
@@ -473,6 +494,7 @@ async function runMipro(args) {
473
494
  let best = resumeFrom?.best ?? seedCandidate;
474
495
  let bestScore = resumeFrom?.bestScore ?? seedScore;
475
496
  /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
497
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
476
498
  let bestOutputs = seedEvaluation?.outputs;
477
499
  const readings = new Map(resumeFrom?.readings ?? []);
478
500
  const swept = new Set(resumeFrom?.swept ?? []);
@@ -492,6 +514,8 @@ async function runMipro(args) {
492
514
  bootstrapMetricCalls,
493
515
  metricCalls: budget.spent(),
494
516
  cacheHits: evaluator.cacheHits(),
517
+ usage: evaluator.usage(),
518
+ acceptedCandidates,
495
519
  rngState: rng.state(),
496
520
  observations: [...observations],
497
521
  surrogateInput: surrogateInput.map((entry) => ({
@@ -553,11 +577,15 @@ async function runMipro(args) {
553
577
  best = candidate;
554
578
  bestScore = score;
555
579
  bestOutputs = evaluation.outputs;
556
- onEvent?.({
557
- type: "incumbent",
580
+ acceptedCandidates += 1;
581
+ emit({
582
+ type: "candidateAccepted",
558
583
  trial,
559
- choices,
560
- score
584
+ candidateId: acceptedCandidates,
585
+ candidate,
586
+ aggregateScore: score,
587
+ instanceScores: instanceRow(evaluation),
588
+ ...trackBestOutputs ? { outputs: evaluation.outputs } : {}
561
589
  });
562
590
  }
563
591
  return "swept";
@@ -624,47 +652,44 @@ async function runMipro(args) {
624
652
  }
625
653
  throw err;
626
654
  }
627
- if (minibatchScore === void 0) {
628
- trial += 1;
629
- continue;
655
+ if (minibatchScore !== void 0) {
656
+ surrogateInput.push({
657
+ choices,
658
+ score: minibatchScore
659
+ });
660
+ const key = choices.join(",");
661
+ readings.set(key, [...readings.get(key) ?? [], minibatchScore]);
662
+ const observation = {
663
+ trial,
664
+ choices,
665
+ minibatchScore,
666
+ promoted: false
667
+ };
668
+ observations.push(observation);
669
+ emit({
670
+ type: "trial",
671
+ trial,
672
+ choices,
673
+ minibatchScore,
674
+ promoted: false
675
+ });
630
676
  }
631
- surrogateInput.push({
632
- choices,
633
- score: minibatchScore
634
- });
635
- const key = choices.join(",");
636
- readings.set(key, [...readings.get(key) ?? [], minibatchScore]);
637
- const observation = {
638
- trial,
639
- choices,
640
- minibatchScore,
641
- promoted: false
642
- };
643
- observations.push(observation);
644
- onEvent?.({
645
- type: "trial",
646
- trial,
647
- choices,
648
- minibatchScore,
649
- promoted: false
650
- });
651
677
  trial += 1;
652
- await checkpoint();
678
+ let cadenceStop;
653
679
  if (trial % fullEvalInterval === 0) {
654
680
  const outcome = await sweepBestUnswept();
655
- if (outcome === "budgetExhausted" || outcome === "aborted") {
656
- stopReason = outcome;
657
- break;
658
- }
659
- if (outcome === "unaffordable") {
660
- stopReason = "budgetExhausted";
661
- break;
662
- }
681
+ if (outcome === "budgetExhausted" || outcome === "aborted") cadenceStop = outcome;
682
+ if (outcome === "unaffordable") cadenceStop = "budgetExhausted";
683
+ }
684
+ await checkpoint();
685
+ if (cadenceStop !== void 0) {
686
+ stopReason = cadenceStop;
687
+ break;
663
688
  }
664
689
  }
665
690
  if (stopReason === "maxTrials" && !signal?.aborted) await sweepBestUnswept();
666
691
  if (signal?.aborted) stopReason = "aborted";
667
- const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
692
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
668
693
  candidate: best,
669
694
  batch: testSet,
670
695
  ids: testIds,
@@ -673,13 +698,17 @@ async function runMipro(args) {
673
698
  candidateId: null,
674
699
  iteration: trial,
675
700
  charge: false
676
- }));
677
- onEvent?.({
701
+ });
702
+ const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
703
+ emit({
678
704
  type: "finish",
679
705
  reason: stopReason,
706
+ bestCandidateId: acceptedCandidates,
680
707
  bestScore,
681
708
  metricCalls: budget.spent(),
682
- ...testScore === void 0 ? {} : { testScore }
709
+ ...testScore === void 0 ? {} : { testScore },
710
+ ...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
711
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
683
712
  });
684
713
  return {
685
714
  snapshot: takeSnapshot(),
@@ -690,7 +719,8 @@ async function runMipro(args) {
690
719
  ...trackBestOutputs ? { bestOutputs } : {},
691
720
  ...testScore === void 0 ? {} : {
692
721
  testScore,
693
- testMetricCalls: evaluator.unchargedCalls()
722
+ testMetricCalls: evaluator.unchargedCalls(),
723
+ testUsage: evaluator.unchargedUsage()
694
724
  },
695
725
  trials: trial,
696
726
  menu,
@@ -728,9 +758,5 @@ function assertConfig(config) {
728
758
  if (config.gamma !== void 0 && (!Number.isFinite(config.gamma) || config.gamma <= 0 || config.gamma > 1)) throw new Error(`gamma must be within (0, 1], received ${config.gamma}`);
729
759
  if (config.tips !== void 0 && config.tips.length === 0) throw new Error("tips must not be empty");
730
760
  }
731
- function defaultInstanceId(args) {
732
- const hash = stableHash(args.datum);
733
- return hash === "" ? String(args.index) : hash;
734
- }
735
761
  //#endregion
736
- export { MiproOptimizer, buildMiproPrompt, proposeConfiguration };
762
+ export { MiproOptimizer, buildMiproPrompt, isCandidateAccepted, isRunFinished, proposeConfiguration };
@@ -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
  const require_rng = require("../rng-DbA_rPIo.cjs");
@@ -43,10 +43,14 @@ var OproOptimizer = class {
43
43
  this.#config = config;
44
44
  }
45
45
  async optimize(task) {
46
- return runOpro({
47
- config: this.#config,
48
- task
49
- });
46
+ try {
47
+ return await runOpro({
48
+ config: this.#config,
49
+ task
50
+ });
51
+ } finally {
52
+ await require_reporting.flushReporters(task.reporters ?? []);
53
+ }
50
54
  }
51
55
  };
52
56
  /**
@@ -91,9 +95,10 @@ function buildOproPrompt(args) {
91
95
  async function runOpro(args) {
92
96
  const { config, task } = args;
93
97
  const { proposalsPerRound = DEFAULT_PROPOSALS_PER_ROUND, concurrency = 1, maxRounds = Number.POSITIVE_INFINITY, maxReflectionCalls = Number.POSITIVE_INFINITY, seed = 0, historySize = DEFAULT_HISTORY_SIZE, exemplars = DEFAULT_EXEMPLARS, scoringSetSize, fullEvalInterval = DEFAULT_FULL_EVAL_INTERVAL, scoreScale = DEFAULT_SCORE_SCALE, buildPrompt = buildOproPrompt, trackBestOutputs = false, checkpointCache = true } = config;
94
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, renderDatum = renderDefault, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
95
- const deadline = require_evaluation.createDeadline({ maxWallClockMs });
96
- const components = require_evaluation.componentNames(seedCandidate);
98
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, renderDatum = renderDefault, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_reporting.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
99
+ const emit = require_reporting.createEmitter(reporters);
100
+ const deadline = require_reporting.createDeadline({ maxWallClockMs });
101
+ const components = require_reporting.componentNames(seedCandidate);
97
102
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
98
103
  if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
99
104
  if (components.length === 0) throw new Error("optimize requires a seed candidate with at least one component");
@@ -106,7 +111,7 @@ async function runOpro(args) {
106
111
  datum,
107
112
  index
108
113
  })) ?? [];
109
- const fingerprint = require_evaluation.runFingerprint({
114
+ const fingerprint = require_reporting.runFingerprint({
110
115
  seedCandidate,
111
116
  trainingIds: trainingSet.map((datum, index) => instanceId({
112
117
  datum,
@@ -116,17 +121,17 @@ async function runOpro(args) {
116
121
  seed,
117
122
  ...cacheNamespace === void 0 ? {} : { cacheNamespace }
118
123
  });
119
- require_evaluation.assertResumable({
124
+ require_reporting.assertResumable({
120
125
  fingerprint,
121
126
  ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
122
127
  });
123
128
  const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
124
- const budget = require_evaluation.createBudget({
129
+ const budget = require_reporting.createBudget({
125
130
  maxMetricCalls,
126
131
  spent: resumeFrom?.metricCalls ?? 0
127
132
  });
128
- const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
129
- const evaluator = require_evaluation.createEvaluator({
133
+ const evaluationCache = cache === false ? void 0 : cache ?? require_reporting.createMemoryCache();
134
+ const evaluator = require_reporting.createEvaluator({
130
135
  adapter,
131
136
  budget,
132
137
  ...retry === void 0 ? {} : { retry },
@@ -134,8 +139,9 @@ async function runOpro(args) {
134
139
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
135
140
  trackOutputs: trackBestOutputs,
136
141
  cacheHits: resumeFrom?.cacheHits ?? 0,
142
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
137
143
  ...signal === void 0 ? {} : { signal },
138
- onEvaluation: (event) => onEvent?.({
144
+ onEvaluation: (event) => emit({
139
145
  type: "evaluation",
140
146
  ...event
141
147
  })
@@ -154,13 +160,13 @@ async function runOpro(args) {
154
160
  function contextOf(candidate, component) {
155
161
  const rest = {};
156
162
  for (const name of components) if (name !== component) rest[name] = candidate[name];
157
- return require_evaluation.stableHash(rest);
163
+ return require_reporting.stableHash(rest);
158
164
  }
159
165
  const trajectory = [];
160
166
  let round = resumeFrom?.round ?? 0;
161
167
  let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
162
168
  let stopReason = "maxRounds";
163
- onEvent?.({
169
+ emit({
164
170
  type: "start",
165
171
  components,
166
172
  validationSetSize: validationSet.length
@@ -193,19 +199,41 @@ async function runOpro(args) {
193
199
  iteration: round
194
200
  });
195
201
  }
202
+ /** What screening a candidate would cost, in rollouts nothing has cached. */
203
+ function screenCost(candidate) {
204
+ return scoringSet === void 0 ? evaluator.countUncached({
205
+ candidate,
206
+ ids: validationIds,
207
+ split: "val"
208
+ }) : evaluator.countUncached({
209
+ candidate,
210
+ ids: scoringIds,
211
+ split: "train"
212
+ });
213
+ }
196
214
  const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
197
- const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_evaluation.requireMeasuredMean({
215
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_reporting.requireMeasuredMean({
198
216
  batch: seedEvaluation,
199
217
  phase: "seed"
200
218
  });
219
+ if (seedEvaluation !== void 0) emit({
220
+ type: "candidateAccepted",
221
+ round: 0,
222
+ candidateId: 0,
223
+ candidate: seedCandidate,
224
+ aggregateScore: seedScore,
225
+ instanceScores: require_reporting.instanceRow(seedEvaluation),
226
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
227
+ });
201
228
  let best = resumeFrom?.best ?? seedCandidate;
202
229
  let bestScore = resumeFrom?.bestScore ?? seedScore;
203
230
  /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
231
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
204
232
  let bestOutputs = seedEvaluation?.outputs;
205
233
  let reported = resumeFrom?.reported ?? seedCandidate;
206
234
  let lastSwept = resumeFrom === void 0 ? seedCandidate : resumeFrom.incumbentSwept ? best : {};
207
235
  let bestSearchScore = resumeFrom?.bestSearchScore ?? seedScore;
208
- if (resumeFrom === void 0 && scoringSet !== void 0) bestSearchScore = require_evaluation.requireMeasuredMean({
236
+ if (resumeFrom === void 0 && scoringSet !== void 0) bestSearchScore = require_reporting.requireMeasuredMean({
209
237
  batch: await screen(seedCandidate, "seed"),
210
238
  phase: "seed"
211
239
  });
@@ -215,19 +243,42 @@ async function runOpro(args) {
215
243
  * what gets reported is the best candidate a full sweep has actually seen —
216
244
  * never a subset number wearing a validation set label.
217
245
  */
246
+ /**
247
+ * The incumbent moved and a full sweep measured it. Emitted from the two
248
+ * places that can be true — a screening run with no scoring set, where the
249
+ * attempt's own evaluation is the sweep, and the cadence that confirms an
250
+ * incumbent later — because a payload assembled twice is one that drifts.
251
+ */
252
+ function emitAccepted(args) {
253
+ acceptedCandidates += 1;
254
+ emit({
255
+ type: "candidateAccepted",
256
+ round,
257
+ candidateId: acceptedCandidates,
258
+ candidate: args.candidate,
259
+ aggregateScore: args.score,
260
+ instanceScores: require_reporting.instanceRow(args.evaluation),
261
+ ...trackBestOutputs ? { outputs: args.evaluation.outputs } : {}
262
+ });
263
+ }
218
264
  async function refreshIncumbent() {
219
265
  if (best === lastSwept || !budget.canAfford(validationSet.length)) return "ok";
220
266
  try {
221
267
  const evaluation = await sweep(best, "validation");
222
- const full = require_evaluation.measuredMean(evaluation);
268
+ const full = require_reporting.measuredMean(evaluation);
223
269
  lastSwept = best;
224
270
  if (full !== void 0 && full > bestScore) {
225
271
  reported = best;
226
272
  bestScore = full;
227
273
  bestOutputs = evaluation.outputs;
274
+ emitAccepted({
275
+ candidate: best,
276
+ evaluation,
277
+ score: full
278
+ });
228
279
  }
229
280
  } catch (err) {
230
- if (err instanceof require_evaluation.BudgetExhausted || signal?.aborted) return "stop";
281
+ if (err instanceof require_reporting.BudgetExhausted || signal?.aborted) return "stop";
231
282
  throw err;
232
283
  }
233
284
  return "ok";
@@ -260,6 +311,8 @@ async function runOpro(args) {
260
311
  reflectionCalls,
261
312
  metricCalls: budget.spent(),
262
313
  cacheHits: evaluator.cacheHits(),
314
+ usage: evaluator.usage(),
315
+ acceptedCandidates,
263
316
  rngState: rng.state(),
264
317
  histories: Object.fromEntries([...histories].map(([name, attempts]) => [name, [...attempts]])),
265
318
  ...scoringIndices === void 0 ? {} : { scoringIndices },
@@ -281,7 +334,7 @@ async function runOpro(args) {
281
334
  stopReason = "proposalsExhausted";
282
335
  break;
283
336
  }
284
- if (require_evaluation.costExhausted({
337
+ if (require_reporting.costExhausted({
285
338
  usage: evaluator.usage(),
286
339
  maxCostUsd
287
340
  })) {
@@ -304,7 +357,7 @@ async function runOpro(args) {
304
357
  const history = histories.get(component);
305
358
  const context = contextOf(best, component);
306
359
  const comparable = history.filter((attempt) => attempt.context === context);
307
- onEvent?.({
360
+ emit({
308
361
  type: "roundStart",
309
362
  round,
310
363
  component,
@@ -333,26 +386,44 @@ async function runOpro(args) {
333
386
  const unique = [...new Set(drawn)].filter((text) => text.length > 0 && !tried.has(text));
334
387
  barrenRounds = unique.length === 0 ? barrenRounds + 1 : 0;
335
388
  let roundStop;
389
+ const scheduled = [];
390
+ let owed = 0;
336
391
  for (const text of unique) {
337
392
  const candidate = {
338
393
  ...best,
339
394
  [component]: text
340
395
  };
341
- let evaluation;
342
- try {
343
- evaluation = await screen(candidate, "validation");
344
- } catch (err) {
345
- if (err instanceof require_evaluation.BudgetExhausted) {
346
- roundStop = "budgetExhausted";
347
- break;
348
- }
349
- if (signal?.aborted) {
350
- roundStop = "aborted";
351
- break;
396
+ const cost = screenCost(candidate);
397
+ if (!budget.canAfford(owed + cost)) {
398
+ roundStop = "budgetExhausted";
399
+ break;
400
+ }
401
+ owed += cost;
402
+ scheduled.push(candidate);
403
+ }
404
+ const screened = await require_concurrency.mapWithConcurrency({
405
+ items: scheduled,
406
+ limit: concurrency,
407
+ task: async (candidate) => {
408
+ if (signal?.aborted) return { stop: "aborted" };
409
+ try {
410
+ return { evaluation: await screen(candidate, "validation") };
411
+ } catch (err) {
412
+ if (err instanceof require_reporting.BudgetExhausted) return { stop: "budgetExhausted" };
413
+ if (signal?.aborted) return { stop: "aborted" };
414
+ throw err;
352
415
  }
353
- throw err;
354
416
  }
355
- const score = require_evaluation.measuredMean(evaluation);
417
+ });
418
+ for (const [index, outcome] of screened.entries()) {
419
+ if (outcome.stop !== void 0) {
420
+ roundStop = outcome.stop;
421
+ break;
422
+ }
423
+ const candidate = scheduled[index];
424
+ const text = candidate[component];
425
+ const evaluation = outcome.evaluation;
426
+ const score = require_reporting.measuredMean(evaluation);
356
427
  if (score === void 0) continue;
357
428
  const accepted = score > bestSearchScore;
358
429
  history.push({
@@ -366,7 +437,7 @@ async function runOpro(args) {
366
437
  candidate,
367
438
  score
368
439
  });
369
- onEvent?.({
440
+ emit({
370
441
  type: "attempt",
371
442
  round,
372
443
  component,
@@ -381,6 +452,11 @@ async function runOpro(args) {
381
452
  lastSwept = candidate;
382
453
  bestScore = score;
383
454
  bestOutputs = evaluation.outputs;
455
+ emitAccepted({
456
+ candidate,
457
+ evaluation,
458
+ score
459
+ });
384
460
  }
385
461
  const scaled = scaleScore(score, scoreScale);
386
462
  for (const name of components) {
@@ -394,22 +470,22 @@ async function runOpro(args) {
394
470
  }
395
471
  }
396
472
  round += 1;
473
+ let cadenceStop;
474
+ if (roundStop === void 0 && scoringSet !== void 0 && round % fullEvalInterval === 0 && await refreshIncumbent() === "stop") cadenceStop = signal?.aborted ? "aborted" : "budgetExhausted";
397
475
  await checkpoint();
398
476
  if (roundStop !== void 0) {
399
477
  stopReason = roundStop;
400
478
  break;
401
479
  }
402
- if (scoringSet !== void 0 && round % fullEvalInterval === 0) {
403
- if (await refreshIncumbent() === "stop") {
404
- stopReason = signal?.aborted ? "aborted" : "budgetExhausted";
405
- break;
406
- }
480
+ if (cadenceStop !== void 0) {
481
+ stopReason = cadenceStop;
482
+ break;
407
483
  }
408
484
  }
409
485
  if (scoringSet !== void 0 && !signal?.aborted) await refreshIncumbent();
410
486
  if (signal?.aborted) stopReason = "aborted";
411
- const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
412
- candidate: best,
487
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
488
+ candidate: reported,
413
489
  batch: testSet,
414
490
  ids: testIds,
415
491
  split: "test",
@@ -417,13 +493,17 @@ async function runOpro(args) {
417
493
  candidateId: null,
418
494
  iteration: round,
419
495
  charge: false
420
- }));
421
- onEvent?.({
496
+ });
497
+ const testScore = heldOut === void 0 ? void 0 : require_reporting.measuredMean(heldOut);
498
+ emit({
422
499
  type: "finish",
423
500
  reason: stopReason,
501
+ bestCandidateId: acceptedCandidates,
424
502
  bestScore,
425
503
  metricCalls: budget.spent(),
426
- ...testScore === void 0 ? {} : { testScore }
504
+ ...testScore === void 0 ? {} : { testScore },
505
+ ...heldOut === void 0 ? {} : { testInstanceScores: require_reporting.instanceRow(heldOut) },
506
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
427
507
  });
428
508
  return {
429
509
  snapshot: takeSnapshot(),
@@ -434,7 +514,8 @@ async function runOpro(args) {
434
514
  ...trackBestOutputs ? { bestOutputs } : {},
435
515
  ...testScore === void 0 ? {} : {
436
516
  testScore,
437
- testMetricCalls: evaluator.unchargedCalls()
517
+ testMetricCalls: evaluator.unchargedCalls(),
518
+ testUsage: evaluator.unchargedUsage()
438
519
  },
439
520
  rounds: round,
440
521
  trajectory,
@@ -478,10 +559,8 @@ function assertConfig(config) {
478
559
  for (const [name, value] of positive) if (value !== void 0 && (!Number.isInteger(value) || value < 1)) throw new Error(`${name} must be a positive integer, received ${value}`);
479
560
  if (config.exemplars !== void 0 && (!Number.isInteger(config.exemplars) || config.exemplars < 0)) throw new Error(`exemplars must be a non-negative integer, received ${config.exemplars}`);
480
561
  }
481
- function defaultInstanceId(args) {
482
- const hash = require_evaluation.stableHash(args.datum);
483
- return hash === "" ? String(args.index) : hash;
484
- }
485
562
  //#endregion
486
563
  exports.OproOptimizer = OproOptimizer;
487
564
  exports.buildOproPrompt = buildOproPrompt;
565
+ exports.isCandidateAccepted = require_reporting.isCandidateAccepted;
566
+ 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/opro/optimize.d.ts
5
5
  /** One instruction that was tried, and what it scored. */
6
6
  /** A history entry plus the system state its score was measured in. */
@@ -111,6 +111,13 @@ interface OproSnapshot {
111
111
  reflectionCalls: number;
112
112
  metricCalls: number;
113
113
  cacheHits: number;
114
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
115
+ usage?: UsageTotals;
116
+ /**
117
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
118
+ * at zero makes a resumed run collide with the run it continues.
119
+ */
120
+ acceptedCandidates?: number;
114
121
  rngState: number;
115
122
  /** Component name -> every text tried for it, with what it scored. */
116
123
  histories: Record<string, RecordedAttempt[]>;
@@ -133,7 +140,8 @@ interface OproTask<Datum, Trajectory = unknown, Output = unknown, K extends stri
133
140
  }) => string;
134
141
  /** Pass `false` to disable caching entirely. */
135
142
  cache?: EvaluationCache | false;
136
- onEvent?: (event: OproEvent<NoInfer<K>>) => void;
143
+ /** Observers of the run. Every one sees every event; none can fail it. */
144
+ reporters?: readonly Reporter<OproEvent<NoInfer<K>>>[];
137
145
  /**
138
146
  * Called with a resumable snapshot after the seed is scored and after every
139
147
  * round. Persist it and a killed run costs the last round, not all of them.
@@ -161,13 +169,13 @@ type OproEvent<K extends string = string> = {
161
169
  score: number;
162
170
  /** True when this attempt became the new incumbent. */
163
171
  accepted: boolean;
164
- } | {
172
+ } | ({
173
+ type: "candidateAccepted";
174
+ round: number;
175
+ } & CandidateAccepted<K>) | ({
165
176
  type: "finish";
166
177
  reason: OproStopReason;
167
- bestScore: number;
168
- metricCalls: number;
169
- testScore?: number;
170
- };
178
+ } & RunFinished);
171
179
  interface OproAttempt<K extends string = string> {
172
180
  round: number;
173
181
  component: K;
@@ -227,4 +235,4 @@ declare function buildOproPrompt(args: {
227
235
  exemplars: readonly string[];
228
236
  }): string;
229
237
  //#endregion
230
- export { type OproAttempt, type OproConfig, type OproEvent, OproOptimizer, type OproPromptBuilder, type OproResult, type OproSnapshot, type OproStopReason, type OproTask, type ScoredAttempt, buildOproPrompt };
238
+ export { type CandidateAccepted, type OproAttempt, type OproConfig, type OproEvent, OproOptimizer, type OproPromptBuilder, type OproResult, type OproSnapshot, type OproStopReason, type OproTask, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, type ScoredAttempt, buildOproPrompt, isCandidateAccepted, isRunFinished };