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,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_evaluation = require("../evaluation-OZOp6TB7.cjs");
3
- const require_demos = require("../demos-B9BJiNKz.cjs");
2
+ const require_warnings = require("../warnings-CWRJF-jA.cjs");
3
+ const require_concurrency = require("../concurrency-C-cFzWW2.cjs");
4
+ const require_demos = require("../demos-9v5ts7F3.cjs");
4
5
  const require_rng = require("../rng-DbA_rPIo.cjs");
5
6
  const require_sampling = require("../sampling-Dars7ctR.cjs");
6
7
  //#region src/simba/advice.ts
@@ -15,7 +16,7 @@ const ADVICE_BLOCK = /<advice\s+component="([^"]+)"\s*>([\s\S]*?)<\/advice>/g;
15
16
  * behaviour rather than to the instance being easier.
16
17
  */
17
18
  function buildAdvicePrompt(args) {
18
- const { components, input, better, worse } = args;
19
+ const { components, current, input, better, worse } = args;
19
20
  return [
20
21
  "Two runs of the same system on the same input are shown below, with the reward each earned.",
21
22
  "Your job is to write advice that would make the system behave like the higher scoring run the next time it sees a similar input.",
@@ -26,12 +27,18 @@ function buildAdvicePrompt(args) {
26
27
  ...worse === void 0 ? [] : trajectoryBlock("worse", worse),
27
28
  ...better === void 0 ? [] : trajectoryBlock("better", better),
28
29
  "",
29
- "Write advice for each of these components:",
30
+ "Write advice for each of these components, shown with what it says now:",
30
31
  "",
31
32
  "<components>",
32
- components.join("\n"),
33
+ components.map((component) => [
34
+ `<component name="${component}">`,
35
+ current[component] ?? "",
36
+ "</component>"
37
+ ].join("\n")).join("\n"),
33
38
  "</components>",
34
39
  "",
40
+ "Your advice is appended to what the component already says. Do not restate advice it already carries; add what is missing or correct what is wrong.",
41
+ "",
35
42
  "The component will not have access to this example, so advice that only covers this input is wasted. State the general behaviour it should adopt, and be concrete about when it applies.",
36
43
  "Address each component's own sub-task rather than the system as a whole.",
37
44
  "Base the advice on what actually differed between the two runs. If nothing useful can be said for a component, leave it out.",
@@ -229,21 +236,32 @@ const DEMO_DROP_SCALE = 3;
229
236
  var SimbaOptimizer = class {
230
237
  #config;
231
238
  constructor(config = {}) {
239
+ assertSimbaConfig(config);
232
240
  this.#config = config;
233
241
  }
234
- optimize(task) {
235
- return run({
236
- config: this.#config,
237
- task
238
- });
242
+ async optimize(task) {
243
+ try {
244
+ return await run({
245
+ config: this.#config,
246
+ task
247
+ });
248
+ } finally {
249
+ await require_warnings.flushReporters(task.reporters ?? []);
250
+ }
239
251
  }
240
252
  };
241
253
  async function run(args) {
242
254
  const { config, task } = args;
243
- const { minibatchSize = DEFAULT_MINIBATCH_SIZE, candidates: candidateCount = DEFAULT_CANDIDATES, maxSteps = DEFAULT_MAX_STEPS, maxDemos = DEFAULT_MAX_DEMOS, samplingTemperature = DEFAULT_TEMPERATURE, candidateTemperature = DEFAULT_TEMPERATURE, strategies, maxReflectionCalls, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
244
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, demoComponents = [], instructionComponents, renderDemo, buildAdvicePrompt: buildPrompt = buildAdvicePrompt, sampler = require_sampling.createEpochShuffledSampler({ minibatchSize }), maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
245
- const components = require_evaluation.componentNames(seedCandidate);
246
- const ruleComponents = instructionComponents ?? components.filter((name) => !demoComponents.includes(name));
255
+ const { minibatchSize = DEFAULT_MINIBATCH_SIZE, candidates: candidateCount = DEFAULT_CANDIDATES, concurrency = 1, maxSteps = DEFAULT_MAX_STEPS, maxDemos = DEFAULT_MAX_DEMOS, samplingTemperature = DEFAULT_TEMPERATURE, candidateTemperature = DEFAULT_TEMPERATURE, strategies, maxReflectionCalls, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
256
+ const { seedCandidate, trainingSet, validationSet: requestedValidationSet, testSet, adapter, reflect, demoComponents = [], instructionComponents, renderDemo, buildAdvicePrompt: buildPrompt = buildAdvicePrompt, sampler = require_sampling.createEpochShuffledSampler({ minibatchSize }), maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_warnings.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
257
+ const { validationSet, warnings } = require_warnings.resolveValidationSet({
258
+ validationSet: requestedValidationSet,
259
+ trainingSet
260
+ });
261
+ const emit = require_warnings.createEmitter(reporters);
262
+ const components = require_warnings.componentNames(seedCandidate);
263
+ const nonDemo = components.filter((name) => !demoComponents.includes(name));
264
+ const ruleComponents = instructionComponents ?? (nonDemo.length > 0 ? nonDemo : components);
247
265
  const enabled = strategies ?? (demoComponents.length > 0 ? ["appendDemo", "appendRule"] : ["appendRule"]);
248
266
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
249
267
  if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
@@ -252,6 +270,10 @@ async function run(args) {
252
270
  if (enabled.includes("appendDemo") && demoComponents.length === 0) throw new Error("the appendDemo strategy requires at least one entry in demoComponents");
253
271
  if (enabled.includes("appendRule") && ruleComponents.length === 0) throw new Error("the appendRule strategy requires at least one instruction component to write into");
254
272
  if (testSet !== void 0 && testSet.length === 0) throw new Error("optimize requires a non-empty testSet when one is given; omit it to skip held-out evaluation");
273
+ const trainingIds = trainingSet.map((datum, index) => instanceId({
274
+ datum,
275
+ index
276
+ }));
255
277
  const validationIds = validationSet.map((datum, index) => instanceId({
256
278
  datum,
257
279
  index
@@ -260,28 +282,25 @@ async function run(args) {
260
282
  datum,
261
283
  index
262
284
  })) ?? [];
263
- const fingerprint = require_evaluation.runFingerprint({
285
+ const fingerprint = require_warnings.runFingerprint({
264
286
  seedCandidate,
265
- trainingIds: trainingSet.map((datum, index) => instanceId({
266
- datum,
267
- index
268
- })),
287
+ trainingIds,
269
288
  validationIds,
270
289
  seed,
271
290
  ...cacheNamespace === void 0 ? {} : { cacheNamespace }
272
291
  });
273
- require_evaluation.assertResumable({
292
+ require_warnings.assertResumable({
274
293
  fingerprint,
275
294
  ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
276
295
  });
277
- const deadline = require_evaluation.createDeadline({ maxWallClockMs });
296
+ const deadline = require_warnings.createDeadline({ maxWallClockMs });
278
297
  const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
279
- const budget = require_evaluation.createBudget({
298
+ const budget = require_warnings.createBudget({
280
299
  maxMetricCalls,
281
300
  spent: resumeFrom?.metricCalls ?? 0
282
301
  });
283
- const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
284
- const evaluator = require_evaluation.createEvaluator({
302
+ const evaluationCache = cache === false ? void 0 : cache ?? require_warnings.createMemoryCache();
303
+ const evaluator = require_warnings.createEvaluator({
285
304
  adapter,
286
305
  budget,
287
306
  ...retry === void 0 ? {} : { retry },
@@ -289,8 +308,9 @@ async function run(args) {
289
308
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
290
309
  trackOutputs: true,
291
310
  cacheHits: resumeFrom?.cacheHits ?? 0,
311
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
292
312
  ...signal === void 0 ? {} : { signal },
293
- onEvaluation: (event) => onEvent?.({
313
+ onEvaluation: (event) => emit({
294
314
  type: "evaluation",
295
315
  ...event
296
316
  })
@@ -306,7 +326,7 @@ async function run(args) {
306
326
  let step = resumeFrom?.step ?? 0;
307
327
  let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
308
328
  let stopReason = "maxSteps";
309
- onEvent?.({
329
+ emit({
310
330
  type: "start",
311
331
  components,
312
332
  validationSetSize: validationSet.length
@@ -318,7 +338,7 @@ async function run(args) {
318
338
  stopReason = "aborted";
319
339
  break;
320
340
  }
321
- if (require_evaluation.costExhausted({
341
+ if (require_warnings.costExhausted({
322
342
  usage: evaluator.usage(),
323
343
  maxCostUsd
324
344
  })) {
@@ -341,16 +361,17 @@ async function run(args) {
341
361
  scores: programs.map((_, index) => averageScore(index)),
342
362
  k: candidateCount
343
363
  });
344
- onEvent?.({
364
+ emit({
345
365
  type: "stepStart",
346
366
  step,
347
367
  poolSize: pool.length
348
368
  });
349
- const batch = sampler({
369
+ const batchIndices = sampler({
350
370
  trainingSet,
351
371
  iteration: step,
352
372
  rng
353
- }).map((index) => trainingSet[index]);
373
+ });
374
+ const batch = batchIndices.map((index) => trainingSet[index]);
354
375
  const samples = [];
355
376
  for (let slot = 0; slot < candidateCount; slot += 1) {
356
377
  const programIndex = softmaxSample({
@@ -372,7 +393,7 @@ async function run(args) {
372
393
  outputs: evaluation.outputs,
373
394
  ...evaluation.feedback === void 0 ? {} : { feedback: evaluation.feedback }
374
395
  });
375
- programScores[programIndex].push(require_evaluation.measuredMean(evaluation) ?? 0);
396
+ programScores[programIndex].push(require_warnings.measuredMean(evaluation) ?? 0);
376
397
  }
377
398
  if (samples.length === 0) {
378
399
  stopReason = "budgetExhausted";
@@ -412,7 +433,7 @@ async function run(args) {
412
433
  high
413
434
  });
414
435
  } catch (err) {
415
- onEvent?.({
436
+ emit({
416
437
  type: "error",
417
438
  step,
418
439
  err
@@ -427,36 +448,54 @@ async function run(args) {
427
448
  });
428
449
  }
429
450
  let stepBest;
451
+ const batchIds = batchIndices.map((index) => trainingIds[index]);
452
+ const scheduled = [];
453
+ let owed = 0;
430
454
  for (const entry of built) {
431
- if (!budget.canAfford(batch.length)) {
455
+ if (!budget.canAfford(owed + batch.length)) {
432
456
  stopReason = "budgetExhausted";
433
457
  break;
434
458
  }
435
- let score;
436
- try {
437
- score = require_evaluation.measuredMean(await evaluator.evaluate({
438
- candidate: entry.candidate,
439
- batch,
440
- ids: batch.map((datum, index) => instanceId({
441
- datum,
442
- index
443
- })),
444
- split: "train",
445
- phase: "minibatch",
446
- candidateId: programs.length,
447
- iteration: step
448
- }));
449
- } catch (err) {
450
- if (err instanceof require_evaluation.BudgetExhausted) {
451
- stopReason = "budgetExhausted";
452
- break;
459
+ owed += evaluator.countUncached({
460
+ candidate: entry.candidate,
461
+ ids: batchIds,
462
+ split: "train"
463
+ });
464
+ scheduled.push(entry);
465
+ }
466
+ const poolBase = programs.length;
467
+ const scored = await mapDistinct({
468
+ items: scheduled,
469
+ limit: concurrency,
470
+ key: (entry) => require_warnings.candidateHash(entry.candidate),
471
+ task: async (entry, index) => {
472
+ try {
473
+ return { score: require_warnings.measuredMean(await evaluator.evaluate({
474
+ candidate: entry.candidate,
475
+ batch,
476
+ ids: batchIds,
477
+ split: "train",
478
+ phase: "minibatch",
479
+ candidateId: poolBase + index,
480
+ iteration: step
481
+ })) };
482
+ } catch (err) {
483
+ if (err instanceof require_warnings.BudgetExhausted) return { stop: true };
484
+ throw err;
453
485
  }
454
- throw err;
455
486
  }
487
+ });
488
+ for (const [index, outcome] of scored.entries()) {
489
+ if (outcome.stop === true) {
490
+ stopReason = "budgetExhausted";
491
+ break;
492
+ }
493
+ const { score } = outcome;
456
494
  if (score === void 0) continue;
495
+ const entry = scheduled[index];
457
496
  programs.push(entry.candidate);
458
497
  programScores.push([score]);
459
- onEvent?.({
498
+ emit({
460
499
  type: "candidate",
461
500
  step,
462
501
  strategy: entry.strategy,
@@ -479,38 +518,78 @@ async function run(args) {
479
518
  }
480
519
  }
481
520
  if (signal?.aborted) stopReason = "aborted";
482
- const finalists = [];
521
+ const contenders = [];
522
+ let owedForFinalists = 0;
483
523
  for (const index of evenlySpacedIndices({
484
524
  length: winners.length,
485
525
  count: candidateCount + 1
486
526
  })) {
487
527
  const winner = winners[index];
488
- if (!budget.canAfford(evaluator.countUncached({
528
+ const uncached = evaluator.countUncached({
489
529
  candidate: winner.candidate,
490
530
  ids: validationIds,
491
531
  split: "val"
492
- }))) break;
493
- let score;
494
- try {
495
- score = require_evaluation.measuredMean(await evaluator.evaluate({
496
- candidate: winner.candidate,
497
- batch: validationSet,
498
- ids: validationIds,
499
- split: "val",
500
- phase: index === 0 ? "seed" : "validation",
501
- candidateId: index,
502
- iteration: step
503
- }));
504
- } catch (err) {
505
- if (err instanceof require_evaluation.BudgetExhausted || signal?.aborted) break;
506
- throw err;
507
- }
508
- if (score !== void 0) finalists.push({
532
+ });
533
+ if (!budget.canAfford(owedForFinalists + uncached)) break;
534
+ owedForFinalists += uncached;
535
+ contenders.push({
536
+ index,
509
537
  candidate: winner.candidate,
510
- score,
511
538
  step: winner.step
512
539
  });
513
540
  }
541
+ const sweeps = await mapDistinct({
542
+ items: contenders,
543
+ limit: concurrency,
544
+ key: (contender) => require_warnings.candidateHash(contender.candidate),
545
+ task: async (contender) => {
546
+ if (signal?.aborted) return { stop: true };
547
+ try {
548
+ const evaluation = await evaluator.evaluate({
549
+ candidate: contender.candidate,
550
+ batch: validationSet,
551
+ ids: validationIds,
552
+ split: "val",
553
+ phase: contender.index === 0 ? "seed" : "validation",
554
+ candidateId: contender.index,
555
+ iteration: step
556
+ });
557
+ return {
558
+ score: require_warnings.measuredMean(evaluation),
559
+ evaluation
560
+ };
561
+ } catch (err) {
562
+ if (err instanceof require_warnings.BudgetExhausted || signal?.aborted) return { stop: true };
563
+ throw err;
564
+ }
565
+ }
566
+ });
567
+ const finalists = [];
568
+ let acceptedCandidates = -1;
569
+ let acceptedScore = Number.NEGATIVE_INFINITY;
570
+ for (const [position, outcome] of sweeps.entries()) {
571
+ if (outcome.stop === true) break;
572
+ if (outcome.score === void 0) continue;
573
+ const contender = contenders[position];
574
+ finalists.push({
575
+ candidate: contender.candidate,
576
+ score: outcome.score,
577
+ step: contender.step
578
+ });
579
+ if (outcome.score > acceptedScore) {
580
+ acceptedScore = outcome.score;
581
+ acceptedCandidates += 1;
582
+ emit({
583
+ type: "candidateAccepted",
584
+ step: contender.step,
585
+ candidateId: acceptedCandidates,
586
+ candidate: contender.candidate,
587
+ aggregateScore: outcome.score,
588
+ instanceScores: require_warnings.instanceRow(outcome.evaluation),
589
+ ...trackBestOutputs ? { outputs: outcome.evaluation.outputs } : {}
590
+ });
591
+ }
592
+ }
514
593
  const seedScore = finalists[0]?.score ?? 0;
515
594
  finalists.sort((a, b) => b.score - a.score);
516
595
  const best = finalists[0] ?? {
@@ -526,7 +605,7 @@ async function run(args) {
526
605
  candidateId: null,
527
606
  iteration: step
528
607
  }))?.outputs;
529
- const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
608
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
530
609
  candidate: best.candidate,
531
610
  batch: testSet,
532
611
  ids: testIds,
@@ -535,13 +614,18 @@ async function run(args) {
535
614
  candidateId: null,
536
615
  iteration: step,
537
616
  charge: false
538
- }));
539
- onEvent?.({
617
+ });
618
+ const testScore = heldOut === void 0 ? void 0 : require_warnings.measuredMean(heldOut);
619
+ emit({
540
620
  type: "finish",
541
621
  reason: stopReason,
622
+ warnings,
623
+ bestCandidateId: acceptedCandidates,
542
624
  bestScore: best.score,
543
625
  metricCalls: budget.spent(),
544
- ...testScore === void 0 ? {} : { testScore }
626
+ ...testScore === void 0 ? {} : { testScore },
627
+ ...heldOut === void 0 ? {} : { testInstanceScores: require_warnings.instanceRow(heldOut) },
628
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
545
629
  });
546
630
  return {
547
631
  bestCandidate: best.candidate,
@@ -557,8 +641,10 @@ async function run(args) {
557
641
  ...bestOutputs === void 0 ? {} : { bestOutputs },
558
642
  ...testScore === void 0 ? {} : {
559
643
  testScore,
560
- testMetricCalls: testSet?.length ?? 0
644
+ testMetricCalls: evaluator.unchargedCalls(),
645
+ testUsage: evaluator.unchargedUsage()
561
646
  },
647
+ warnings,
562
648
  stopReason
563
649
  };
564
650
  /**
@@ -594,7 +680,11 @@ async function run(args) {
594
680
  const drops = /* @__PURE__ */ new Set();
595
681
  for (let draw = 0; draw < Math.min(wanted, held); draw += 1) drops.add(rng.nextInt(held));
596
682
  const next = { ...candidate };
597
- for (const [name, demos] of blocks) next[name] = require_demos.formatDemos(demos.filter((_, index) => !drops.has(index)), renderDemo === void 0 ? {} : { render: renderDemo });
683
+ for (const [name, demos] of blocks) next[name] = require_demos.replaceDemos({
684
+ text: next[name] ?? "",
685
+ demos: demos.filter((_, index) => !drops.has(index)),
686
+ ...renderDemo === void 0 ? {} : { render: renderDemo }
687
+ });
598
688
  return next;
599
689
  }
600
690
  /**
@@ -615,8 +705,12 @@ async function run(args) {
615
705
  };
616
706
  const next = { ...candidate };
617
707
  for (const name of demoComponents) {
618
- const kept = require_demos.parseDemos(next[name] ?? "");
619
- next[name] = require_demos.formatDemos([...kept, demo], renderDemo === void 0 ? {} : { render: renderDemo });
708
+ const text = next[name] ?? "";
709
+ next[name] = require_demos.replaceDemos({
710
+ text,
711
+ demos: [...require_demos.parseDemos(text), demo],
712
+ ...renderDemo === void 0 ? {} : { render: renderDemo }
713
+ });
620
714
  }
621
715
  return next;
622
716
  }
@@ -644,6 +738,7 @@ async function run(args) {
644
738
  const response = await reflect({
645
739
  prompt: buildPrompt({
646
740
  components: ruleComponents,
741
+ current: Object.fromEntries(ruleComponents.map((name) => [name, candidate[name] ?? ""])),
647
742
  input: bucket.datum,
648
743
  ...better === void 0 ? {} : { better },
649
744
  ...worse === void 0 ? {} : { worse }
@@ -684,6 +779,7 @@ async function run(args) {
684
779
  metricCalls: budget.spent(),
685
780
  reflectionCalls,
686
781
  cacheHits: evaluator.cacheHits(),
782
+ usage: evaluator.usage(),
687
783
  rngState: rng.state(),
688
784
  ...sampler.state === void 0 ? {} : { sampler: sampler.state() },
689
785
  ...cached === void 0 ? {} : { cache: cached }
@@ -694,14 +790,51 @@ async function run(args) {
694
790
  await onCheckpoint(takeSnapshot(completed));
695
791
  }
696
792
  }
697
- function defaultInstanceId(args) {
698
- return String(args.index);
793
+ /**
794
+ * Runs `items` concurrently, except that items sharing a key run one after the
795
+ * other.
796
+ *
797
+ * Two identical candidates cost one evaluation and one cache hit when they are
798
+ * scored in sequence, and two evaluations when they overlap: the cache is only
799
+ * written once a rollout returns. Serializing the duplicates is what keeps a
800
+ * fan-out from buying a second copy of a score the run has already paid for.
801
+ */
802
+ async function mapDistinct(args) {
803
+ const { items, limit, key, task } = args;
804
+ const groups = /* @__PURE__ */ new Map();
805
+ items.forEach((item, index) => {
806
+ const group = groups.get(key(item));
807
+ if (group === void 0) {
808
+ groups.set(key(item), [index]);
809
+ return;
810
+ }
811
+ group.push(index);
812
+ });
813
+ const results = new Array(items.length);
814
+ await require_concurrency.mapWithConcurrency({
815
+ items: [...groups.values()],
816
+ limit,
817
+ task: async (indices) => {
818
+ for (const index of indices) results[index] = await task(items[index], index);
819
+ }
820
+ });
821
+ return results;
822
+ }
823
+ /**
824
+ * Range checks on the search knobs, run at construction so a configuration
825
+ * that could never terminate is refused before a task is ever handed to it.
826
+ */
827
+ function assertSimbaConfig(config) {
828
+ const { concurrency = 1 } = config;
829
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
699
830
  }
700
831
  //#endregion
701
832
  exports.SimbaOptimizer = SimbaOptimizer;
702
833
  exports.buildAdvicePrompt = buildAdvicePrompt;
703
834
  exports.buildBuckets = buildBuckets;
704
835
  exports.evenlySpacedIndices = evenlySpacedIndices;
836
+ exports.isCandidateAccepted = require_warnings.isCandidateAccepted;
837
+ exports.isRunFinished = require_warnings.isRunFinished;
705
838
  exports.parseAdvice = parseAdvice;
706
839
  exports.percentile = percentile;
707
840
  exports.samplePoisson = samplePoisson;
@@ -1,8 +1,8 @@
1
- import { n as Candidate, t as Adapter, u as TextModel } from "../types-CWv4IQFF.cjs";
1
+ import { S as UsageTotals, a as RunFinished, f as Adapter, i as Reporter, n as OptimizerEvent, o as isCandidateAccepted, p as Candidate, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted, x as TextModel } from "../reporting-bq007_2z.cjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.cjs";
3
3
  import { t as Rng } from "../rng-BR5MOedA.cjs";
4
- import { r as DemoRenderer } from "../demos-BTuzFNsp.cjs";
5
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-B7SpRwl7.cjs";
4
+ import { r as DemoRenderer } from "../demos-ByaLZy-Z.cjs";
5
+ import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-4Zv-Zt2t.cjs";
6
6
  import { t as BatchSampler } from "../sampling-axOwfZf5.cjs";
7
7
  //#region src/simba/advice.d.ts
8
8
  interface AdviceTrajectory<Output = unknown> {
@@ -13,6 +13,12 @@ interface AdviceTrajectory<Output = unknown> {
13
13
  interface AdvicePromptArgs<Datum = unknown, Output = unknown> {
14
14
  /** Components the advice is wanted for, named so each can be addressed. */
15
15
  components: readonly string[];
16
+ /**
17
+ * What each of those components currently says. Advice is appended to this
18
+ * text rather than replacing it, so a proposer that cannot see it writes
19
+ * blind: it restates what is already there, and cannot correct it.
20
+ */
21
+ current: Record<string, string>;
16
22
  input: Datum;
17
23
  /** The higher scoring run of this instance, when there is one to contrast. */
18
24
  better?: AdviceTrajectory<Output>;
@@ -52,11 +58,41 @@ interface SimbaFinalist<K extends string = string> {
52
58
  step: number;
53
59
  }
54
60
  interface SimbaConfig {
55
- /** Instances per step. Default 32. */
61
+ /**
62
+ * Instances per step. Default 32.
63
+ *
64
+ * Minibatch defaults do not transfer between optimizers — GEPA's 3 and
65
+ * MIPRO's 35 mean different things, and SIMBA's ranks instances within a
66
+ * batch by how much its programs disagreed.
67
+ *
68
+ * @see docs/tuning.md
69
+ */
56
70
  minibatchSize?: number;
57
71
  /** Programs sampled per step, and candidates built from them. Default 6. */
58
72
  candidates?: number;
59
- /** Steps to run. Default 8. */
73
+ /**
74
+ * How many evaluations may be in flight at once. Default 1.
75
+ *
76
+ * Covers the two places a step's work is independent — scoring the
77
+ * candidates a step built, and sweeping the finalists at the end of the run
78
+ * — and nothing else. The trajectory samples and the mutations that read
79
+ * them are deliberately left in sequence: each of those reads state the one
80
+ * before it wrote, so overlapping them would make a seeded run depend on
81
+ * which call returned first.
82
+ */
83
+ concurrency?: number;
84
+ /**
85
+ * Steps to run. Default 8.
86
+ *
87
+ * A run has to be funded past the finalist reserve before any step happens:
88
+ * `min(candidates + 1, maxSteps + 1) * validationSet.length` rollouts are
89
+ * held back to sweep the finalists, and each step then costs
90
+ * `candidates * minibatchSize + minibatchSize`. Fund only the steps and the
91
+ * run stops after one, reporting `budgetExhausted` like any other exhausted
92
+ * budget.
93
+ *
94
+ * @see docs/tuning.md
95
+ */
60
96
  maxSteps?: number;
61
97
  /** Demos a candidate may hold before the loop starts dropping them. Default 4. */
62
98
  maxDemos?: number;
@@ -97,6 +133,8 @@ interface SimbaSnapshot {
97
133
  metricCalls: number;
98
134
  reflectionCalls: number;
99
135
  cacheHits: number;
136
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
137
+ usage?: UsageTotals;
100
138
  rngState: number;
101
139
  sampler?: unknown;
102
140
  cache?: [string, CachedScore][];
@@ -121,7 +159,8 @@ interface SimbaTask<Datum, Trajectory = unknown, Output = unknown, K extends str
121
159
  index: number;
122
160
  }) => string;
123
161
  cache?: EvaluationCache | false;
124
- onEvent?: (event: SimbaEvent<NoInfer<K>>) => void;
162
+ /** Observers of the run. Every one sees every event; none can fail it. */
163
+ reporters?: readonly Reporter<SimbaEvent<NoInfer<K>>>[];
125
164
  onCheckpoint?: (snapshot: SimbaSnapshot) => void | Promise<void>;
126
165
  resumeFrom?: SimbaSnapshot;
127
166
  }
@@ -143,17 +182,18 @@ type SimbaEvent<K extends string = string> = {
143
182
  /** The program it was mutated from; 0 is the seed. */
144
183
  sourceProgram: number;
145
184
  minibatchScore: number;
146
- } | {
185
+ } | ({
186
+ type: "candidateAccepted";
187
+ /** The step whose winner this was. */
188
+ step: number;
189
+ } & CandidateAccepted<K>) | {
147
190
  type: "error";
148
191
  step: number;
149
192
  err: unknown;
150
- } | {
193
+ } | ({
151
194
  type: "finish";
152
195
  reason: SimbaStopReason;
153
- bestScore: number;
154
- metricCalls: number;
155
- testScore?: number;
156
- };
196
+ } & RunFinished);
157
197
  interface SimbaResult<K extends string = string, Output = unknown> extends OptimizerResult<K, SimbaStopReason, Output> {
158
198
  /** The seed's own validation score, so the lift the run bought is readable. */
159
199
  seedScore: number;
@@ -286,4 +326,4 @@ declare function evenlySpacedIndices(args: {
286
326
  count: number;
287
327
  }): number[];
288
328
  //#endregion
289
- export { type AdvicePromptArgs, type AdvicePromptBuilder, type AdviceTrajectory, type SimbaBucket, type SimbaConfig, type SimbaEvent, type SimbaFinalist, SimbaOptimizer, type SimbaResult, type SimbaRollout, type SimbaSample, type SimbaSnapshot, type SimbaStopReason, type SimbaStrategy, type SimbaTask, buildAdvicePrompt, buildBuckets, evenlySpacedIndices, parseAdvice, percentile, samplePoisson, softmaxWeights, topKPlusBaseline };
329
+ export { type AdvicePromptArgs, type AdvicePromptBuilder, type AdviceTrajectory, type CandidateAccepted, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, type SimbaBucket, type SimbaConfig, type SimbaEvent, type SimbaFinalist, SimbaOptimizer, type SimbaResult, type SimbaRollout, type SimbaSample, type SimbaSnapshot, type SimbaStopReason, type SimbaStrategy, type SimbaTask, buildAdvicePrompt, buildBuckets, evenlySpacedIndices, isCandidateAccepted, isRunFinished, parseAdvice, percentile, samplePoisson, softmaxWeights, topKPlusBaseline };