textopt 0.0.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +509 -0
  3. package/dist/bootstrap-search/index.cjs +308 -0
  4. package/dist/bootstrap-search/index.d.cts +162 -0
  5. package/dist/bootstrap-search/index.d.mts +162 -0
  6. package/dist/bootstrap-search/index.mjs +307 -0
  7. package/dist/cache-CuSo0NJ8.d.cts +24 -0
  8. package/dist/cache-CuSo0NJ8.d.mts +24 -0
  9. package/dist/concurrency-C-cFzWW2.cjs +44 -0
  10. package/dist/concurrency-D58PWeSk.mjs +39 -0
  11. package/dist/demos-B0pVQjYC.d.mts +88 -0
  12. package/dist/demos-B9BJiNKz.cjs +143 -0
  13. package/dist/demos-BTuzFNsp.d.cts +88 -0
  14. package/dist/demos-Degx6UmP.mjs +126 -0
  15. package/dist/evaluation-BV0nSZVx.mjs +521 -0
  16. package/dist/evaluation-OZOp6TB7.cjs +598 -0
  17. package/dist/file-cache.cjs +70 -0
  18. package/dist/file-cache.d.cts +21 -0
  19. package/dist/file-cache.d.mts +21 -0
  20. package/dist/file-cache.mjs +69 -0
  21. package/dist/gepa/index.cjs +1671 -0
  22. package/dist/gepa/index.d.cts +385 -0
  23. package/dist/gepa/index.d.mts +385 -0
  24. package/dist/gepa/index.mjs +1652 -0
  25. package/dist/index.cjs +266 -0
  26. package/dist/index.d.cts +221 -0
  27. package/dist/index.d.mts +221 -0
  28. package/dist/index.mjs +245 -0
  29. package/dist/math-COOofUyv.cjs +101 -0
  30. package/dist/math-DhrDmpFS.mjs +78 -0
  31. package/dist/mipro/index.cjs +739 -0
  32. package/dist/mipro/index.d.cts +372 -0
  33. package/dist/mipro/index.d.mts +372 -0
  34. package/dist/mipro/index.mjs +736 -0
  35. package/dist/opro/index.cjs +487 -0
  36. package/dist/opro/index.d.cts +230 -0
  37. package/dist/opro/index.d.mts +230 -0
  38. package/dist/opro/index.mjs +485 -0
  39. package/dist/optimizer-B7SpRwl7.d.cts +288 -0
  40. package/dist/optimizer-DqCoth_w.d.mts +288 -0
  41. package/dist/random-search/index.cjs +321 -0
  42. package/dist/random-search/index.d.cts +156 -0
  43. package/dist/random-search/index.d.mts +156 -0
  44. package/dist/random-search/index.mjs +319 -0
  45. package/dist/reflection-CQToe-5B.d.cts +283 -0
  46. package/dist/reflection-Cr_upzU0.d.mts +283 -0
  47. package/dist/reflection-DRfbk6hu.cjs +249 -0
  48. package/dist/reflection-mwMhrjs_.mjs +214 -0
  49. package/dist/rng-BR5MOedA.d.cts +22 -0
  50. package/dist/rng-BR5MOedA.d.mts +22 -0
  51. package/dist/rng-DbA_rPIo.cjs +67 -0
  52. package/dist/rng-Dtc5eZ_W.mjs +62 -0
  53. package/dist/sampling-CfHt7Gue.mjs +59 -0
  54. package/dist/sampling-DFo_7RNJ.d.mts +23 -0
  55. package/dist/sampling-Dars7ctR.cjs +64 -0
  56. package/dist/sampling-axOwfZf5.d.cts +23 -0
  57. package/dist/simba/index.cjs +709 -0
  58. package/dist/simba/index.d.cts +289 -0
  59. package/dist/simba/index.d.mts +289 -0
  60. package/dist/simba/index.mjs +700 -0
  61. package/dist/testing.cjs +155 -0
  62. package/dist/testing.d.cts +53 -0
  63. package/dist/testing.d.mts +53 -0
  64. package/dist/testing.mjs +148 -0
  65. package/dist/text--v4Ffbus.mjs +21 -0
  66. package/dist/text-CK_HB3su.cjs +26 -0
  67. package/dist/types-CWv4IQFF.d.cts +129 -0
  68. package/dist/types-CWv4IQFF.d.mts +129 -0
  69. package/package.json +135 -0
