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,319 @@
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
+ //#region src/random-search/optimize.ts
5
+ const DEFAULT_VARIANTS = 4;
6
+ /**
7
+ * The ablation baseline: propose blind, evaluate in full, keep what wins.
8
+ *
9
+ * It exists to be beaten. Reflective search costs a frontier-model call per
10
+ * proposal on top of its rollouts, and running the same budget through a
11
+ * search that cannot read feedback at all puts a floor under what that call
12
+ * has to buy. A GEPA run that does not clear this one is paying for machinery
13
+ * it is not using.
14
+ *
15
+ * What the gap measures is the whole of that machinery, not reflection alone:
16
+ * this drops feedback, Pareto parent selection, and minibatch screening
17
+ * together, and GEPA's own ablations put candidate selection at several points
18
+ * by itself. For reflection in isolation, keep GEPA and hand it a
19
+ * `reflection.buildPrompt` that withholds the evidence — same frontier, same
20
+ * screening, one variable.
21
+ *
22
+ * Not DSPy's `BootstrapFewShotWithRandomSearch`, which the literature usually
23
+ * means by "random search" and which searches bootstrapped demo sets; nor
24
+ * random search in the Bergstra–Bengio sense, since proposals are paraphrases
25
+ * of the incumbent rather than independent draws. No reference implements this
26
+ * baseline — it is original here, and the GEPA paper has no equivalent.
27
+ */
28
+ var RandomSearchOptimizer = class {
29
+ #config;
30
+ constructor(config = {}) {
31
+ assertConfig(config);
32
+ this.#config = config;
33
+ }
34
+ async optimize(task) {
35
+ return runRandomSearch({
36
+ config: this.#config,
37
+ task
38
+ });
39
+ }
40
+ };
41
+ /**
42
+ * Adapted from the GEPA reflection prompt with every source of evidence
43
+ * removed. What is left is the ablation: the same model, the same component,
44
+ * the same output format, and nothing to reason from.
45
+ */
46
+ function buildParaphrasePrompt(args) {
47
+ const { componentName, currentText, attempt } = args;
48
+ return [
49
+ `Here is the current instruction for the "${componentName}" component of a larger system:`,
50
+ "",
51
+ "<current_instruction>",
52
+ currentText,
53
+ "</current_instruction>",
54
+ "",
55
+ "Write a different instruction for this component.",
56
+ "You have no information about how the current one has performed, so do not guess at its weaknesses — vary it instead.",
57
+ `Make variation ${attempt + 1} distinct from the others: change the wording, the level of detail, or the strategy it describes.`,
58
+ "",
59
+ "Return only the new instruction, inside a ``` block."
60
+ ].join("\n");
61
+ }
62
+ async function runRandomSearch(args) {
63
+ const { config, task } = args;
64
+ const { variants = DEFAULT_VARIANTS, concurrency = 1, maxRounds = Number.POSITIVE_INFINITY, buildPrompt = buildParaphrasePrompt, trackBestOutputs = false, checkpointCache = true } = config;
65
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
66
+ const deadline = createDeadline({ maxWallClockMs });
67
+ const components = componentNames(seedCandidate);
68
+ if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
69
+ if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
70
+ if (components.length === 0) throw new Error("optimize requires a seed candidate with at least one component");
71
+ 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");
72
+ const validationIds = validationSet.map((datum, index) => instanceId({
73
+ datum,
74
+ index
75
+ }));
76
+ const testIds = testSet?.map((datum, index) => instanceId({
77
+ datum,
78
+ index
79
+ })) ?? [];
80
+ const fingerprint = runFingerprint({
81
+ seedCandidate,
82
+ trainingIds: trainingSet.map((datum, index) => instanceId({
83
+ datum,
84
+ index
85
+ })),
86
+ validationIds,
87
+ ...cacheNamespace === void 0 ? {} : { cacheNamespace }
88
+ });
89
+ assertResumable({
90
+ fingerprint,
91
+ ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
92
+ });
93
+ const budget = createBudget({
94
+ maxMetricCalls,
95
+ spent: resumeFrom?.metricCalls ?? 0
96
+ });
97
+ const evaluationCache = cache === false ? void 0 : cache ?? createMemoryCache();
98
+ const evaluator = createEvaluator({
99
+ adapter,
100
+ budget,
101
+ ...retry === void 0 ? {} : { retry },
102
+ ...cacheNamespace === void 0 ? {} : { cacheNamespace },
103
+ ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
104
+ trackOutputs: trackBestOutputs,
105
+ cacheHits: resumeFrom?.cacheHits ?? 0,
106
+ ...signal === void 0 ? {} : { signal },
107
+ onEvaluation: (event) => onEvent?.({
108
+ type: "evaluation",
109
+ ...event
110
+ })
111
+ });
112
+ evaluator.restore(resumeFrom?.cache ?? []);
113
+ let round = resumeFrom?.round ?? 0;
114
+ let variantsEvaluated = resumeFrom?.variantsEvaluated ?? 0;
115
+ let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
116
+ let stopReason = "maxRounds";
117
+ /**
118
+ * Consecutive rounds that neither spent a rollout nor improved on the
119
+ * incumbent. A proposer stuck on texts that are already cached costs
120
+ * nothing, so neither the metric budget nor the cost ceiling can end the
121
+ * run — without this the loop spins forever, burning reflection calls that
122
+ * no budget here bounds.
123
+ */
124
+ let stalledRounds = 0;
125
+ onEvent?.({
126
+ type: "start",
127
+ components,
128
+ validationSetSize: validationSet.length
129
+ });
130
+ async function sweep(args) {
131
+ return evaluator.evaluate({
132
+ candidate: args.candidate,
133
+ batch: validationSet,
134
+ ids: validationIds,
135
+ split: "val",
136
+ phase: args.phase,
137
+ candidateId: null,
138
+ iteration: round
139
+ });
140
+ }
141
+ function takeSnapshot() {
142
+ const cached = checkpointCache ? evaluationCache?.entries?.() : void 0;
143
+ return {
144
+ version: 1,
145
+ fingerprint,
146
+ best,
147
+ bestScore,
148
+ seedScore,
149
+ round,
150
+ variantsEvaluated,
151
+ reflectionCalls,
152
+ metricCalls: budget.spent(),
153
+ cacheHits: evaluator.cacheHits(),
154
+ ...cached === void 0 ? {} : { cache: cached }
155
+ };
156
+ }
157
+ async function checkpoint() {
158
+ if (onCheckpoint === void 0) return;
159
+ await onCheckpoint(takeSnapshot());
160
+ }
161
+ const seedEvaluation = resumeFrom === void 0 ? await sweep({
162
+ candidate: seedCandidate,
163
+ phase: "seed"
164
+ }) : void 0;
165
+ const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : requireMeasuredMean({
166
+ batch: seedEvaluation,
167
+ phase: "seed"
168
+ });
169
+ let best = resumeFrom?.best ?? seedCandidate;
170
+ let bestScore = resumeFrom?.bestScore ?? seedScore;
171
+ /** Absent on a resumed run until a variant wins: outputs are not checkpointed. */
172
+ let bestOutputs = seedEvaluation?.outputs;
173
+ await checkpoint();
174
+ while (round < maxRounds) {
175
+ if (signal?.aborted) {
176
+ stopReason = "aborted";
177
+ break;
178
+ }
179
+ if (costExhausted({
180
+ usage: evaluator.usage(),
181
+ maxCostUsd
182
+ })) {
183
+ stopReason = "costExhausted";
184
+ break;
185
+ }
186
+ if (deadline.exceeded()) {
187
+ stopReason = "deadlineReached";
188
+ break;
189
+ }
190
+ if (!budget.canAfford(variants * validationSet.length)) {
191
+ stopReason = "budgetExhausted";
192
+ break;
193
+ }
194
+ const component = components[round % components.length];
195
+ onEvent?.({
196
+ type: "roundStart",
197
+ round,
198
+ component
199
+ });
200
+ const spentBefore = budget.spent();
201
+ const scoreBefore = bestScore;
202
+ const currentText = best[component];
203
+ const drawn = await mapWithConcurrency({
204
+ items: Array.from({ length: variants }, (_, attempt) => attempt),
205
+ limit: concurrency,
206
+ signal,
207
+ task: async (attempt) => {
208
+ reflectionCalls += 1;
209
+ const response = await reflect({
210
+ prompt: buildPrompt({
211
+ componentName: component,
212
+ currentText,
213
+ attempt
214
+ }),
215
+ ...signal === void 0 ? {} : { signal }
216
+ });
217
+ return parseProposedText(response);
218
+ }
219
+ });
220
+ const unique = [...new Set(drawn)].filter((text) => text.length > 0 && text !== currentText);
221
+ let roundStop;
222
+ for (const text of unique) {
223
+ const candidate = {
224
+ ...best,
225
+ [component]: text
226
+ };
227
+ let evaluation;
228
+ try {
229
+ evaluation = await sweep({
230
+ candidate,
231
+ phase: "validation"
232
+ });
233
+ } catch (err) {
234
+ if (err instanceof BudgetExhausted) {
235
+ roundStop = "budgetExhausted";
236
+ break;
237
+ }
238
+ if (signal?.aborted) {
239
+ roundStop = "aborted";
240
+ break;
241
+ }
242
+ throw err;
243
+ }
244
+ variantsEvaluated += 1;
245
+ const score = measuredMean(evaluation);
246
+ if (score !== void 0 && score > bestScore) {
247
+ onEvent?.({
248
+ type: "candidateAccepted",
249
+ round,
250
+ component,
251
+ score,
252
+ previousScore: bestScore
253
+ });
254
+ best = candidate;
255
+ bestScore = score;
256
+ bestOutputs = evaluation.outputs;
257
+ }
258
+ }
259
+ round += 1;
260
+ stalledRounds = budget.spent() === spentBefore && bestScore === scoreBefore ? stalledRounds + 1 : 0;
261
+ await checkpoint();
262
+ if (stalledRounds >= components.length) {
263
+ stopReason = "proposerStalled";
264
+ break;
265
+ }
266
+ if (roundStop !== void 0) {
267
+ stopReason = roundStop;
268
+ break;
269
+ }
270
+ }
271
+ if (signal?.aborted) stopReason = "aborted";
272
+ const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
273
+ candidate: best,
274
+ batch: testSet,
275
+ ids: testIds,
276
+ split: "test",
277
+ phase: "test",
278
+ candidateId: null,
279
+ iteration: round,
280
+ charge: false
281
+ }));
282
+ onEvent?.({
283
+ type: "finish",
284
+ reason: stopReason,
285
+ bestScore,
286
+ metricCalls: budget.spent(),
287
+ ...testScore === void 0 ? {} : { testScore }
288
+ });
289
+ return {
290
+ bestCandidate: best,
291
+ bestScore,
292
+ usage: evaluator.usage(),
293
+ seedScore,
294
+ snapshot: takeSnapshot(),
295
+ ...trackBestOutputs ? { bestOutputs } : {},
296
+ ...testScore === void 0 ? {} : {
297
+ testScore,
298
+ testMetricCalls: evaluator.unchargedCalls()
299
+ },
300
+ rounds: round,
301
+ variantsEvaluated,
302
+ metricCalls: budget.spent(),
303
+ reflectionCalls,
304
+ cacheHits: evaluator.cacheHits(),
305
+ stopReason
306
+ };
307
+ }
308
+ function assertConfig(config) {
309
+ const { variants = DEFAULT_VARIANTS, concurrency = 1, maxRounds } = config;
310
+ if (!Number.isInteger(variants) || variants < 1) throw new Error(`variants must be a positive integer, received ${variants}`);
311
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
312
+ if (maxRounds !== void 0 && (!Number.isInteger(maxRounds) || maxRounds < 1)) throw new Error(`maxRounds must be a positive integer, received ${maxRounds}`);
313
+ }
314
+ function defaultInstanceId(args) {
315
+ const hash = stableHash(args.datum);
316
+ return hash === "" ? String(args.index) : hash;
317
+ }
318
+ //#endregion
319
+ export { RandomSearchOptimizer, buildParaphrasePrompt };
@@ -0,0 +1,283 @@
1
+ import { i as EvaluationBatch, n as Candidate, o as EvaluationPhase, s as EvaluationSplit, t as Adapter, u as TextModel } from "./types-CWv4IQFF.cjs";
2
+ import { t as CachedScore } from "./cache-CuSo0NJ8.cjs";
3
+ import { t as Rng } from "./rng-BR5MOedA.cjs";
4
+ //#region src/gepa/types.d.ts
5
+ /**
6
+ * One piece of evidence reflection reads: what went in, what came out, and how
7
+ * it scored. `Evidence` is the adapter's own slot — trace steps, retrieved
8
+ * documents, errors — typed rather than smuggled in through an index signature.
9
+ */
10
+ interface ReflectiveRecord<Evidence = unknown> {
11
+ inputs: unknown;
12
+ generatedOutputs: unknown;
13
+ feedback: string;
14
+ score?: number;
15
+ evidence?: Evidence;
16
+ }
17
+ /**
18
+ * Component name -> records shown to the reflection model for that component.
19
+ *
20
+ * Partial, not total: an adapter only fills the components it was asked to
21
+ * update, so a component with nothing to diagnose is simply absent.
22
+ */
23
+ type ReflectiveDataset<K extends string = string> = Partial<Record<K, ReflectiveRecord[]>>;
24
+ interface MakeReflectiveDatasetArgs<Datum, Trajectory, Output, K extends string = string> {
25
+ candidate: Candidate<K>;
26
+ batch: readonly Datum[];
27
+ evaluation: EvaluationBatch<Trajectory, Output>;
28
+ componentsToUpdate: readonly K[];
29
+ }
30
+ /** New text for the subset of components a proposal actually changed. */
31
+ type ComponentPatch<K extends string = string> = Partial<Record<K, string>>;
32
+ /**
33
+ * A component text that was proposed and lost on the minibatch. Showing these
34
+ * back to the reflection model is what stops a run from re-deriving the same
35
+ * dead end: only exact duplicates are filtered structurally, so without this
36
+ * the model can spend the whole budget circling one bad idea.
37
+ */
38
+ interface RejectedProposal {
39
+ text: string;
40
+ parentScore: number;
41
+ childScore: number;
42
+ }
43
+ interface ProposeArgs<K extends string = string> {
44
+ candidate: Candidate<K>;
45
+ reflectiveDataset: ReflectiveDataset<K>;
46
+ componentsToUpdate: readonly K[];
47
+ /** Component name -> texts already tried and rejected for it. */
48
+ rejectedProposals?: Partial<Record<K, RejectedProposal[]>>;
49
+ /**
50
+ * Which proposal this is in the run, counting from 0. Assigned when the
51
+ * iteration is planned rather than when it executes, so concurrent
52
+ * proposals get stable, distinct values instead of racing for a counter.
53
+ * The default proposer rotates its strategies on it.
54
+ */
55
+ attempt?: number;
56
+ reflect: TextModel;
57
+ signal?: AbortSignal;
58
+ }
59
+ /**
60
+ * An adapter GEPA can reflect against: evaluation, plus the traces reflection
61
+ * reads. `makeReflectiveDataset` is what turns a scored batch into the
62
+ * per-component evidence a reflection call is written from.
63
+ */
64
+ interface GepaAdapter<Datum, Trajectory = unknown, Output = unknown, K extends string = string> extends Adapter<Datum, Trajectory, Output, K> {
65
+ makeReflectiveDataset(args: MakeReflectiveDatasetArgs<Datum, Trajectory, Output, K>): Promise<ReflectiveDataset<K>> | ReflectiveDataset<K>;
66
+ proposeNewTexts?(args: ProposeArgs<K>): Promise<ComponentPatch<K>> | ComponentPatch<K>;
67
+ }
68
+ type CandidateSource = "seed" | "mutation" | "merge";
69
+ interface CandidateRecord<K extends string = string> {
70
+ id: number;
71
+ candidate: Candidate<K>;
72
+ parentIds: number[];
73
+ /**
74
+ * One entry per validation instance. `undefined` marks an instance the
75
+ * evaluation policy did not select for this candidate — unknown, not zero.
76
+ */
77
+ instanceScores: (number | undefined)[];
78
+ /** Mean over the instances that were scored. */
79
+ aggregateScore: number;
80
+ /** Mean of each objective over the evaluated validation instances. */
81
+ objectiveScores?: Record<string, number>;
82
+ source: CandidateSource;
83
+ updatedComponents: K[];
84
+ iteration: number;
85
+ /**
86
+ * Round-robin position this candidate resumes from the next time it is
87
+ * chosen as a parent. Per-candidate rather than global: a lineage that is
88
+ * only reselected every fifth iteration must still walk its own components
89
+ * one at a time instead of being pinned to whichever one the global counter
90
+ * happens to land on.
91
+ */
92
+ componentCursor: number;
93
+ }
94
+ /** What a Pareto frontier is taken over. */
95
+ type ParetoFrontier = "instance" | "objective" | "hybrid";
96
+ /** Read-only view handed to candidate selectors. */
97
+ interface SelectionState {
98
+ scoreMatrix: readonly (readonly (number | undefined)[])[];
99
+ aggregateScores: readonly number[];
100
+ /** Per-candidate objective means, absent when the adapter reports none. */
101
+ objectiveScores?: readonly (Readonly<Record<string, number>> | undefined)[];
102
+ }
103
+ type CandidateSelector = (args: {
104
+ state: SelectionState;
105
+ rng: Rng;
106
+ }) => number;
107
+ type ComponentSelector<K extends string = string> = (args: {
108
+ candidate: Candidate<K>;
109
+ /** This candidate's own round-robin position, advanced after each selection. */
110
+ cursor: number;
111
+ iteration: number;
112
+ rng: Rng;
113
+ }) => K[];
114
+ type AcceptancePolicy = (args: {
115
+ parentScores: readonly number[];
116
+ childScores: readonly number[];
117
+ }) => boolean;
118
+ /**
119
+ * Which validation instances a candidate is scored on, and how the best
120
+ * candidate is read back out of possibly partial coverage.
121
+ *
122
+ * A full sweep per accepted candidate is the published behaviour and the
123
+ * default. Swapping in a partial policy trades frontier fidelity for rollouts:
124
+ * candidates scored on different instances are no longer strictly comparable,
125
+ * which is why picking the best is the policy's job too.
126
+ */
127
+ interface ValEvaluationPolicy<Datum = unknown, K extends string = string> {
128
+ selectInstances(args: {
129
+ validationSet: readonly Datum[];
130
+ candidate: Candidate<K>;
131
+ records: readonly CandidateRecord<K>[];
132
+ iteration: number;
133
+ rng: Rng;
134
+ }): number[];
135
+ bestCandidate(records: readonly CandidateRecord<K>[]): number;
136
+ }
137
+ type GepaStopReason = "budgetExhausted" | "costExhausted" | "deadlineReached" | "reflectionBudgetExhausted" | "aborted" | "maxIterations";
138
+ type GepaEvent<K extends string = string> = {
139
+ type: "start";
140
+ components: K[];
141
+ validationSetSize: number;
142
+ } | {
143
+ type: "iterationStart";
144
+ iteration: number;
145
+ parentIds: number[];
146
+ } | {
147
+ type: "evaluation";
148
+ iteration: number;
149
+ phase: EvaluationPhase;
150
+ split: EvaluationSplit;
151
+ candidateId: number | null;
152
+ metricCalls: number;
153
+ cacheHits: number;
154
+ meanScore: number;
155
+ } | {
156
+ type: "proposal";
157
+ iteration: number;
158
+ parentId: number;
159
+ componentsToUpdate: K[];
160
+ changed: boolean;
161
+ } | {
162
+ type: "candidateAccepted";
163
+ iteration: number;
164
+ candidateId: number;
165
+ parentIds: number[];
166
+ aggregateScore: number;
167
+ source: CandidateSource;
168
+ } | {
169
+ type: "candidateRejected";
170
+ iteration: number;
171
+ parentId: number;
172
+ parentScore: number;
173
+ childScore: number;
174
+ source: CandidateSource;
175
+ /**
176
+ * "worse" lost to its parent on the minibatch. "notSelected" beat its
177
+ * parent but lost to a stronger proposal from the same iteration — it is
178
+ * not a dead end, and is never fed back to reflection as one.
179
+ */
180
+ reason: "worse" | "notSelected";
181
+ } | {
182
+ type: "error";
183
+ iteration: number;
184
+ err: unknown;
185
+ } | {
186
+ type: "finish";
187
+ reason: GepaStopReason;
188
+ bestCandidateId: number;
189
+ metricCalls: number;
190
+ /** The winner's held-out score, when a testSet was given. */
191
+ testScore?: number;
192
+ };
193
+ /**
194
+ * Everything needed to continue a run: the candidate pool with its scores, the
195
+ * budget already spent, the position of the random stream, and the bookkeeping
196
+ * that stops merges and proposals from being relitigated. Plain JSON — write it
197
+ * wherever you like and hand it back as `resumeFrom`.
198
+ *
199
+ * Deliberately not generic over component names. It leaves the process and
200
+ * comes back through `JSON.parse` with plain string keys, so the narrowing back
201
+ * to a run's own components happens once inside the engine, guarded by the
202
+ * fingerprint, rather than being a type the caller has to reconstruct.
203
+ *
204
+ * A resumed run follows the same trajectory an uninterrupted one would, as long
205
+ * as the batch sampler reports its state and the evaluation cache is either
206
+ * checkpointed or disabled — a cache that is neither leaves the resumed run
207
+ * paying again for rollouts the first one had already bought.
208
+ */
209
+ interface GepaSnapshot {
210
+ version: 1;
211
+ /**
212
+ * Identifies the run this checkpoint came from — seed candidate, instance
213
+ * ids and seed. Resuming against a different setup is refused rather than
214
+ * silently scoring old candidates against new data.
215
+ */
216
+ fingerprint: string;
217
+ records: CandidateRecord[];
218
+ iteration: number;
219
+ metricCalls: number;
220
+ reflectionCalls: number;
221
+ cacheHits: number;
222
+ rngState: number;
223
+ /** Whatever the batch sampler reports from `state()`, when it has one. */
224
+ sampler?: unknown;
225
+ rejectedProposals: Record<string, RejectedProposal[]>;
226
+ merge: {
227
+ attempts: string[];
228
+ descriptions: string[];
229
+ due: number;
230
+ tested: number;
231
+ lastIterationAccepted: boolean;
232
+ };
233
+ /** Cached instance scores, when the cache can enumerate them. */
234
+ cache?: [string, CachedScore][];
235
+ }
236
+ //#endregion
237
+ //#region src/gepa/reflection.d.ts
238
+ interface ReflectionPromptArgs {
239
+ componentName: string;
240
+ currentText: string;
241
+ records: readonly ReflectiveRecord[];
242
+ /** Texts already tried for this component that lost to their parent. */
243
+ rejected?: readonly RejectedProposal[];
244
+ }
245
+ type ReflectionPromptBuilder = (args: ReflectionPromptArgs) => string;
246
+ /**
247
+ * Adapted from the reflection prompt in the GEPA paper (Agrawal et al., 2025).
248
+ * The instruction to mine domain facts out of the traces matters as much as the
249
+ * instruction to fix failures — most of the lift comes from the model writing
250
+ * down knowledge the traces revealed.
251
+ */
252
+ declare function buildReflectionPrompt(args: ReflectionPromptArgs): string;
253
+ /**
254
+ * Cut rather than add. Reflective evolution only ever appends — every
255
+ * iteration diagnoses a failure and writes a rule preventing it — so an
256
+ * instruction grows monotonically until it is mostly edge cases that no longer
257
+ * fire. Nothing else in the loop ever removes one.
258
+ */
259
+ declare function buildSimplifyPrompt(args: ReflectionPromptArgs): string;
260
+ /**
261
+ * Replace a rule that fits the instances it was written from with the
262
+ * principle behind it. Feedback is drawn from minibatches, so a rule written
263
+ * to fix three examples routinely encodes those three examples.
264
+ */
265
+ declare function buildGeneralizePrompt(args: ReflectionPromptArgs): string;
266
+ /**
267
+ * Start from the evidence rather than from the incumbent. Every other strategy
268
+ * edits the current text, which anchors each proposal to whatever the search
269
+ * happened to reach first; this one is the only escape from a bad opening.
270
+ */
271
+ declare function buildRewritePrompt(args: ReflectionPromptArgs): string;
272
+ /**
273
+ * A rotation covering the four directions a proposal can move in: fix what is
274
+ * broken, cut what is dead, widen what is too narrow, and start over.
275
+ *
276
+ * Drawing a proposal k times from one template samples one direction k times.
277
+ * Rotating costs nothing extra — same call count, same rollouts — and is the
278
+ * cheapest diversity available. Opt in via `reflection.strategies`; the
279
+ * default stays the published single prompt.
280
+ */
281
+ declare function diverseReflectionStrategies(): ReflectionPromptBuilder[];
282
+ //#endregion
283
+ export { RejectedProposal as C, ReflectiveRecord as S, ValEvaluationPolicy as T, GepaStopReason as _, buildRewritePrompt as a, ProposeArgs as b, AcceptancePolicy as c, CandidateSource as d, ComponentPatch as f, GepaSnapshot as g, GepaEvent as h, buildReflectionPrompt as i, CandidateRecord as l, GepaAdapter as m, ReflectionPromptBuilder as n, buildSimplifyPrompt as o, ComponentSelector as p, buildGeneralizePrompt as r, diverseReflectionStrategies as s, ReflectionPromptArgs as t, CandidateSelector as u, MakeReflectiveDatasetArgs as v, SelectionState as w, ReflectiveDataset as x, ParetoFrontier as y };