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.
- package/LICENSE +21 -0
- package/README.md +509 -0
- package/dist/bootstrap-search/index.cjs +308 -0
- package/dist/bootstrap-search/index.d.cts +162 -0
- package/dist/bootstrap-search/index.d.mts +162 -0
- package/dist/bootstrap-search/index.mjs +307 -0
- package/dist/cache-CuSo0NJ8.d.cts +24 -0
- package/dist/cache-CuSo0NJ8.d.mts +24 -0
- package/dist/concurrency-C-cFzWW2.cjs +44 -0
- package/dist/concurrency-D58PWeSk.mjs +39 -0
- package/dist/demos-B0pVQjYC.d.mts +88 -0
- package/dist/demos-B9BJiNKz.cjs +143 -0
- package/dist/demos-BTuzFNsp.d.cts +88 -0
- package/dist/demos-Degx6UmP.mjs +126 -0
- package/dist/evaluation-BV0nSZVx.mjs +521 -0
- package/dist/evaluation-OZOp6TB7.cjs +598 -0
- package/dist/file-cache.cjs +70 -0
- package/dist/file-cache.d.cts +21 -0
- package/dist/file-cache.d.mts +21 -0
- package/dist/file-cache.mjs +69 -0
- package/dist/gepa/index.cjs +1671 -0
- package/dist/gepa/index.d.cts +385 -0
- package/dist/gepa/index.d.mts +385 -0
- package/dist/gepa/index.mjs +1652 -0
- package/dist/index.cjs +266 -0
- package/dist/index.d.cts +221 -0
- package/dist/index.d.mts +221 -0
- package/dist/index.mjs +245 -0
- package/dist/math-COOofUyv.cjs +101 -0
- package/dist/math-DhrDmpFS.mjs +78 -0
- package/dist/mipro/index.cjs +739 -0
- package/dist/mipro/index.d.cts +372 -0
- package/dist/mipro/index.d.mts +372 -0
- package/dist/mipro/index.mjs +736 -0
- package/dist/opro/index.cjs +487 -0
- package/dist/opro/index.d.cts +230 -0
- package/dist/opro/index.d.mts +230 -0
- package/dist/opro/index.mjs +485 -0
- package/dist/optimizer-B7SpRwl7.d.cts +288 -0
- package/dist/optimizer-DqCoth_w.d.mts +288 -0
- package/dist/random-search/index.cjs +321 -0
- package/dist/random-search/index.d.cts +156 -0
- package/dist/random-search/index.d.mts +156 -0
- package/dist/random-search/index.mjs +319 -0
- package/dist/reflection-CQToe-5B.d.cts +283 -0
- package/dist/reflection-Cr_upzU0.d.mts +283 -0
- package/dist/reflection-DRfbk6hu.cjs +249 -0
- package/dist/reflection-mwMhrjs_.mjs +214 -0
- package/dist/rng-BR5MOedA.d.cts +22 -0
- package/dist/rng-BR5MOedA.d.mts +22 -0
- package/dist/rng-DbA_rPIo.cjs +67 -0
- package/dist/rng-Dtc5eZ_W.mjs +62 -0
- package/dist/sampling-CfHt7Gue.mjs +59 -0
- package/dist/sampling-DFo_7RNJ.d.mts +23 -0
- package/dist/sampling-Dars7ctR.cjs +64 -0
- package/dist/sampling-axOwfZf5.d.cts +23 -0
- package/dist/simba/index.cjs +709 -0
- package/dist/simba/index.d.cts +289 -0
- package/dist/simba/index.d.mts +289 -0
- package/dist/simba/index.mjs +700 -0
- package/dist/testing.cjs +155 -0
- package/dist/testing.d.cts +53 -0
- package/dist/testing.d.mts +53 -0
- package/dist/testing.mjs +148 -0
- package/dist/text--v4Ffbus.mjs +21 -0
- package/dist/text-CK_HB3su.cjs +26 -0
- package/dist/types-CWv4IQFF.d.cts +129 -0
- package/dist/types-CWv4IQFF.d.mts +129 -0
- package/package.json +135 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_evaluation = require("../evaluation-OZOp6TB7.cjs");
|
|
3
|
+
const require_demos = require("../demos-B9BJiNKz.cjs");
|
|
4
|
+
const require_rng = require("../rng-DbA_rPIo.cjs");
|
|
5
|
+
//#region src/bootstrap-search/optimize.ts
|
|
6
|
+
const DEFAULT_CANDIDATES = 16;
|
|
7
|
+
const DEFAULT_MAX_DEMOS = 4;
|
|
8
|
+
const DEFAULT_MIN_DEMOS = 1;
|
|
9
|
+
const DEFAULT_MAX_LABELED_DEMOS = 16;
|
|
10
|
+
/**
|
|
11
|
+
* Bootstrapped few-shot search: harvest demonstrations from rollouts the metric
|
|
12
|
+
* already rewarded, and pick the set that scores best.
|
|
13
|
+
*
|
|
14
|
+
* DSPy's `BootstrapFewShotWithRandomSearch`, which is what the literature
|
|
15
|
+
* usually means by "random search" over prompts. It is the only optimizer here
|
|
16
|
+
* that calls no model to write text: every candidate is assembled from outputs
|
|
17
|
+
* the system itself produced, so the search costs rollouts and nothing else.
|
|
18
|
+
* That makes it the right first thing to try — it is cheap, it needs no
|
|
19
|
+
* frontier model, and on tasks where the instruction is already adequate and
|
|
20
|
+
* the format is not, it is often the whole win.
|
|
21
|
+
*
|
|
22
|
+
* The fixed candidates come first and in DSPy's order: zero-shot (seed -3),
|
|
23
|
+
* labels-only when gold outputs exist (seed -2), and one unshuffled harvest at
|
|
24
|
+
* full size (seed -1). Shuffled harvests of random size follow. Keeping
|
|
25
|
+
* zero-shot in the running is not a formality — demonstrations can hurt, and a
|
|
26
|
+
* search that cannot return "no demos" has no baseline to report against.
|
|
27
|
+
*
|
|
28
|
+
* One deviation, deliberate: DSPy bootstraps each predictor separately from the
|
|
29
|
+
* traces of one pass. This library's adapter runs the whole system, so a
|
|
30
|
+
* harvest is a set of end-to-end rollouts and every demo component is given the
|
|
31
|
+
* same block. For per-module demos, use `createPipelineAdapter` with GEPA,
|
|
32
|
+
* which sees each module's own inputs and outputs.
|
|
33
|
+
*/
|
|
34
|
+
var BootstrapSearchOptimizer = class {
|
|
35
|
+
#config;
|
|
36
|
+
constructor(config = {}) {
|
|
37
|
+
this.#config = config;
|
|
38
|
+
}
|
|
39
|
+
optimize(task) {
|
|
40
|
+
return run({
|
|
41
|
+
config: this.#config,
|
|
42
|
+
task
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
async function run(args) {
|
|
47
|
+
const { config, task } = args;
|
|
48
|
+
const { candidates: shuffledHarvests = DEFAULT_CANDIDATES, maxDemos = DEFAULT_MAX_DEMOS, minDemos = DEFAULT_MIN_DEMOS, maxLabeledDemos = DEFAULT_MAX_LABELED_DEMOS, demoMinScore, stopAtScore, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
|
|
49
|
+
const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, demoComponents, renderDemo, goldOutput, maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
|
|
50
|
+
const deadline = require_evaluation.createDeadline({ maxWallClockMs });
|
|
51
|
+
const components = require_evaluation.componentNames(seedCandidate);
|
|
52
|
+
if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
|
|
53
|
+
if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
|
|
54
|
+
if (demoComponents.length === 0) throw new Error("optimize requires at least one demoComponent: this search has nothing to put demonstrations in otherwise");
|
|
55
|
+
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");
|
|
56
|
+
const validationIds = validationSet.map((datum, index) => instanceId({
|
|
57
|
+
datum,
|
|
58
|
+
index
|
|
59
|
+
}));
|
|
60
|
+
const testIds = testSet?.map((datum, index) => instanceId({
|
|
61
|
+
datum,
|
|
62
|
+
index
|
|
63
|
+
})) ?? [];
|
|
64
|
+
const fingerprint = require_evaluation.runFingerprint({
|
|
65
|
+
seedCandidate,
|
|
66
|
+
trainingIds: trainingSet.map((datum, index) => instanceId({
|
|
67
|
+
datum,
|
|
68
|
+
index
|
|
69
|
+
})),
|
|
70
|
+
validationIds,
|
|
71
|
+
seed,
|
|
72
|
+
...cacheNamespace === void 0 ? {} : { cacheNamespace }
|
|
73
|
+
});
|
|
74
|
+
require_evaluation.assertResumable({
|
|
75
|
+
fingerprint,
|
|
76
|
+
...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
|
|
77
|
+
});
|
|
78
|
+
const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
|
|
79
|
+
const budget = require_evaluation.createBudget({
|
|
80
|
+
maxMetricCalls,
|
|
81
|
+
spent: resumeFrom?.metricCalls ?? 0
|
|
82
|
+
});
|
|
83
|
+
const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
|
|
84
|
+
const evaluator = require_evaluation.createEvaluator({
|
|
85
|
+
adapter,
|
|
86
|
+
budget,
|
|
87
|
+
...retry === void 0 ? {} : { retry },
|
|
88
|
+
...cacheNamespace === void 0 ? {} : { cacheNamespace },
|
|
89
|
+
...evaluationCache === void 0 ? {} : { cache: evaluationCache },
|
|
90
|
+
trackOutputs: trackBestOutputs,
|
|
91
|
+
cacheHits: resumeFrom?.cacheHits ?? 0,
|
|
92
|
+
...signal === void 0 ? {} : { signal },
|
|
93
|
+
onEvaluation: (event) => onEvent?.({
|
|
94
|
+
type: "evaluation",
|
|
95
|
+
...event
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
evaluator.restore(resumeFrom?.cache ?? []);
|
|
99
|
+
const evaluated = [...resumeFrom?.candidates ?? []];
|
|
100
|
+
let drawn = resumeFrom?.drawn ?? 0;
|
|
101
|
+
let bootstrapMetricCalls = resumeFrom?.bootstrapMetricCalls ?? 0;
|
|
102
|
+
let stopReason = "candidatesExhausted";
|
|
103
|
+
onEvent?.({
|
|
104
|
+
type: "start",
|
|
105
|
+
components,
|
|
106
|
+
validationSetSize: validationSet.length
|
|
107
|
+
});
|
|
108
|
+
async function sweep(candidate, phase) {
|
|
109
|
+
return evaluator.evaluate({
|
|
110
|
+
candidate,
|
|
111
|
+
batch: validationSet,
|
|
112
|
+
ids: validationIds,
|
|
113
|
+
split: "val",
|
|
114
|
+
phase,
|
|
115
|
+
candidateId: null,
|
|
116
|
+
iteration: evaluated.length
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const seedScore = resumeFrom?.seedScore ?? require_evaluation.requireMeasuredMean({
|
|
120
|
+
batch: await sweep(seedCandidate, "seed"),
|
|
121
|
+
phase: "seed"
|
|
122
|
+
});
|
|
123
|
+
let best = resumeFrom?.best ?? seedCandidate;
|
|
124
|
+
let bestScore = resumeFrom?.bestScore ?? seedScore;
|
|
125
|
+
let bestOutputs;
|
|
126
|
+
function takeSnapshot() {
|
|
127
|
+
const cached = checkpointCache ? evaluationCache?.entries?.() : void 0;
|
|
128
|
+
return {
|
|
129
|
+
version: 1,
|
|
130
|
+
fingerprint,
|
|
131
|
+
candidates: [...evaluated],
|
|
132
|
+
best,
|
|
133
|
+
bestScore,
|
|
134
|
+
seedScore,
|
|
135
|
+
drawn,
|
|
136
|
+
metricCalls: budget.spent(),
|
|
137
|
+
bootstrapMetricCalls,
|
|
138
|
+
cacheHits: evaluator.cacheHits(),
|
|
139
|
+
rngState: rng.state(),
|
|
140
|
+
...cached === void 0 ? {} : { cache: cached }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function checkpoint() {
|
|
144
|
+
if (onCheckpoint === void 0) return;
|
|
145
|
+
await onCheckpoint(takeSnapshot());
|
|
146
|
+
}
|
|
147
|
+
const plan = candidatePlan({
|
|
148
|
+
shuffledHarvests,
|
|
149
|
+
labeled: goldOutput !== void 0
|
|
150
|
+
});
|
|
151
|
+
for (; drawn < plan.length; drawn += 1) {
|
|
152
|
+
if (signal?.aborted) {
|
|
153
|
+
stopReason = "aborted";
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (require_evaluation.costExhausted({
|
|
157
|
+
usage: evaluator.usage(),
|
|
158
|
+
maxCostUsd
|
|
159
|
+
})) {
|
|
160
|
+
stopReason = "costExhausted";
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (deadline.exceeded()) {
|
|
164
|
+
stopReason = "deadlineReached";
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
if (!budget.canAfford(validationSet.length + 1)) {
|
|
168
|
+
stopReason = "budgetExhausted";
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
const source = plan[drawn];
|
|
172
|
+
const block = await buildBlock(source);
|
|
173
|
+
const candidate = withDemos(block);
|
|
174
|
+
let evaluation;
|
|
175
|
+
try {
|
|
176
|
+
evaluation = await sweep(candidate, "validation");
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err instanceof require_evaluation.BudgetExhausted) {
|
|
179
|
+
stopReason = "budgetExhausted";
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (signal?.aborted) {
|
|
183
|
+
stopReason = "aborted";
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
const score = require_evaluation.measuredMean(evaluation);
|
|
189
|
+
if (score === void 0) continue;
|
|
190
|
+
const accepted = score > bestScore;
|
|
191
|
+
evaluated.push({
|
|
192
|
+
candidate,
|
|
193
|
+
source,
|
|
194
|
+
demos: countDemos(block),
|
|
195
|
+
score
|
|
196
|
+
});
|
|
197
|
+
onEvent?.({
|
|
198
|
+
type: "candidate",
|
|
199
|
+
index: evaluated.length - 1,
|
|
200
|
+
source,
|
|
201
|
+
demos: countDemos(block),
|
|
202
|
+
score,
|
|
203
|
+
accepted
|
|
204
|
+
});
|
|
205
|
+
if (accepted) {
|
|
206
|
+
best = candidate;
|
|
207
|
+
bestScore = score;
|
|
208
|
+
bestOutputs = evaluation.outputs;
|
|
209
|
+
}
|
|
210
|
+
await checkpoint();
|
|
211
|
+
if (stopAtScore !== void 0 && score >= stopAtScore) {
|
|
212
|
+
stopReason = "scoreReached";
|
|
213
|
+
drawn += 1;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (signal?.aborted) stopReason = "aborted";
|
|
218
|
+
const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
|
|
219
|
+
candidate: best,
|
|
220
|
+
batch: testSet,
|
|
221
|
+
ids: testIds,
|
|
222
|
+
split: "test",
|
|
223
|
+
phase: "test",
|
|
224
|
+
candidateId: null,
|
|
225
|
+
iteration: evaluated.length,
|
|
226
|
+
charge: false
|
|
227
|
+
}));
|
|
228
|
+
onEvent?.({
|
|
229
|
+
type: "finish",
|
|
230
|
+
reason: stopReason,
|
|
231
|
+
bestScore,
|
|
232
|
+
metricCalls: budget.spent(),
|
|
233
|
+
...testScore === void 0 ? {} : { testScore }
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
bestCandidate: best,
|
|
237
|
+
bestScore,
|
|
238
|
+
usage: evaluator.usage(),
|
|
239
|
+
seedScore,
|
|
240
|
+
candidates: evaluated,
|
|
241
|
+
bootstrapMetricCalls,
|
|
242
|
+
cacheHits: evaluator.cacheHits(),
|
|
243
|
+
metricCalls: budget.spent(),
|
|
244
|
+
snapshot: takeSnapshot(),
|
|
245
|
+
...trackBestOutputs && bestOutputs !== void 0 ? { bestOutputs } : {},
|
|
246
|
+
...testScore === void 0 ? {} : {
|
|
247
|
+
testScore,
|
|
248
|
+
testMetricCalls: testSet?.length ?? 0
|
|
249
|
+
},
|
|
250
|
+
stopReason
|
|
251
|
+
};
|
|
252
|
+
function withDemos(block) {
|
|
253
|
+
const candidate = { ...seedCandidate };
|
|
254
|
+
for (const name of demoComponents) candidate[name] = block;
|
|
255
|
+
return candidate;
|
|
256
|
+
}
|
|
257
|
+
async function buildBlock(source) {
|
|
258
|
+
if (source === "zeroShot") return "";
|
|
259
|
+
if (source === "labeled") return labeledBlock();
|
|
260
|
+
const requested = source === "unshuffled" ? maxDemos : minDemos + rng.nextInt(Math.max(1, maxDemos - minDemos + 1));
|
|
261
|
+
const affordable = Math.min(trainingSet.length, budget.remaining() - validationSet.length);
|
|
262
|
+
if (affordable < 1) return "";
|
|
263
|
+
const harvest = await require_demos.bootstrapDemos({
|
|
264
|
+
adapter,
|
|
265
|
+
candidate: seedCandidate,
|
|
266
|
+
trainingSet,
|
|
267
|
+
...demoMinScore === void 0 ? {} : { minScore: demoMinScore },
|
|
268
|
+
maxDemos: requested,
|
|
269
|
+
maxMetricCalls: affordable,
|
|
270
|
+
...source === "unshuffled" ? {} : { rng },
|
|
271
|
+
...renderDemo === void 0 ? {} : { renderDemo },
|
|
272
|
+
...signal === void 0 ? {} : { signal }
|
|
273
|
+
});
|
|
274
|
+
bootstrapMetricCalls += harvest.metricCalls;
|
|
275
|
+
budget.reserve(harvest.metricCalls);
|
|
276
|
+
return harvest.block;
|
|
277
|
+
}
|
|
278
|
+
function labeledBlock() {
|
|
279
|
+
const labelled = trainingSet.map((datum) => ({
|
|
280
|
+
input: datum,
|
|
281
|
+
output: goldOutput?.(datum)
|
|
282
|
+
})).filter((demo) => demo.output !== void 0).slice(0, maxLabeledDemos);
|
|
283
|
+
return require_demos.formatDemos(labelled, renderDemo === void 0 ? {} : { render: renderDemo });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The order candidates are tried in, following DSPy's special seeds: zero-shot
|
|
288
|
+
* first, then labels-only, then one unshuffled full-size harvest, then the
|
|
289
|
+
* shuffled ones. Cheapest and most reliable first, so a run cut short by its
|
|
290
|
+
* budget still has the baseline it needs to report against.
|
|
291
|
+
*/
|
|
292
|
+
function candidatePlan(args) {
|
|
293
|
+
const { shuffledHarvests, labeled } = args;
|
|
294
|
+
return [
|
|
295
|
+
"zeroShot",
|
|
296
|
+
...labeled ? ["labeled"] : [],
|
|
297
|
+
"unshuffled",
|
|
298
|
+
...Array.from({ length: shuffledHarvests }, () => "bootstrapped")
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
function countDemos(block) {
|
|
302
|
+
return block.split("<demo>").length - 1;
|
|
303
|
+
}
|
|
304
|
+
function defaultInstanceId(args) {
|
|
305
|
+
return String(args.index);
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
exports.BootstrapSearchOptimizer = BootstrapSearchOptimizer;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { n as Candidate, t as Adapter } from "../types-CWv4IQFF.cjs";
|
|
2
|
+
import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.cjs";
|
|
3
|
+
import { r as DemoRenderer } from "../demos-BTuzFNsp.cjs";
|
|
4
|
+
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-B7SpRwl7.cjs";
|
|
5
|
+
//#region src/bootstrap-search/optimize.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Where a candidate's demo block came from. `zeroShot` holds no demos at all,
|
|
8
|
+
* `labeled` is built from gold outputs and costs no rollouts, `unshuffled`
|
|
9
|
+
* takes the training set in order, and `bootstrapped` is one shuffled harvest.
|
|
10
|
+
*/
|
|
11
|
+
type DemoSource = "zeroShot" | "labeled" | "unshuffled" | "bootstrapped";
|
|
12
|
+
interface BootstrapCandidate<K extends string = string> {
|
|
13
|
+
candidate: Candidate<K>;
|
|
14
|
+
source: DemoSource;
|
|
15
|
+
/** Demos in the block, so a win can be read against how much it carried. */
|
|
16
|
+
demos: number;
|
|
17
|
+
score: number;
|
|
18
|
+
}
|
|
19
|
+
interface BootstrapSearchConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Shuffled harvests attempted, beyond the fixed candidates every run tries.
|
|
22
|
+
* Default 16, DSPy's `num_candidate_programs`.
|
|
23
|
+
*/
|
|
24
|
+
candidates?: number;
|
|
25
|
+
/** Most demos a harvested set may hold. Default 4. */
|
|
26
|
+
maxDemos?: number;
|
|
27
|
+
/** Fewest demos a shuffled harvest may ask for. Default 1. */
|
|
28
|
+
minDemos?: number;
|
|
29
|
+
/** Most demos the labels-only candidate may hold. Default 16. */
|
|
30
|
+
maxLabeledDemos?: number;
|
|
31
|
+
/** Score a rollout must reach to be kept as a demo. */
|
|
32
|
+
demoMinScore?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Stop as soon as a candidate reaches this validation score. Unset evaluates
|
|
35
|
+
* every candidate, which is the reliable reading and the expensive one.
|
|
36
|
+
*/
|
|
37
|
+
stopAtScore?: number;
|
|
38
|
+
seed?: number;
|
|
39
|
+
trackBestOutputs?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Include cached instance scores in every checkpoint. Leaving them out keeps
|
|
42
|
+
* snapshots small at the cost of a resumed run re-paying for rollouts it
|
|
43
|
+
* cannot look up. Default true.
|
|
44
|
+
*/
|
|
45
|
+
checkpointCache?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Everything needed to continue a run: which candidates have been evaluated,
|
|
49
|
+
* what they scored, and the budget already spent. Plain JSON — persist it and
|
|
50
|
+
* hand it back as `resumeFrom`.
|
|
51
|
+
*/
|
|
52
|
+
interface BootstrapSearchSnapshot {
|
|
53
|
+
version: 1;
|
|
54
|
+
fingerprint: string;
|
|
55
|
+
candidates: BootstrapCandidate[];
|
|
56
|
+
best: Candidate;
|
|
57
|
+
bestScore: number;
|
|
58
|
+
seedScore: number;
|
|
59
|
+
/** How many candidates have been drawn, including the fixed ones. */
|
|
60
|
+
drawn: number;
|
|
61
|
+
metricCalls: number;
|
|
62
|
+
bootstrapMetricCalls: number;
|
|
63
|
+
cacheHits: number;
|
|
64
|
+
rngState: number;
|
|
65
|
+
cache?: [string, CachedScore][];
|
|
66
|
+
}
|
|
67
|
+
interface BootstrapSearchTask<Datum, Trajectory = unknown, Output = unknown, K extends string = string> extends OptimizerTask<Datum, Trajectory, Output, K> {
|
|
68
|
+
/**
|
|
69
|
+
* The base adapter, and no reflection model: this search writes no text at
|
|
70
|
+
* all, so there is nothing for a model to propose.
|
|
71
|
+
*/
|
|
72
|
+
adapter: Adapter<Datum, Trajectory, Output, NoInfer<K>>;
|
|
73
|
+
/**
|
|
74
|
+
* Components holding few-shot demo blocks. Every one of them receives the
|
|
75
|
+
* same harvest, because a harvest is a pass over the training set and one
|
|
76
|
+
* pass per component would multiply the cost of the search by the number of
|
|
77
|
+
* components for evidence that came from the same rollouts.
|
|
78
|
+
*/
|
|
79
|
+
demoComponents: readonly NoInfer<K>[];
|
|
80
|
+
/** Renders a harvested rollout as demo text. Defaults to JSON. */
|
|
81
|
+
renderDemo?: DemoRenderer<NoInfer<Datum>, NoInfer<Output>>;
|
|
82
|
+
/**
|
|
83
|
+
* The gold output for a training datum, where the caller has labels. Supply
|
|
84
|
+
* it and the run tries a labels-only candidate, which costs no rollouts to
|
|
85
|
+
* build and is the only candidate available at all to a system too weak to
|
|
86
|
+
* bootstrap from.
|
|
87
|
+
*/
|
|
88
|
+
goldOutput?: (datum: NoInfer<Datum>) => NoInfer<Output> | undefined;
|
|
89
|
+
instanceId?: (args: {
|
|
90
|
+
datum: NoInfer<Datum>;
|
|
91
|
+
index: number;
|
|
92
|
+
}) => string;
|
|
93
|
+
/** Pass `false` to disable caching entirely. */
|
|
94
|
+
cache?: EvaluationCache | false;
|
|
95
|
+
onEvent?: (event: BootstrapSearchEvent<NoInfer<K>>) => void;
|
|
96
|
+
/** Called with a resumable snapshot after every candidate is scored. */
|
|
97
|
+
onCheckpoint?: (snapshot: BootstrapSearchSnapshot) => void | Promise<void>;
|
|
98
|
+
/** Snapshot to continue from. */
|
|
99
|
+
resumeFrom?: BootstrapSearchSnapshot;
|
|
100
|
+
}
|
|
101
|
+
type BootstrapSearchStopReason = "budgetExhausted" | "costExhausted" | "deadlineReached" | "scoreReached" | "candidatesExhausted" | "aborted";
|
|
102
|
+
type BootstrapSearchEvent<K extends string = string> = {
|
|
103
|
+
type: "start";
|
|
104
|
+
components: K[];
|
|
105
|
+
validationSetSize: number;
|
|
106
|
+
} | ({
|
|
107
|
+
type: "evaluation";
|
|
108
|
+
} & EvaluationEvent) | {
|
|
109
|
+
type: "candidate";
|
|
110
|
+
index: number;
|
|
111
|
+
source: DemoSource;
|
|
112
|
+
demos: number;
|
|
113
|
+
score: number;
|
|
114
|
+
accepted: boolean;
|
|
115
|
+
} | {
|
|
116
|
+
type: "finish";
|
|
117
|
+
reason: BootstrapSearchStopReason;
|
|
118
|
+
bestScore: number;
|
|
119
|
+
metricCalls: number;
|
|
120
|
+
testScore?: number;
|
|
121
|
+
};
|
|
122
|
+
interface BootstrapSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, BootstrapSearchStopReason, Output> {
|
|
123
|
+
/** The seed's score, so the lift the demos bought is readable directly. */
|
|
124
|
+
seedScore: number;
|
|
125
|
+
/** Every candidate evaluated, in the order it was tried. */
|
|
126
|
+
candidates: BootstrapCandidate<K>[];
|
|
127
|
+
/** Rollouts spent harvesting demos, included in `metricCalls`. */
|
|
128
|
+
bootstrapMetricCalls: number;
|
|
129
|
+
cacheHits: number;
|
|
130
|
+
snapshot: BootstrapSearchSnapshot;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Bootstrapped few-shot search: harvest demonstrations from rollouts the metric
|
|
134
|
+
* already rewarded, and pick the set that scores best.
|
|
135
|
+
*
|
|
136
|
+
* DSPy's `BootstrapFewShotWithRandomSearch`, which is what the literature
|
|
137
|
+
* usually means by "random search" over prompts. It is the only optimizer here
|
|
138
|
+
* that calls no model to write text: every candidate is assembled from outputs
|
|
139
|
+
* the system itself produced, so the search costs rollouts and nothing else.
|
|
140
|
+
* That makes it the right first thing to try — it is cheap, it needs no
|
|
141
|
+
* frontier model, and on tasks where the instruction is already adequate and
|
|
142
|
+
* the format is not, it is often the whole win.
|
|
143
|
+
*
|
|
144
|
+
* The fixed candidates come first and in DSPy's order: zero-shot (seed -3),
|
|
145
|
+
* labels-only when gold outputs exist (seed -2), and one unshuffled harvest at
|
|
146
|
+
* full size (seed -1). Shuffled harvests of random size follow. Keeping
|
|
147
|
+
* zero-shot in the running is not a formality — demonstrations can hurt, and a
|
|
148
|
+
* search that cannot return "no demos" has no baseline to report against.
|
|
149
|
+
*
|
|
150
|
+
* One deviation, deliberate: DSPy bootstraps each predictor separately from the
|
|
151
|
+
* traces of one pass. This library's adapter runs the whole system, so a
|
|
152
|
+
* harvest is a set of end-to-end rollouts and every demo component is given the
|
|
153
|
+
* same block. For per-module demos, use `createPipelineAdapter` with GEPA,
|
|
154
|
+
* which sees each module's own inputs and outputs.
|
|
155
|
+
*/
|
|
156
|
+
declare class BootstrapSearchOptimizer implements Optimizer<BootstrapSearchStopReason> {
|
|
157
|
+
#private;
|
|
158
|
+
constructor(config?: BootstrapSearchConfig);
|
|
159
|
+
optimize<Datum, Trajectory = unknown, Output = unknown, const K extends string = string>(task: BootstrapSearchTask<Datum, Trajectory, Output, K>): Promise<BootstrapSearchResult<K, Output>>;
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type DemoSource };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { n as Candidate, t as Adapter } from "../types-CWv4IQFF.mjs";
|
|
2
|
+
import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.mjs";
|
|
3
|
+
import { r as DemoRenderer } from "../demos-B0pVQjYC.mjs";
|
|
4
|
+
import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-DqCoth_w.mjs";
|
|
5
|
+
//#region src/bootstrap-search/optimize.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Where a candidate's demo block came from. `zeroShot` holds no demos at all,
|
|
8
|
+
* `labeled` is built from gold outputs and costs no rollouts, `unshuffled`
|
|
9
|
+
* takes the training set in order, and `bootstrapped` is one shuffled harvest.
|
|
10
|
+
*/
|
|
11
|
+
type DemoSource = "zeroShot" | "labeled" | "unshuffled" | "bootstrapped";
|
|
12
|
+
interface BootstrapCandidate<K extends string = string> {
|
|
13
|
+
candidate: Candidate<K>;
|
|
14
|
+
source: DemoSource;
|
|
15
|
+
/** Demos in the block, so a win can be read against how much it carried. */
|
|
16
|
+
demos: number;
|
|
17
|
+
score: number;
|
|
18
|
+
}
|
|
19
|
+
interface BootstrapSearchConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Shuffled harvests attempted, beyond the fixed candidates every run tries.
|
|
22
|
+
* Default 16, DSPy's `num_candidate_programs`.
|
|
23
|
+
*/
|
|
24
|
+
candidates?: number;
|
|
25
|
+
/** Most demos a harvested set may hold. Default 4. */
|
|
26
|
+
maxDemos?: number;
|
|
27
|
+
/** Fewest demos a shuffled harvest may ask for. Default 1. */
|
|
28
|
+
minDemos?: number;
|
|
29
|
+
/** Most demos the labels-only candidate may hold. Default 16. */
|
|
30
|
+
maxLabeledDemos?: number;
|
|
31
|
+
/** Score a rollout must reach to be kept as a demo. */
|
|
32
|
+
demoMinScore?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Stop as soon as a candidate reaches this validation score. Unset evaluates
|
|
35
|
+
* every candidate, which is the reliable reading and the expensive one.
|
|
36
|
+
*/
|
|
37
|
+
stopAtScore?: number;
|
|
38
|
+
seed?: number;
|
|
39
|
+
trackBestOutputs?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Include cached instance scores in every checkpoint. Leaving them out keeps
|
|
42
|
+
* snapshots small at the cost of a resumed run re-paying for rollouts it
|
|
43
|
+
* cannot look up. Default true.
|
|
44
|
+
*/
|
|
45
|
+
checkpointCache?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Everything needed to continue a run: which candidates have been evaluated,
|
|
49
|
+
* what they scored, and the budget already spent. Plain JSON — persist it and
|
|
50
|
+
* hand it back as `resumeFrom`.
|
|
51
|
+
*/
|
|
52
|
+
interface BootstrapSearchSnapshot {
|
|
53
|
+
version: 1;
|
|
54
|
+
fingerprint: string;
|
|
55
|
+
candidates: BootstrapCandidate[];
|
|
56
|
+
best: Candidate;
|
|
57
|
+
bestScore: number;
|
|
58
|
+
seedScore: number;
|
|
59
|
+
/** How many candidates have been drawn, including the fixed ones. */
|
|
60
|
+
drawn: number;
|
|
61
|
+
metricCalls: number;
|
|
62
|
+
bootstrapMetricCalls: number;
|
|
63
|
+
cacheHits: number;
|
|
64
|
+
rngState: number;
|
|
65
|
+
cache?: [string, CachedScore][];
|
|
66
|
+
}
|
|
67
|
+
interface BootstrapSearchTask<Datum, Trajectory = unknown, Output = unknown, K extends string = string> extends OptimizerTask<Datum, Trajectory, Output, K> {
|
|
68
|
+
/**
|
|
69
|
+
* The base adapter, and no reflection model: this search writes no text at
|
|
70
|
+
* all, so there is nothing for a model to propose.
|
|
71
|
+
*/
|
|
72
|
+
adapter: Adapter<Datum, Trajectory, Output, NoInfer<K>>;
|
|
73
|
+
/**
|
|
74
|
+
* Components holding few-shot demo blocks. Every one of them receives the
|
|
75
|
+
* same harvest, because a harvest is a pass over the training set and one
|
|
76
|
+
* pass per component would multiply the cost of the search by the number of
|
|
77
|
+
* components for evidence that came from the same rollouts.
|
|
78
|
+
*/
|
|
79
|
+
demoComponents: readonly NoInfer<K>[];
|
|
80
|
+
/** Renders a harvested rollout as demo text. Defaults to JSON. */
|
|
81
|
+
renderDemo?: DemoRenderer<NoInfer<Datum>, NoInfer<Output>>;
|
|
82
|
+
/**
|
|
83
|
+
* The gold output for a training datum, where the caller has labels. Supply
|
|
84
|
+
* it and the run tries a labels-only candidate, which costs no rollouts to
|
|
85
|
+
* build and is the only candidate available at all to a system too weak to
|
|
86
|
+
* bootstrap from.
|
|
87
|
+
*/
|
|
88
|
+
goldOutput?: (datum: NoInfer<Datum>) => NoInfer<Output> | undefined;
|
|
89
|
+
instanceId?: (args: {
|
|
90
|
+
datum: NoInfer<Datum>;
|
|
91
|
+
index: number;
|
|
92
|
+
}) => string;
|
|
93
|
+
/** Pass `false` to disable caching entirely. */
|
|
94
|
+
cache?: EvaluationCache | false;
|
|
95
|
+
onEvent?: (event: BootstrapSearchEvent<NoInfer<K>>) => void;
|
|
96
|
+
/** Called with a resumable snapshot after every candidate is scored. */
|
|
97
|
+
onCheckpoint?: (snapshot: BootstrapSearchSnapshot) => void | Promise<void>;
|
|
98
|
+
/** Snapshot to continue from. */
|
|
99
|
+
resumeFrom?: BootstrapSearchSnapshot;
|
|
100
|
+
}
|
|
101
|
+
type BootstrapSearchStopReason = "budgetExhausted" | "costExhausted" | "deadlineReached" | "scoreReached" | "candidatesExhausted" | "aborted";
|
|
102
|
+
type BootstrapSearchEvent<K extends string = string> = {
|
|
103
|
+
type: "start";
|
|
104
|
+
components: K[];
|
|
105
|
+
validationSetSize: number;
|
|
106
|
+
} | ({
|
|
107
|
+
type: "evaluation";
|
|
108
|
+
} & EvaluationEvent) | {
|
|
109
|
+
type: "candidate";
|
|
110
|
+
index: number;
|
|
111
|
+
source: DemoSource;
|
|
112
|
+
demos: number;
|
|
113
|
+
score: number;
|
|
114
|
+
accepted: boolean;
|
|
115
|
+
} | {
|
|
116
|
+
type: "finish";
|
|
117
|
+
reason: BootstrapSearchStopReason;
|
|
118
|
+
bestScore: number;
|
|
119
|
+
metricCalls: number;
|
|
120
|
+
testScore?: number;
|
|
121
|
+
};
|
|
122
|
+
interface BootstrapSearchResult<K extends string = string, Output = unknown> extends OptimizerResult<K, BootstrapSearchStopReason, Output> {
|
|
123
|
+
/** The seed's score, so the lift the demos bought is readable directly. */
|
|
124
|
+
seedScore: number;
|
|
125
|
+
/** Every candidate evaluated, in the order it was tried. */
|
|
126
|
+
candidates: BootstrapCandidate<K>[];
|
|
127
|
+
/** Rollouts spent harvesting demos, included in `metricCalls`. */
|
|
128
|
+
bootstrapMetricCalls: number;
|
|
129
|
+
cacheHits: number;
|
|
130
|
+
snapshot: BootstrapSearchSnapshot;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Bootstrapped few-shot search: harvest demonstrations from rollouts the metric
|
|
134
|
+
* already rewarded, and pick the set that scores best.
|
|
135
|
+
*
|
|
136
|
+
* DSPy's `BootstrapFewShotWithRandomSearch`, which is what the literature
|
|
137
|
+
* usually means by "random search" over prompts. It is the only optimizer here
|
|
138
|
+
* that calls no model to write text: every candidate is assembled from outputs
|
|
139
|
+
* the system itself produced, so the search costs rollouts and nothing else.
|
|
140
|
+
* That makes it the right first thing to try — it is cheap, it needs no
|
|
141
|
+
* frontier model, and on tasks where the instruction is already adequate and
|
|
142
|
+
* the format is not, it is often the whole win.
|
|
143
|
+
*
|
|
144
|
+
* The fixed candidates come first and in DSPy's order: zero-shot (seed -3),
|
|
145
|
+
* labels-only when gold outputs exist (seed -2), and one unshuffled harvest at
|
|
146
|
+
* full size (seed -1). Shuffled harvests of random size follow. Keeping
|
|
147
|
+
* zero-shot in the running is not a formality — demonstrations can hurt, and a
|
|
148
|
+
* search that cannot return "no demos" has no baseline to report against.
|
|
149
|
+
*
|
|
150
|
+
* One deviation, deliberate: DSPy bootstraps each predictor separately from the
|
|
151
|
+
* traces of one pass. This library's adapter runs the whole system, so a
|
|
152
|
+
* harvest is a set of end-to-end rollouts and every demo component is given the
|
|
153
|
+
* same block. For per-module demos, use `createPipelineAdapter` with GEPA,
|
|
154
|
+
* which sees each module's own inputs and outputs.
|
|
155
|
+
*/
|
|
156
|
+
declare class BootstrapSearchOptimizer implements Optimizer<BootstrapSearchStopReason> {
|
|
157
|
+
#private;
|
|
158
|
+
constructor(config?: BootstrapSearchConfig);
|
|
159
|
+
optimize<Datum, Trajectory = unknown, Output = unknown, const K extends string = string>(task: BootstrapSearchTask<Datum, Trajectory, Output, K>): Promise<BootstrapSearchResult<K, Output>>;
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { type BootstrapCandidate, type BootstrapSearchConfig, type BootstrapSearchEvent, BootstrapSearchOptimizer, type BootstrapSearchResult, type BootstrapSearchSnapshot, type BootstrapSearchStopReason, type BootstrapSearchTask, type DemoSource };
|