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 { 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 { 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, y as createMemoryCache } from "../warnings-OxvDi9kN.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-Brobjfuc.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,12 @@ 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: requestedValidationSet, 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 { validationSet, warnings } = resolveValidationSet({
282
+ validationSet: requestedValidationSet,
283
+ trainingSet
284
+ });
285
+ const emit = createEmitter(reporters);
277
286
  const deadline = createDeadline({ maxWallClockMs });
278
287
  const components = componentNames(seedCandidate);
279
288
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
@@ -318,15 +327,16 @@ async function runMipro(args) {
318
327
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
319
328
  trackOutputs: trackBestOutputs,
320
329
  cacheHits: resumeFrom?.cacheHits ?? 0,
330
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
321
331
  ...signal === void 0 ? {} : { signal },
322
- onEvaluation: (event) => onEvent?.({
332
+ onEvaluation: (event) => emit({
323
333
  type: "evaluation",
324
334
  ...event
325
335
  })
326
336
  });
327
337
  evaluator.restore(resumeFrom?.cache ?? []);
328
338
  if (resumeFrom?.sampler !== void 0) batchSampler.restore?.(resumeFrom.sampler);
329
- onEvent?.({
339
+ emit({
330
340
  type: "start",
331
341
  components,
332
342
  validationSetSize: validationSet.length
@@ -373,20 +383,26 @@ async function runMipro(args) {
373
383
  for (let index = 0; index < demoSets; index += 1) {
374
384
  const affordable = Math.min(trainingSet.length, budget.remaining() - validationSet.length);
375
385
  if (affordable < 1) break;
386
+ if (costExhausted({
387
+ usage: evaluator.usage(),
388
+ maxCostUsd
389
+ })) break;
376
390
  const requested = demoSets === 1 ? maxDemos : Math.round(1 + index * (maxDemos - 1) / (demoSets - 1));
377
- const harvest = await bootstrapDemos({
391
+ const harvest = await harvestFewShotExamples({
378
392
  adapter,
379
393
  candidate: seedCandidate,
380
394
  trainingSet,
381
395
  ...demoMinScore === void 0 ? {} : { minScore: demoMinScore },
382
396
  maxDemos: requested,
383
397
  maxMetricCalls: affordable,
398
+ ...maxCostUsd === void 0 ? {} : { maxCostUsd: maxCostUsd - evaluator.usage().costUsd },
384
399
  rng,
385
400
  ...renderDemo === void 0 ? {} : { renderDemo },
386
401
  ...signal === void 0 ? {} : { signal }
387
402
  });
388
403
  bootstrapMetricCalls += harvest.metricCalls;
389
404
  budget.reserve(harvest.metricCalls);
405
+ evaluator.absorbUsage(harvest.usage);
390
406
  if (harvest.demos.length > 0) blocks.push(formatDemos(harvest.demos, renderDemo === void 0 ? {} : { render: renderDemo }));
391
407
  }
392
408
  return [.../* @__PURE__ */ new Set([
@@ -431,7 +447,7 @@ async function runMipro(args) {
431
447
  });
432
448
  menu[name] = [seedCandidate[name], ...drawn.filter((text) => text.length > 0)];
433
449
  }
434
- onEvent?.({
450
+ emit({
435
451
  type: "menu",
436
452
  menu,
437
453
  reflectionCalls
@@ -466,6 +482,15 @@ async function runMipro(args) {
466
482
  phase: "seed"
467
483
  });
468
484
  if (seedEvaluation !== void 0) fullEvaluations += 1;
485
+ if (seedEvaluation !== void 0) emit({
486
+ type: "candidateAccepted",
487
+ trial: 0,
488
+ candidateId: 0,
489
+ candidate: seedCandidate,
490
+ aggregateScore: seedScore,
491
+ instanceScores: instanceRow(seedEvaluation),
492
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
493
+ });
469
494
  if (resumeFrom === void 0) surrogateInput.push({
470
495
  choices: menuSizes.map(() => 0),
471
496
  score: seedScore
@@ -473,6 +498,7 @@ async function runMipro(args) {
473
498
  let best = resumeFrom?.best ?? seedCandidate;
474
499
  let bestScore = resumeFrom?.bestScore ?? seedScore;
475
500
  /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
501
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
476
502
  let bestOutputs = seedEvaluation?.outputs;
477
503
  const readings = new Map(resumeFrom?.readings ?? []);
478
504
  const swept = new Set(resumeFrom?.swept ?? []);
@@ -492,6 +518,8 @@ async function runMipro(args) {
492
518
  bootstrapMetricCalls,
493
519
  metricCalls: budget.spent(),
494
520
  cacheHits: evaluator.cacheHits(),
521
+ usage: evaluator.usage(),
522
+ acceptedCandidates,
495
523
  rngState: rng.state(),
496
524
  observations: [...observations],
497
525
  surrogateInput: surrogateInput.map((entry) => ({
@@ -553,11 +581,15 @@ async function runMipro(args) {
553
581
  best = candidate;
554
582
  bestScore = score;
555
583
  bestOutputs = evaluation.outputs;
556
- onEvent?.({
557
- type: "incumbent",
584
+ acceptedCandidates += 1;
585
+ emit({
586
+ type: "candidateAccepted",
558
587
  trial,
559
- choices,
560
- score
588
+ candidateId: acceptedCandidates,
589
+ candidate,
590
+ aggregateScore: score,
591
+ instanceScores: instanceRow(evaluation),
592
+ ...trackBestOutputs ? { outputs: evaluation.outputs } : {}
561
593
  });
562
594
  }
563
595
  return "swept";
@@ -624,47 +656,44 @@ async function runMipro(args) {
624
656
  }
625
657
  throw err;
626
658
  }
627
- if (minibatchScore === void 0) {
628
- trial += 1;
629
- continue;
659
+ if (minibatchScore !== void 0) {
660
+ surrogateInput.push({
661
+ choices,
662
+ score: minibatchScore
663
+ });
664
+ const key = choices.join(",");
665
+ readings.set(key, [...readings.get(key) ?? [], minibatchScore]);
666
+ const observation = {
667
+ trial,
668
+ choices,
669
+ minibatchScore,
670
+ promoted: false
671
+ };
672
+ observations.push(observation);
673
+ emit({
674
+ type: "trial",
675
+ trial,
676
+ choices,
677
+ minibatchScore,
678
+ promoted: false
679
+ });
630
680
  }
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
681
  trial += 1;
652
- await checkpoint();
682
+ let cadenceStop;
653
683
  if (trial % fullEvalInterval === 0) {
654
684
  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
- }
685
+ if (outcome === "budgetExhausted" || outcome === "aborted") cadenceStop = outcome;
686
+ if (outcome === "unaffordable") cadenceStop = "budgetExhausted";
687
+ }
688
+ await checkpoint();
689
+ if (cadenceStop !== void 0) {
690
+ stopReason = cadenceStop;
691
+ break;
663
692
  }
664
693
  }
665
694
  if (stopReason === "maxTrials" && !signal?.aborted) await sweepBestUnswept();
666
695
  if (signal?.aborted) stopReason = "aborted";
667
- const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
696
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
668
697
  candidate: best,
669
698
  batch: testSet,
670
699
  ids: testIds,
@@ -673,13 +702,18 @@ async function runMipro(args) {
673
702
  candidateId: null,
674
703
  iteration: trial,
675
704
  charge: false
676
- }));
677
- onEvent?.({
705
+ });
706
+ const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
707
+ emit({
678
708
  type: "finish",
679
709
  reason: stopReason,
710
+ warnings,
711
+ bestCandidateId: acceptedCandidates,
680
712
  bestScore,
681
713
  metricCalls: budget.spent(),
682
- ...testScore === void 0 ? {} : { testScore }
714
+ ...testScore === void 0 ? {} : { testScore },
715
+ ...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
716
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
683
717
  });
684
718
  return {
685
719
  snapshot: takeSnapshot(),
@@ -690,7 +724,8 @@ async function runMipro(args) {
690
724
  ...trackBestOutputs ? { bestOutputs } : {},
691
725
  ...testScore === void 0 ? {} : {
692
726
  testScore,
693
- testMetricCalls: evaluator.unchargedCalls()
727
+ testMetricCalls: evaluator.unchargedCalls(),
728
+ testUsage: evaluator.unchargedUsage()
694
729
  },
695
730
  trials: trial,
696
731
  menu,
@@ -700,6 +735,7 @@ async function runMipro(args) {
700
735
  metricCalls: budget.spent(),
701
736
  reflectionCalls,
702
737
  cacheHits: evaluator.cacheHits(),
738
+ warnings,
703
739
  stopReason
704
740
  };
705
741
  }
@@ -728,9 +764,5 @@ function assertConfig(config) {
728
764
  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
765
  if (config.tips !== void 0 && config.tips.length === 0) throw new Error("tips must not be empty");
730
766
  }
731
- function defaultInstanceId(args) {
732
- const hash = stableHash(args.datum);
733
- return hash === "" ? String(args.index) : hash;
734
- }
735
767
  //#endregion
736
- export { MiproOptimizer, buildMiproPrompt, proposeConfiguration };
768
+ 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_warnings = require("../warnings-CWRJF-jA.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_warnings.flushReporters(task.reporters ?? []);
53
+ }
50
54
  }
51
55
  };
52
56
  /**
@@ -91,9 +95,14 @@ 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: requestedValidationSet, testSet, adapter, reflect, maxMetricCalls, renderDatum = renderDefault, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_warnings.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
99
+ const { validationSet, warnings } = require_warnings.resolveValidationSet({
100
+ validationSet: requestedValidationSet,
101
+ trainingSet
102
+ });
103
+ const emit = require_warnings.createEmitter(reporters);
104
+ const deadline = require_warnings.createDeadline({ maxWallClockMs });
105
+ const components = require_warnings.componentNames(seedCandidate);
97
106
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
98
107
  if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
99
108
  if (components.length === 0) throw new Error("optimize requires a seed candidate with at least one component");
@@ -106,7 +115,7 @@ async function runOpro(args) {
106
115
  datum,
107
116
  index
108
117
  })) ?? [];
109
- const fingerprint = require_evaluation.runFingerprint({
118
+ const fingerprint = require_warnings.runFingerprint({
110
119
  seedCandidate,
111
120
  trainingIds: trainingSet.map((datum, index) => instanceId({
112
121
  datum,
@@ -116,17 +125,17 @@ async function runOpro(args) {
116
125
  seed,
117
126
  ...cacheNamespace === void 0 ? {} : { cacheNamespace }
118
127
  });
119
- require_evaluation.assertResumable({
128
+ require_warnings.assertResumable({
120
129
  fingerprint,
121
130
  ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
122
131
  });
123
132
  const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
124
- const budget = require_evaluation.createBudget({
133
+ const budget = require_warnings.createBudget({
125
134
  maxMetricCalls,
126
135
  spent: resumeFrom?.metricCalls ?? 0
127
136
  });
128
- const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
129
- const evaluator = require_evaluation.createEvaluator({
137
+ const evaluationCache = cache === false ? void 0 : cache ?? require_warnings.createMemoryCache();
138
+ const evaluator = require_warnings.createEvaluator({
130
139
  adapter,
131
140
  budget,
132
141
  ...retry === void 0 ? {} : { retry },
@@ -134,8 +143,9 @@ async function runOpro(args) {
134
143
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
135
144
  trackOutputs: trackBestOutputs,
136
145
  cacheHits: resumeFrom?.cacheHits ?? 0,
146
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
137
147
  ...signal === void 0 ? {} : { signal },
138
- onEvaluation: (event) => onEvent?.({
148
+ onEvaluation: (event) => emit({
139
149
  type: "evaluation",
140
150
  ...event
141
151
  })
@@ -154,13 +164,13 @@ async function runOpro(args) {
154
164
  function contextOf(candidate, component) {
155
165
  const rest = {};
156
166
  for (const name of components) if (name !== component) rest[name] = candidate[name];
157
- return require_evaluation.stableHash(rest);
167
+ return require_warnings.stableHash(rest);
158
168
  }
159
169
  const trajectory = [];
160
170
  let round = resumeFrom?.round ?? 0;
161
171
  let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
162
172
  let stopReason = "maxRounds";
163
- onEvent?.({
173
+ emit({
164
174
  type: "start",
165
175
  components,
166
176
  validationSetSize: validationSet.length
@@ -193,19 +203,41 @@ async function runOpro(args) {
193
203
  iteration: round
194
204
  });
195
205
  }
206
+ /** What screening a candidate would cost, in rollouts nothing has cached. */
207
+ function screenCost(candidate) {
208
+ return scoringSet === void 0 ? evaluator.countUncached({
209
+ candidate,
210
+ ids: validationIds,
211
+ split: "val"
212
+ }) : evaluator.countUncached({
213
+ candidate,
214
+ ids: scoringIds,
215
+ split: "train"
216
+ });
217
+ }
196
218
  const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
197
- const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_evaluation.requireMeasuredMean({
219
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_warnings.requireMeasuredMean({
198
220
  batch: seedEvaluation,
199
221
  phase: "seed"
200
222
  });
223
+ if (seedEvaluation !== void 0) emit({
224
+ type: "candidateAccepted",
225
+ round: 0,
226
+ candidateId: 0,
227
+ candidate: seedCandidate,
228
+ aggregateScore: seedScore,
229
+ instanceScores: require_warnings.instanceRow(seedEvaluation),
230
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
231
+ });
201
232
  let best = resumeFrom?.best ?? seedCandidate;
202
233
  let bestScore = resumeFrom?.bestScore ?? seedScore;
203
234
  /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
235
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
204
236
  let bestOutputs = seedEvaluation?.outputs;
205
237
  let reported = resumeFrom?.reported ?? seedCandidate;
206
238
  let lastSwept = resumeFrom === void 0 ? seedCandidate : resumeFrom.incumbentSwept ? best : {};
207
239
  let bestSearchScore = resumeFrom?.bestSearchScore ?? seedScore;
208
- if (resumeFrom === void 0 && scoringSet !== void 0) bestSearchScore = require_evaluation.requireMeasuredMean({
240
+ if (resumeFrom === void 0 && scoringSet !== void 0) bestSearchScore = require_warnings.requireMeasuredMean({
209
241
  batch: await screen(seedCandidate, "seed"),
210
242
  phase: "seed"
211
243
  });
@@ -215,19 +247,42 @@ async function runOpro(args) {
215
247
  * what gets reported is the best candidate a full sweep has actually seen —
216
248
  * never a subset number wearing a validation set label.
217
249
  */
250
+ /**
251
+ * The incumbent moved and a full sweep measured it. Emitted from the two
252
+ * places that can be true — a screening run with no scoring set, where the
253
+ * attempt's own evaluation is the sweep, and the cadence that confirms an
254
+ * incumbent later — because a payload assembled twice is one that drifts.
255
+ */
256
+ function emitAccepted(args) {
257
+ acceptedCandidates += 1;
258
+ emit({
259
+ type: "candidateAccepted",
260
+ round,
261
+ candidateId: acceptedCandidates,
262
+ candidate: args.candidate,
263
+ aggregateScore: args.score,
264
+ instanceScores: require_warnings.instanceRow(args.evaluation),
265
+ ...trackBestOutputs ? { outputs: args.evaluation.outputs } : {}
266
+ });
267
+ }
218
268
  async function refreshIncumbent() {
219
269
  if (best === lastSwept || !budget.canAfford(validationSet.length)) return "ok";
220
270
  try {
221
271
  const evaluation = await sweep(best, "validation");
222
- const full = require_evaluation.measuredMean(evaluation);
272
+ const full = require_warnings.measuredMean(evaluation);
223
273
  lastSwept = best;
224
274
  if (full !== void 0 && full > bestScore) {
225
275
  reported = best;
226
276
  bestScore = full;
227
277
  bestOutputs = evaluation.outputs;
278
+ emitAccepted({
279
+ candidate: best,
280
+ evaluation,
281
+ score: full
282
+ });
228
283
  }
229
284
  } catch (err) {
230
- if (err instanceof require_evaluation.BudgetExhausted || signal?.aborted) return "stop";
285
+ if (err instanceof require_warnings.BudgetExhausted || signal?.aborted) return "stop";
231
286
  throw err;
232
287
  }
233
288
  return "ok";
@@ -260,6 +315,8 @@ async function runOpro(args) {
260
315
  reflectionCalls,
261
316
  metricCalls: budget.spent(),
262
317
  cacheHits: evaluator.cacheHits(),
318
+ usage: evaluator.usage(),
319
+ acceptedCandidates,
263
320
  rngState: rng.state(),
264
321
  histories: Object.fromEntries([...histories].map(([name, attempts]) => [name, [...attempts]])),
265
322
  ...scoringIndices === void 0 ? {} : { scoringIndices },
@@ -281,7 +338,7 @@ async function runOpro(args) {
281
338
  stopReason = "proposalsExhausted";
282
339
  break;
283
340
  }
284
- if (require_evaluation.costExhausted({
341
+ if (require_warnings.costExhausted({
285
342
  usage: evaluator.usage(),
286
343
  maxCostUsd
287
344
  })) {
@@ -304,7 +361,7 @@ async function runOpro(args) {
304
361
  const history = histories.get(component);
305
362
  const context = contextOf(best, component);
306
363
  const comparable = history.filter((attempt) => attempt.context === context);
307
- onEvent?.({
364
+ emit({
308
365
  type: "roundStart",
309
366
  round,
310
367
  component,
@@ -333,26 +390,44 @@ async function runOpro(args) {
333
390
  const unique = [...new Set(drawn)].filter((text) => text.length > 0 && !tried.has(text));
334
391
  barrenRounds = unique.length === 0 ? barrenRounds + 1 : 0;
335
392
  let roundStop;
393
+ const scheduled = [];
394
+ let owed = 0;
336
395
  for (const text of unique) {
337
396
  const candidate = {
338
397
  ...best,
339
398
  [component]: text
340
399
  };
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;
400
+ const cost = screenCost(candidate);
401
+ if (!budget.canAfford(owed + cost)) {
402
+ roundStop = "budgetExhausted";
403
+ break;
404
+ }
405
+ owed += cost;
406
+ scheduled.push(candidate);
407
+ }
408
+ const screened = await require_concurrency.mapWithConcurrency({
409
+ items: scheduled,
410
+ limit: concurrency,
411
+ task: async (candidate) => {
412
+ if (signal?.aborted) return { stop: "aborted" };
413
+ try {
414
+ return { evaluation: await screen(candidate, "validation") };
415
+ } catch (err) {
416
+ if (err instanceof require_warnings.BudgetExhausted) return { stop: "budgetExhausted" };
417
+ if (signal?.aborted) return { stop: "aborted" };
418
+ throw err;
352
419
  }
353
- throw err;
354
420
  }
355
- const score = require_evaluation.measuredMean(evaluation);
421
+ });
422
+ for (const [index, outcome] of screened.entries()) {
423
+ if (outcome.stop !== void 0) {
424
+ roundStop = outcome.stop;
425
+ break;
426
+ }
427
+ const candidate = scheduled[index];
428
+ const text = candidate[component];
429
+ const evaluation = outcome.evaluation;
430
+ const score = require_warnings.measuredMean(evaluation);
356
431
  if (score === void 0) continue;
357
432
  const accepted = score > bestSearchScore;
358
433
  history.push({
@@ -366,7 +441,7 @@ async function runOpro(args) {
366
441
  candidate,
367
442
  score
368
443
  });
369
- onEvent?.({
444
+ emit({
370
445
  type: "attempt",
371
446
  round,
372
447
  component,
@@ -381,6 +456,11 @@ async function runOpro(args) {
381
456
  lastSwept = candidate;
382
457
  bestScore = score;
383
458
  bestOutputs = evaluation.outputs;
459
+ emitAccepted({
460
+ candidate,
461
+ evaluation,
462
+ score
463
+ });
384
464
  }
385
465
  const scaled = scaleScore(score, scoreScale);
386
466
  for (const name of components) {
@@ -394,22 +474,22 @@ async function runOpro(args) {
394
474
  }
395
475
  }
396
476
  round += 1;
477
+ let cadenceStop;
478
+ if (roundStop === void 0 && scoringSet !== void 0 && round % fullEvalInterval === 0 && await refreshIncumbent() === "stop") cadenceStop = signal?.aborted ? "aborted" : "budgetExhausted";
397
479
  await checkpoint();
398
480
  if (roundStop !== void 0) {
399
481
  stopReason = roundStop;
400
482
  break;
401
483
  }
402
- if (scoringSet !== void 0 && round % fullEvalInterval === 0) {
403
- if (await refreshIncumbent() === "stop") {
404
- stopReason = signal?.aborted ? "aborted" : "budgetExhausted";
405
- break;
406
- }
484
+ if (cadenceStop !== void 0) {
485
+ stopReason = cadenceStop;
486
+ break;
407
487
  }
408
488
  }
409
489
  if (scoringSet !== void 0 && !signal?.aborted) await refreshIncumbent();
410
490
  if (signal?.aborted) stopReason = "aborted";
411
- const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
412
- candidate: best,
491
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
492
+ candidate: reported,
413
493
  batch: testSet,
414
494
  ids: testIds,
415
495
  split: "test",
@@ -417,13 +497,18 @@ async function runOpro(args) {
417
497
  candidateId: null,
418
498
  iteration: round,
419
499
  charge: false
420
- }));
421
- onEvent?.({
500
+ });
501
+ const testScore = heldOut === void 0 ? void 0 : require_warnings.measuredMean(heldOut);
502
+ emit({
422
503
  type: "finish",
423
504
  reason: stopReason,
505
+ warnings,
506
+ bestCandidateId: acceptedCandidates,
424
507
  bestScore,
425
508
  metricCalls: budget.spent(),
426
- ...testScore === void 0 ? {} : { testScore }
509
+ ...testScore === void 0 ? {} : { testScore },
510
+ ...heldOut === void 0 ? {} : { testInstanceScores: require_warnings.instanceRow(heldOut) },
511
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
427
512
  });
428
513
  return {
429
514
  snapshot: takeSnapshot(),
@@ -434,13 +519,15 @@ async function runOpro(args) {
434
519
  ...trackBestOutputs ? { bestOutputs } : {},
435
520
  ...testScore === void 0 ? {} : {
436
521
  testScore,
437
- testMetricCalls: evaluator.unchargedCalls()
522
+ testMetricCalls: evaluator.unchargedCalls(),
523
+ testUsage: evaluator.unchargedUsage()
438
524
  },
439
525
  rounds: round,
440
526
  trajectory,
441
527
  metricCalls: budget.spent(),
442
528
  reflectionCalls,
443
529
  cacheHits: evaluator.cacheHits(),
530
+ warnings,
444
531
  stopReason
445
532
  };
446
533
  }
@@ -478,10 +565,8 @@ function assertConfig(config) {
478
565
  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
566
  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
567
  }
481
- function defaultInstanceId(args) {
482
- const hash = require_evaluation.stableHash(args.datum);
483
- return hash === "" ? String(args.index) : hash;
484
- }
485
568
  //#endregion
486
569
  exports.OproOptimizer = OproOptimizer;
487
570
  exports.buildOproPrompt = buildOproPrompt;
571
+ exports.isCandidateAccepted = require_warnings.isCandidateAccepted;
572
+ exports.isRunFinished = require_warnings.isRunFinished;