@@ -0,0 +1,487 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_evaluation = require("../evaluation-OZOp6TB7.cjs");
3
+ const require_concurrency = require("../concurrency-C-cFzWW2.cjs");
4
+ const require_text = require("../text-CK_HB3su.cjs");
5
+ const require_rng = require("../rng-DbA_rPIo.cjs");
6
+ //#region src/opro/optimize.ts
7
+ /**
8
+ * Rounds that may pass without a proposal nobody has tried before, per
9
+ * component, before the run gives up.
10
+ *
11
+ * Such a round spends no rollouts, so the budget guard — the only thing
12
+ * bounding a run at the default round limit — never fires, and a proposal
13
+ * model that has settled on one answer would loop forever. Waiting a few
14
+ * rounds costs one prompt each and leaves room for a stochastic model to find
15
+ * something new; waiting indefinitely is a hang.
16
+ */
17
+ const BARREN_ROUNDS = 3;
18
+ const DEFAULT_PROPOSALS_PER_ROUND = 8;
19
+ const DEFAULT_HISTORY_SIZE = 20;
20
+ const DEFAULT_EXEMPLARS = 3;
21
+ const DEFAULT_SCORE_SCALE = 100;
22
+ const DEFAULT_FULL_EVAL_INTERVAL = 3;
23
+ /**
24
+ * Optimization by prompting: show the model what has been tried and what each
25
+ * attempt scored, and ask for something better.
26
+ *
27
+ * The regime this is for is the one GEPA cannot serve. GEPA's advantage over
28
+ * blind search is the per-instance feedback string — a diagnosis of *why* a
29
+ * rollout failed. Plenty of metrics cannot produce one: a reward model, a
30
+ * preference score, a classifier's accuracy over a closed label set. Handed
31
+ * those, reflection is asked to diagnose a failure it has no evidence about,
32
+ * and pays a frontier-model call to guess.
33
+ *
34
+ * This search asks for less and needs less. One scalar per candidate is the
35
+ * entire signal, and the model reasons over the *trajectory* of scores rather
36
+ * than the anatomy of a single failure. Where feedback does exist, use GEPA:
37
+ * a score history is a much thinner channel than a paragraph saying what broke.
38
+ */
39
+ var OproOptimizer = class {
40
+ #config;
41
+ constructor(config = {}) {
42
+ assertConfig(config);
43
+ this.#config = config;
44
+ }
45
+ async optimize(task) {
46
+ return runOpro({
47
+ config: this.#config,
48
+ task
49
+ });
50
+ }
51
+ };
52
+ /**
53
+ * Adapted from the meta-prompt in *Large Language Models as Optimizers* (Yang
54
+ * et al., 2023). Ascending order is load-bearing: the paper finds the model
55
+ * attends most to what is nearest the end of the prompt, so the best attempt
56
+ * has to be last.
57
+ *
58
+ * Exemplars carry an `<INS>` marker for the same reason the paper's do: an
59
+ * instruction written against the task in the abstract reads differently from
60
+ * one written to sit in a particular slot. The marker is placed ahead of the
61
+ * input, which is where a component's text usually goes but which this
62
+ * optimizer cannot actually know — only the adapter composes the real prompt.
63
+ * The paper also shows each exemplar's gold answer. Nothing here needs to know
64
+ * what that is: `renderDatum` is handed the whole datum, so a caller holding
65
+ * labels can render them alongside the input.
66
+ */
67
+ function buildOproPrompt(args) {
68
+ const { componentName, history, exemplars } = args;
69
+ return [
70
+ `I am tuning the "${componentName}" component of a larger system. Below are the instructions I have tried, each with the score it achieved. Higher scores are better.`,
71
+ "",
72
+ "<attempts>",
73
+ history.map((attempt) => `score: ${attempt.score}\n<instruction>\n${attempt.text}\n</instruction>`).join("\n\n"),
74
+ "</attempts>",
75
+ ...exemplars.length === 0 ? [] : [
76
+ "",
77
+ "Here are examples of the inputs this component receives. `<INS>` marks where your instruction is read:",
78
+ "",
79
+ "<inputs>",
80
+ exemplars.map((exemplar) => `input:\n<INS>\n${exemplar}`).join("\n\n"),
81
+ "</inputs>"
82
+ ],
83
+ "",
84
+ "Write a new instruction that will score higher than every instruction above.",
85
+ "Work out what the higher-scoring instructions do that the lower-scoring ones do not, and push further in that direction.",
86
+ "Do not repeat an instruction that has already been tried.",
87
+ "",
88
+ "Return only the new instruction, inside a ``` block."
89
+ ].join("\n");
90
+ }
91
+ async function runOpro(args) {
92
+ const { config, task } = args;
93
+ 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);
97
+ if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
98
+ if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
99
+ if (components.length === 0) throw new Error("optimize requires a seed candidate with at least one component");
100
+ 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");
101
+ const validationIds = validationSet.map((datum, index) => instanceId({
102
+ datum,
103
+ index
104
+ }));
105
+ const testIds = testSet?.map((datum, index) => instanceId({
106
+ datum,
107
+ index
108
+ })) ?? [];
109
+ const fingerprint = require_evaluation.runFingerprint({
110
+ seedCandidate,
111
+ trainingIds: trainingSet.map((datum, index) => instanceId({
112
+ datum,
113
+ index
114
+ })),
115
+ validationIds,
116
+ seed,
117
+ ...cacheNamespace === void 0 ? {} : { cacheNamespace }
118
+ });
119
+ require_evaluation.assertResumable({
120
+ fingerprint,
121
+ ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
122
+ });
123
+ const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
124
+ const budget = require_evaluation.createBudget({
125
+ maxMetricCalls,
126
+ spent: resumeFrom?.metricCalls ?? 0
127
+ });
128
+ const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
129
+ const evaluator = require_evaluation.createEvaluator({
130
+ adapter,
131
+ budget,
132
+ ...retry === void 0 ? {} : { retry },
133
+ ...cacheNamespace === void 0 ? {} : { cacheNamespace },
134
+ ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
135
+ trackOutputs: trackBestOutputs,
136
+ cacheHits: resumeFrom?.cacheHits ?? 0,
137
+ ...signal === void 0 ? {} : { signal },
138
+ onEvaluation: (event) => onEvent?.({
139
+ type: "evaluation",
140
+ ...event
141
+ })
142
+ });
143
+ evaluator.restore(resumeFrom?.cache ?? []);
144
+ function drawExemplars() {
145
+ return rng.sample(trainingSet, Math.min(exemplars, trainingSet.length)).map(renderDatum);
146
+ }
147
+ const scoringIndices = resumeFrom?.scoringIndices ?? (scoringSetSize === void 0 ? void 0 : rng.sample(trainingSet.map((_, index) => index), Math.min(scoringSetSize, trainingSet.length)));
148
+ const scoringSet = scoringIndices?.map((index) => trainingSet[index]);
149
+ const scoringIds = scoringIndices?.map((index) => instanceId({
150
+ datum: trainingSet[index],
151
+ index
152
+ }));
153
+ const histories = new Map(components.map((name) => [name, [...resumeFrom?.histories[name] ?? []]]));
154
+ function contextOf(candidate, component) {
155
+ const rest = {};
156
+ for (const name of components) if (name !== component) rest[name] = candidate[name];
157
+ return require_evaluation.stableHash(rest);
158
+ }
159
+ const trajectory = [];
160
+ let round = resumeFrom?.round ?? 0;
161
+ let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
162
+ let stopReason = "maxRounds";
163
+ onEvent?.({
164
+ type: "start",
165
+ components,
166
+ validationSetSize: validationSet.length
167
+ });
168
+ async function sweep(candidate, phase) {
169
+ return evaluator.evaluate({
170
+ candidate,
171
+ batch: validationSet,
172
+ ids: validationIds,
173
+ split: "val",
174
+ phase,
175
+ candidateId: null,
176
+ iteration: round
177
+ });
178
+ }
179
+ /**
180
+ * Screens a candidate. With a scoring set that is a slice of the training set;
181
+ * without one it is the full validation set, and screening and reporting are the
182
+ * same measurement.
183
+ */
184
+ async function screen(candidate, phase) {
185
+ if (scoringSet === void 0) return sweep(candidate, phase);
186
+ return evaluator.evaluate({
187
+ candidate,
188
+ batch: scoringSet,
189
+ ids: scoringIds,
190
+ split: "train",
191
+ phase: "minibatch",
192
+ candidateId: null,
193
+ iteration: round
194
+ });
195
+ }
196
+ const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
197
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : require_evaluation.requireMeasuredMean({
198
+ batch: seedEvaluation,
199
+ phase: "seed"
200
+ });
201
+ let best = resumeFrom?.best ?? seedCandidate;
202
+ let bestScore = resumeFrom?.bestScore ?? seedScore;
203
+ /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
204
+ let bestOutputs = seedEvaluation?.outputs;
205
+ let reported = resumeFrom?.reported ?? seedCandidate;
206
+ let lastSwept = resumeFrom === void 0 ? seedCandidate : resumeFrom.incumbentSwept ? best : {};
207
+ let bestSearchScore = resumeFrom?.bestSearchScore ?? seedScore;
208
+ if (resumeFrom === void 0 && scoringSet !== void 0) bestSearchScore = require_evaluation.requireMeasuredMean({
209
+ batch: await screen(seedCandidate, "seed"),
210
+ phase: "seed"
211
+ });
212
+ /**
213
+ * Measures the incumbent on the whole validation set. The search chooses by subset
214
+ * score and can therefore chase something that only works on the subset, so
215
+ * what gets reported is the best candidate a full sweep has actually seen —
216
+ * never a subset number wearing a validation set label.
217
+ */
218
+ async function refreshIncumbent() {
219
+ if (best === lastSwept || !budget.canAfford(validationSet.length)) return "ok";
220
+ try {
221
+ const evaluation = await sweep(best, "validation");
222
+ const full = require_evaluation.measuredMean(evaluation);
223
+ lastSwept = best;
224
+ if (full !== void 0 && full > bestScore) {
225
+ reported = best;
226
+ bestScore = full;
227
+ bestOutputs = evaluation.outputs;
228
+ }
229
+ } catch (err) {
230
+ if (err instanceof require_evaluation.BudgetExhausted || signal?.aborted) return "stop";
231
+ throw err;
232
+ }
233
+ return "ok";
234
+ }
235
+ if (resumeFrom === void 0) {
236
+ for (const name of components) histories.get(name)?.push({
237
+ text: seedCandidate[name],
238
+ score: scaleScore(bestSearchScore, scoreScale),
239
+ context: contextOf(seedCandidate, name)
240
+ });
241
+ trajectory.push({
242
+ round: 0,
243
+ component: components[0],
244
+ candidate: seedCandidate,
245
+ score: seedScore
246
+ });
247
+ }
248
+ function takeSnapshot() {
249
+ const cached = checkpointCache ? evaluationCache?.entries?.() : void 0;
250
+ return {
251
+ version: 1,
252
+ fingerprint,
253
+ best,
254
+ reported,
255
+ incumbentSwept: best === lastSwept,
256
+ bestScore,
257
+ bestSearchScore,
258
+ seedScore,
259
+ round,
260
+ reflectionCalls,
261
+ metricCalls: budget.spent(),
262
+ cacheHits: evaluator.cacheHits(),
263
+ rngState: rng.state(),
264
+ histories: Object.fromEntries([...histories].map(([name, attempts]) => [name, [...attempts]])),
265
+ ...scoringIndices === void 0 ? {} : { scoringIndices },
266
+ ...cached === void 0 ? {} : { cache: cached }
267
+ };
268
+ }
269
+ async function checkpoint() {
270
+ if (onCheckpoint === void 0) return;
271
+ await onCheckpoint(takeSnapshot());
272
+ }
273
+ await checkpoint();
274
+ let barrenRounds = 0;
275
+ while (round < maxRounds) {
276
+ if (signal?.aborted) {
277
+ stopReason = "aborted";
278
+ break;
279
+ }
280
+ if (barrenRounds >= BARREN_ROUNDS * components.length) {
281
+ stopReason = "proposalsExhausted";
282
+ break;
283
+ }
284
+ if (require_evaluation.costExhausted({
285
+ usage: evaluator.usage(),
286
+ maxCostUsd
287
+ })) {
288
+ stopReason = "costExhausted";
289
+ break;
290
+ }
291
+ if (deadline.exceeded()) {
292
+ stopReason = "deadlineReached";
293
+ break;
294
+ }
295
+ if (reflectionCalls >= maxReflectionCalls) {
296
+ stopReason = "reflectionBudgetExhausted";
297
+ break;
298
+ }
299
+ if (!budget.canAfford(validationSet.length)) {
300
+ stopReason = "budgetExhausted";
301
+ break;
302
+ }
303
+ const component = components[round % components.length];
304
+ const history = histories.get(component);
305
+ const context = contextOf(best, component);
306
+ const comparable = history.filter((attempt) => attempt.context === context);
307
+ onEvent?.({
308
+ type: "roundStart",
309
+ round,
310
+ component,
311
+ historySize: comparable.length
312
+ });
313
+ const affordableProposals = Math.min(proposalsPerRound, maxReflectionCalls - reflectionCalls);
314
+ const prompt = buildPrompt({
315
+ componentName: component,
316
+ history: topAttempts({
317
+ history: comparable,
318
+ keep: historySize
319
+ }),
320
+ exemplars: drawExemplars()
321
+ });
322
+ reflectionCalls += affordableProposals;
323
+ const drawn = await require_concurrency.mapWithConcurrency({
324
+ items: Array.from({ length: affordableProposals }, (_, index) => index),
325
+ limit: concurrency,
326
+ signal,
327
+ task: async () => require_text.parseProposedText(await reflect({
328
+ prompt,
329
+ ...signal === void 0 ? {} : { signal }
330
+ }))
331
+ });
332
+ const tried = new Set(comparable.map((attempt) => attempt.text));
333
+ const unique = [...new Set(drawn)].filter((text) => text.length > 0 && !tried.has(text));
334
+ barrenRounds = unique.length === 0 ? barrenRounds + 1 : 0;
335
+ let roundStop;
336
+ for (const text of unique) {
337
+ const candidate = {
338
+ ...best,
339
+ [component]: text
340
+ };
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;
352
+ }
353
+ throw err;
354
+ }
355
+ const score = require_evaluation.measuredMean(evaluation);
356
+ if (score === void 0) continue;
357
+ const accepted = score > bestSearchScore;
358
+ history.push({
359
+ text,
360
+ score: scaleScore(score, scoreScale),
361
+ context
362
+ });
363
+ trajectory.push({
364
+ round: round + 1,
365
+ component,
366
+ candidate,
367
+ score
368
+ });
369
+ onEvent?.({
370
+ type: "attempt",
371
+ round,
372
+ component,
373
+ score,
374
+ accepted
375
+ });
376
+ if (accepted) {
377
+ best = candidate;
378
+ bestSearchScore = score;
379
+ if (scoringSet === void 0) {
380
+ reported = candidate;
381
+ lastSwept = candidate;
382
+ bestScore = score;
383
+ bestOutputs = evaluation.outputs;
384
+ }
385
+ const scaled = scaleScore(score, scoreScale);
386
+ for (const name of components) {
387
+ if (name === component) continue;
388
+ histories.get(name)?.push({
389
+ text: candidate[name],
390
+ score: scaled,
391
+ context: contextOf(candidate, name)
392
+ });
393
+ }
394
+ }
395
+ }
396
+ round += 1;
397
+ await checkpoint();
398
+ if (roundStop !== void 0) {
399
+ stopReason = roundStop;
400
+ break;
401
+ }
402
+ if (scoringSet !== void 0 && round % fullEvalInterval === 0) {
403
+ if (await refreshIncumbent() === "stop") {
404
+ stopReason = signal?.aborted ? "aborted" : "budgetExhausted";
405
+ break;
406
+ }
407
+ }
408
+ }
409
+ if (scoringSet !== void 0 && !signal?.aborted) await refreshIncumbent();
410
+ if (signal?.aborted) stopReason = "aborted";
411
+ const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
412
+ candidate: best,
413
+ batch: testSet,
414
+ ids: testIds,
415
+ split: "test",
416
+ phase: "test",
417
+ candidateId: null,
418
+ iteration: round,
419
+ charge: false
420
+ }));
421
+ onEvent?.({
422
+ type: "finish",
423
+ reason: stopReason,
424
+ bestScore,
425
+ metricCalls: budget.spent(),
426
+ ...testScore === void 0 ? {} : { testScore }
427
+ });
428
+ return {
429
+ snapshot: takeSnapshot(),
430
+ bestCandidate: reported,
431
+ bestScore,
432
+ usage: evaluator.usage(),
433
+ seedScore,
434
+ ...trackBestOutputs ? { bestOutputs } : {},
435
+ ...testScore === void 0 ? {} : {
436
+ testScore,
437
+ testMetricCalls: evaluator.unchargedCalls()
438
+ },
439
+ rounds: round,
440
+ trajectory,
441
+ metricCalls: budget.spent(),
442
+ reflectionCalls,
443
+ cacheHits: evaluator.cacheHits(),
444
+ stopReason
445
+ };
446
+ }
447
+ /**
448
+ * The strongest `keep` attempts, ordered weakest first. Dropping the weak tail
449
+ * rather than the old one is deliberate: the prompt's job is to show a
450
+ * gradient, and an attempt that scored badly a hundred rounds ago still marks
451
+ * the bottom of it.
452
+ */
453
+ function topAttempts(args) {
454
+ const { history, keep } = args;
455
+ return [...history].sort((a, b) => b.score - a.score).slice(0, keep).sort((a, b) => a.score - b.score);
456
+ }
457
+ function scaleScore(score, scale) {
458
+ return Math.round(score * scale);
459
+ }
460
+ function renderDefault(datum) {
461
+ if (typeof datum === "string") return datum;
462
+ try {
463
+ return JSON.stringify(datum, null, 2) ?? String(datum);
464
+ } catch {
465
+ return String(datum);
466
+ }
467
+ }
468
+ function assertConfig(config) {
469
+ const positive = [
470
+ ["proposalsPerRound", config.proposalsPerRound],
471
+ ["concurrency", config.concurrency],
472
+ ["maxRounds", config.maxRounds],
473
+ ["maxReflectionCalls", config.maxReflectionCalls],
474
+ ["historySize", config.historySize],
475
+ ["scoringSetSize", config.scoringSetSize],
476
+ ["fullEvalInterval", config.fullEvalInterval]
477
+ ];
478
+ 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
+ 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
+ }
481
+ function defaultInstanceId(args) {
482
+ const hash = require_evaluation.stableHash(args.datum);
483
+ return hash === "" ? String(args.index) : hash;
484
+ }
485
+ //#endregion
486
+ exports.OproOptimizer = OproOptimizer;
487
+ exports.buildOproPrompt = buildOproPrompt;