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