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
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
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_math = require("./math-COOofUyv.cjs");
|
|
5
|
+
const require_demos = require("./demos-B9BJiNKz.cjs");
|
|
6
|
+
const require_text = require("./text-CK_HB3su.cjs");
|
|
7
|
+
//#region src/compare.ts
|
|
8
|
+
const EXACT_LIMIT = 16;
|
|
9
|
+
/**
|
|
10
|
+
* Run several optimizers over the same seeds and report which one actually won.
|
|
11
|
+
*
|
|
12
|
+
* Two things make this worth a helper rather than a for-loop. The first is that
|
|
13
|
+
* it ranks on `testScore` where a run reports one: the validation score is the
|
|
14
|
+
* number the search selected against for its whole run, so an entrant that
|
|
15
|
+
* overfits looks strongest on exactly the number it fitted. The second is that
|
|
16
|
+
* a difference in means over a handful of seeds is usually noise, and the
|
|
17
|
+
* paired sign-flip p-value against the winner is what says whether it is.
|
|
18
|
+
*
|
|
19
|
+
* Entrants are functions of a seed rather than optimizer instances, because the
|
|
20
|
+
* seed is constructor config and every optimizer here is deterministic given
|
|
21
|
+
* one — comparing two entrants at a single seed compares two anecdotes. It also
|
|
22
|
+
* leaves the task where the caller builds it, which is the only place the
|
|
23
|
+
* optimizer-specific parts of it (`reflect`, `cache`, `onEvent`) are known.
|
|
24
|
+
*/
|
|
25
|
+
async function compare(args) {
|
|
26
|
+
const { entrants, seeds, concurrency = 1 } = args;
|
|
27
|
+
const names = Object.keys(entrants);
|
|
28
|
+
if (names.length === 0) throw new Error("compare requires at least one entrant");
|
|
29
|
+
if (seeds.length === 0) throw new Error("compare requires at least one seed");
|
|
30
|
+
const grid = names.flatMap((entrant) => seeds.map((seed) => ({
|
|
31
|
+
entrant,
|
|
32
|
+
seed
|
|
33
|
+
})));
|
|
34
|
+
const runs = await require_concurrency.mapWithConcurrency({
|
|
35
|
+
items: grid,
|
|
36
|
+
limit: concurrency,
|
|
37
|
+
task: async ({ entrant, seed }) => {
|
|
38
|
+
const result = await entrants[entrant]({ seed });
|
|
39
|
+
return {
|
|
40
|
+
entrant,
|
|
41
|
+
seed,
|
|
42
|
+
score: result.testScore ?? result.bestScore,
|
|
43
|
+
bestScore: result.bestScore,
|
|
44
|
+
...result.testScore === void 0 ? {} : { testScore: result.testScore },
|
|
45
|
+
metricCalls: result.metricCalls,
|
|
46
|
+
costUsd: result.usage.costUsd,
|
|
47
|
+
stopReason: result.stopReason
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const summaries = names.map((entrant) => summarize({
|
|
52
|
+
entrant,
|
|
53
|
+
runs: runs.filter((run) => run.entrant === entrant)
|
|
54
|
+
}));
|
|
55
|
+
const winner = summaries.reduce((best, summary) => summary.meanScore > best.meanScore ? summary : best);
|
|
56
|
+
return {
|
|
57
|
+
winner: winner.entrant,
|
|
58
|
+
summaries: summaries.map((summary) => summary.entrant === winner.entrant ? summary : {
|
|
59
|
+
...summary,
|
|
60
|
+
pValueVsWinner: margin({
|
|
61
|
+
winner: winner.entrant,
|
|
62
|
+
entrant: summary.entrant,
|
|
63
|
+
runs,
|
|
64
|
+
seeds
|
|
65
|
+
})
|
|
66
|
+
}),
|
|
67
|
+
runs
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function summarize(args) {
|
|
71
|
+
const { entrant, runs } = args;
|
|
72
|
+
const scores = runs.map((run) => run.score);
|
|
73
|
+
return {
|
|
74
|
+
entrant,
|
|
75
|
+
runs: runs.length,
|
|
76
|
+
meanScore: mean(scores),
|
|
77
|
+
sdScore: standardDeviation(scores),
|
|
78
|
+
minScore: Math.min(...scores),
|
|
79
|
+
maxScore: Math.max(...scores),
|
|
80
|
+
meanMetricCalls: mean(runs.map((run) => run.metricCalls)),
|
|
81
|
+
meanCostUsd: mean(runs.map((run) => run.costUsd))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Paired across seeds rather than pooled: the same seed puts both entrants on
|
|
86
|
+
* the same sampling order, so the difference at a seed is a comparison and the
|
|
87
|
+
* spread between seeds is not.
|
|
88
|
+
*/
|
|
89
|
+
function margin(args) {
|
|
90
|
+
const { winner, entrant, runs, seeds } = args;
|
|
91
|
+
const differences = seeds.map((seed) => {
|
|
92
|
+
return scoreOf({
|
|
93
|
+
runs,
|
|
94
|
+
entrant: winner,
|
|
95
|
+
seed
|
|
96
|
+
}) - scoreOf({
|
|
97
|
+
runs,
|
|
98
|
+
entrant,
|
|
99
|
+
seed
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
return require_math.signFlipPValue({
|
|
103
|
+
differences,
|
|
104
|
+
observed: differences.reduce((total, value) => total + value, 0),
|
|
105
|
+
maxExact: EXACT_LIMIT
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function scoreOf(args) {
|
|
109
|
+
const { runs, entrant, seed } = args;
|
|
110
|
+
return runs.find((candidate) => candidate.entrant === entrant && candidate.seed === seed)?.score ?? 0;
|
|
111
|
+
}
|
|
112
|
+
function mean(values) {
|
|
113
|
+
return values.reduce((total, value) => total + value, 0) / values.length;
|
|
114
|
+
}
|
|
115
|
+
function standardDeviation(values) {
|
|
116
|
+
if (values.length < 2) return 0;
|
|
117
|
+
const average = mean(values);
|
|
118
|
+
const variance = values.reduce((total, value) => total + (value - average) ** 2, 0) / (values.length - 1);
|
|
119
|
+
return Math.sqrt(variance);
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/judge.ts
|
|
123
|
+
const DEFAULT_SCALE = 5;
|
|
124
|
+
const SCORE = /<score\s+name="([^"]+)"\s*>\s*([\d.]+)\s*<\/score>/g;
|
|
125
|
+
const FEEDBACK = /<feedback>([\s\S]*?)<\/feedback>/;
|
|
126
|
+
/**
|
|
127
|
+
* A model-graded metric that returns written feedback alongside the score.
|
|
128
|
+
*
|
|
129
|
+
* The feedback is the point. A judge that returns only a number reduces a whole
|
|
130
|
+
* paragraph of diagnosis to one scalar, and reflective search — the thing most
|
|
131
|
+
* likely to be pointed at a task too open-ended to score by string match — runs
|
|
132
|
+
* on exactly that diagnosis. So the prompt demands feedback, and demands it be
|
|
133
|
+
* addressed to the instructions rather than to the graded output: "this answer
|
|
134
|
+
* should have mentioned the refund window" tells a rewriting model nothing that
|
|
135
|
+
* "the instruction never says to state the refund window" does not say better.
|
|
136
|
+
*
|
|
137
|
+
* Grades on a small integer scale and normalizes afterwards. Models discriminate
|
|
138
|
+
* between 2 and 4 far more reliably than between 0.4 and 0.8, and the scale is
|
|
139
|
+
* the caller's to widen once they have seen the judge bunch its answers.
|
|
140
|
+
*/
|
|
141
|
+
function createJudge(args) {
|
|
142
|
+
const { model, criteria, scale = DEFAULT_SCALE, renderInput = stringify, renderOutput = stringify, buildPrompt = buildJudgePrompt } = args;
|
|
143
|
+
if (criteria.length === 0) throw new Error("createJudge requires at least one criterion");
|
|
144
|
+
return async ({ input, output, expected, signal }) => {
|
|
145
|
+
return readVerdict({
|
|
146
|
+
response: await model({
|
|
147
|
+
prompt: buildPrompt({
|
|
148
|
+
input: renderInput(input),
|
|
149
|
+
output: renderOutput(output),
|
|
150
|
+
expected: expected === void 0 ? void 0 : renderOutput(expected),
|
|
151
|
+
criteria,
|
|
152
|
+
scale
|
|
153
|
+
}),
|
|
154
|
+
signal
|
|
155
|
+
}),
|
|
156
|
+
criteria,
|
|
157
|
+
scale
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function buildJudgePrompt(args) {
|
|
162
|
+
const { input, output, expected, criteria, scale } = args;
|
|
163
|
+
return [
|
|
164
|
+
"You are grading one output of an automated system against the criteria below.",
|
|
165
|
+
"",
|
|
166
|
+
"<input>",
|
|
167
|
+
input,
|
|
168
|
+
"</input>",
|
|
169
|
+
"",
|
|
170
|
+
"<output>",
|
|
171
|
+
output,
|
|
172
|
+
"</output>",
|
|
173
|
+
...expected === void 0 ? [] : [
|
|
174
|
+
"",
|
|
175
|
+
"<expected_answer>",
|
|
176
|
+
expected,
|
|
177
|
+
"</expected_answer>"
|
|
178
|
+
],
|
|
179
|
+
"",
|
|
180
|
+
"<criteria>",
|
|
181
|
+
criteria.map((criterion) => `<${criterion.name}>${criterion.description}</${criterion.name}>`).join("\n"),
|
|
182
|
+
"</criteria>",
|
|
183
|
+
"",
|
|
184
|
+
`Grade each criterion from 0 to ${scale}, where ${scale} is a perfect answer.`,
|
|
185
|
+
"",
|
|
186
|
+
"Then write feedback. It is read by a program that rewrites the system's instructions, not by a person reviewing this output, so say what the instructions should tell the system to do differently. Feedback about this particular answer is of no use to it.",
|
|
187
|
+
"",
|
|
188
|
+
"Reply in exactly this format and nothing else:",
|
|
189
|
+
...criteria.map((criterion) => `<score name="${criterion.name}">…</score>`),
|
|
190
|
+
"<feedback>…</feedback>"
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A judge that answered off-format graded nothing, so the result is unknown
|
|
195
|
+
* rather than bad. Reporting it as transient is what keeps a formatting failure
|
|
196
|
+
* out of the cache and off the candidate's record — the same treatment a rate
|
|
197
|
+
* limit gets, for the same reason.
|
|
198
|
+
*/
|
|
199
|
+
function readVerdict(args) {
|
|
200
|
+
const { response, criteria, scale } = args;
|
|
201
|
+
const feedback = response.match(FEEDBACK)?.[1]?.trim() ?? "";
|
|
202
|
+
const graded = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const match of response.matchAll(SCORE)) graded.set(match[1], Number(match[2]));
|
|
204
|
+
const objectiveScores = {};
|
|
205
|
+
for (const criterion of criteria) {
|
|
206
|
+
const grade = graded.get(criterion.name);
|
|
207
|
+
if (grade === void 0 || Number.isNaN(grade)) return {
|
|
208
|
+
score: 0,
|
|
209
|
+
feedback: `Judge did not grade "${criterion.name}".`,
|
|
210
|
+
transient: true
|
|
211
|
+
};
|
|
212
|
+
objectiveScores[criterion.name] = clamp(grade / scale);
|
|
213
|
+
}
|
|
214
|
+
const grades = Object.values(objectiveScores);
|
|
215
|
+
return {
|
|
216
|
+
score: grades.reduce((total, grade) => total + grade, 0) / grades.length,
|
|
217
|
+
feedback,
|
|
218
|
+
objectiveScores
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function stringify(value) {
|
|
222
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
223
|
+
}
|
|
224
|
+
function clamp(value) {
|
|
225
|
+
return Math.min(1, Math.max(0, value));
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/usage.ts
|
|
229
|
+
const TOKENS_PER_PRICED_UNIT = 1e6;
|
|
230
|
+
/**
|
|
231
|
+
* Costs a token reading, leaving it untouched when no price list is given.
|
|
232
|
+
*
|
|
233
|
+
* Prices belong to the caller rather than to this library: they change without
|
|
234
|
+
* warning, differ per account, and a stale table baked in here would report
|
|
235
|
+
* confident numbers that are quietly wrong.
|
|
236
|
+
*/
|
|
237
|
+
function priceUsage(args) {
|
|
238
|
+
const { usage, pricing } = args;
|
|
239
|
+
if (pricing === void 0) return usage;
|
|
240
|
+
return {
|
|
241
|
+
...usage,
|
|
242
|
+
costUsd: ((usage.inputTokens ?? 0) * pricing.inputPerMillionUsd + (usage.outputTokens ?? 0) * pricing.outputPerMillionUsd) / TOKENS_PER_PRICED_UNIT
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
exports.BudgetExhausted = require_evaluation.BudgetExhausted;
|
|
247
|
+
exports.assertResumable = require_evaluation.assertResumable;
|
|
248
|
+
exports.bootstrapDemos = require_demos.bootstrapDemos;
|
|
249
|
+
exports.buildJudgePrompt = buildJudgePrompt;
|
|
250
|
+
exports.candidateFingerprint = require_evaluation.candidateFingerprint;
|
|
251
|
+
exports.compare = compare;
|
|
252
|
+
exports.componentNames = require_evaluation.componentNames;
|
|
253
|
+
exports.costExhausted = require_evaluation.costExhausted;
|
|
254
|
+
exports.createBudget = require_evaluation.createBudget;
|
|
255
|
+
exports.createDeadline = require_evaluation.createDeadline;
|
|
256
|
+
exports.createEvaluator = require_evaluation.createEvaluator;
|
|
257
|
+
exports.createJudge = createJudge;
|
|
258
|
+
exports.createMemoryCache = require_evaluation.createMemoryCache;
|
|
259
|
+
exports.formatDemos = require_demos.formatDemos;
|
|
260
|
+
exports.mapWithConcurrency = require_concurrency.mapWithConcurrency;
|
|
261
|
+
exports.measuredMean = require_evaluation.measuredMean;
|
|
262
|
+
exports.parseDemos = require_demos.parseDemos;
|
|
263
|
+
exports.parseProposedText = require_text.parseProposedText;
|
|
264
|
+
exports.priceUsage = priceUsage;
|
|
265
|
+
exports.requireMeasuredMean = require_evaluation.requireMeasuredMean;
|
|
266
|
+
exports.runFingerprint = require_evaluation.runFingerprint;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { a as EvaluationContext, c as RolloutUsage, d as UsageTotals, f as componentNames, i as EvaluationBatch, l as ScoreResult, n as Candidate, o as EvaluationPhase, r as EvaluateArgs, s as EvaluationSplit, t as Adapter, u as TextModel } from "./types-CWv4IQFF.cjs";
|
|
2
|
+
import { n as EvaluationCache, r as createMemoryCache, t as CachedScore } from "./cache-CuSo0NJ8.cjs";
|
|
3
|
+
import { t as Rng } from "./rng-BR5MOedA.cjs";
|
|
4
|
+
import { a as formatDemos, i as bootstrapDemos, n as Demo, o as parseDemos, r as DemoRenderer, t as BootstrapResult } from "./demos-BTuzFNsp.cjs";
|
|
5
|
+
import { a as EvaluateBatchArgs, c as Evaluator, d as costExhausted, f as createEvaluator, g as createBudget, h as Budget, i as BudgetExhausted, l as RetryPolicy, m as requireMeasuredMean, n as OptimizerResult, o as EvaluateTracedArgs, p as measuredMean, r as OptimizerTask, s as EvaluationEvent, t as Optimizer, u as ScoredBatch } from "./optimizer-B7SpRwl7.cjs";
|
|
6
|
+
import { t as BatchSampler } from "./sampling-axOwfZf5.cjs";
|
|
7
|
+
//#region src/checkpoint.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Identifies the run a checkpoint came from: seed candidate, instance ids,
|
|
10
|
+
* seed, and cache namespace.
|
|
11
|
+
*
|
|
12
|
+
* Hashed rather than embedded, because it goes into every snapshot and is only
|
|
13
|
+
* ever compared for equality. The namespace is part of it because a snapshot
|
|
14
|
+
* carries cached scores, and resuming under a different one would replay
|
|
15
|
+
* measurements of a system the run is no longer running. The test set is
|
|
16
|
+
* deliberately absent: it never touches selection, so adding one to a resumed
|
|
17
|
+
* run changes nothing about what that run would have done.
|
|
18
|
+
*/
|
|
19
|
+
declare function runFingerprint(args: {
|
|
20
|
+
seedCandidate: Candidate;
|
|
21
|
+
trainingIds: readonly string[];
|
|
22
|
+
validationIds: readonly string[];
|
|
23
|
+
seed?: number;
|
|
24
|
+
cacheNamespace?: string;
|
|
25
|
+
}): string;
|
|
26
|
+
/**
|
|
27
|
+
* Refuses a checkpoint from a different run rather than silently scoring old
|
|
28
|
+
* candidates against new data — the failure mode that produces a plausible
|
|
29
|
+
* result nobody can reproduce.
|
|
30
|
+
*/
|
|
31
|
+
declare function assertResumable(args: {
|
|
32
|
+
fingerprint: string;
|
|
33
|
+
snapshot?: {
|
|
34
|
+
fingerprint: string;
|
|
35
|
+
};
|
|
36
|
+
}): void;
|
|
37
|
+
/** A candidate's identity: component names and their text, order-independent. */
|
|
38
|
+
declare function candidateFingerprint<K extends string>(candidate: Candidate<K>): string;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/compare.d.ts
|
|
41
|
+
interface ComparisonRun {
|
|
42
|
+
entrant: string;
|
|
43
|
+
seed: number;
|
|
44
|
+
/** What the entrants are ranked on: the held-out score when there is one. */
|
|
45
|
+
score: number;
|
|
46
|
+
bestScore: number;
|
|
47
|
+
testScore?: number;
|
|
48
|
+
metricCalls: number;
|
|
49
|
+
costUsd: number;
|
|
50
|
+
stopReason: string;
|
|
51
|
+
}
|
|
52
|
+
interface ComparisonSummary {
|
|
53
|
+
entrant: string;
|
|
54
|
+
runs: number;
|
|
55
|
+
meanScore: number;
|
|
56
|
+
sdScore: number;
|
|
57
|
+
minScore: number;
|
|
58
|
+
maxScore: number;
|
|
59
|
+
meanMetricCalls: number;
|
|
60
|
+
meanCostUsd: number;
|
|
61
|
+
/**
|
|
62
|
+
* How often the winner's margin over this entrant would arise if the two
|
|
63
|
+
* were equally good and each seed's outcome were a coin flip. Absent for the
|
|
64
|
+
* winner itself.
|
|
65
|
+
*/
|
|
66
|
+
pValueVsWinner?: number;
|
|
67
|
+
}
|
|
68
|
+
interface Comparison {
|
|
69
|
+
/** Highest mean score. Read `pValueVsWinner` before believing it. */
|
|
70
|
+
winner: string;
|
|
71
|
+
summaries: ComparisonSummary[];
|
|
72
|
+
runs: ComparisonRun[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run several optimizers over the same seeds and report which one actually won.
|
|
76
|
+
*
|
|
77
|
+
* Two things make this worth a helper rather than a for-loop. The first is that
|
|
78
|
+
* it ranks on `testScore` where a run reports one: the validation score is the
|
|
79
|
+
* number the search selected against for its whole run, so an entrant that
|
|
80
|
+
* overfits looks strongest on exactly the number it fitted. The second is that
|
|
81
|
+
* a difference in means over a handful of seeds is usually noise, and the
|
|
82
|
+
* paired sign-flip p-value against the winner is what says whether it is.
|
|
83
|
+
*
|
|
84
|
+
* Entrants are functions of a seed rather than optimizer instances, because the
|
|
85
|
+
* seed is constructor config and every optimizer here is deterministic given
|
|
86
|
+
* one — comparing two entrants at a single seed compares two anecdotes. It also
|
|
87
|
+
* leaves the task where the caller builds it, which is the only place the
|
|
88
|
+
* optimizer-specific parts of it (`reflect`, `cache`, `onEvent`) are known.
|
|
89
|
+
*/
|
|
90
|
+
declare function compare<K extends string, Output = unknown>(args: {
|
|
91
|
+
entrants: Record<string, (args: {
|
|
92
|
+
seed: number;
|
|
93
|
+
}) => Promise<OptimizerResult<K, string, Output>>>;
|
|
94
|
+
seeds: readonly number[];
|
|
95
|
+
/** Runs in flight at once. Default 1. */
|
|
96
|
+
concurrency?: number;
|
|
97
|
+
}): Promise<Comparison>;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/concurrency.d.ts
|
|
100
|
+
/**
|
|
101
|
+
* Order-preserving concurrency-limited map. An optimization run's workload is
|
|
102
|
+
* entirely IO bound, so adapters use this to fan out a batch across a bounded
|
|
103
|
+
* number of in-flight model calls.
|
|
104
|
+
*/
|
|
105
|
+
declare function mapWithConcurrency<Item, Result>(args: {
|
|
106
|
+
items: readonly Item[];
|
|
107
|
+
limit: number;
|
|
108
|
+
task: (item: Item, index: number) => Promise<Result>;
|
|
109
|
+
/**
|
|
110
|
+
* Checked before each dispatch. Aborting stops the fan-out rather than
|
|
111
|
+
* letting the remaining batch items spend rollouts on a cancelled run.
|
|
112
|
+
*/
|
|
113
|
+
signal?: AbortSignal;
|
|
114
|
+
}): Promise<Result[]>;
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/deadline.d.ts
|
|
117
|
+
interface Deadline {
|
|
118
|
+
exceeded(): boolean;
|
|
119
|
+
/** Milliseconds left, `Infinity` when no limit was set. Never negative. */
|
|
120
|
+
remainingMs(): number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A wall-clock limit on a run.
|
|
124
|
+
*
|
|
125
|
+
* Rollout and cost ceilings both bound what a run *spends*, and neither bounds
|
|
126
|
+
* how long it takes: a run waiting on a rate-limited provider can sit for an
|
|
127
|
+
* hour without spending a dollar. A deadline is what makes an optimizer safe to
|
|
128
|
+
* put behind a request timeout or a nightly job.
|
|
129
|
+
*
|
|
130
|
+
* Checked between evaluations, so a run overruns by at most the length of one.
|
|
131
|
+
* The clock is injectable because a deadline that can only be tested by waiting
|
|
132
|
+
* is a deadline nobody tests.
|
|
133
|
+
*/
|
|
134
|
+
declare function createDeadline(args: {
|
|
135
|
+
maxWallClockMs?: number;
|
|
136
|
+
now?: () => number;
|
|
137
|
+
}): Deadline;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/judge.d.ts
|
|
140
|
+
/** One thing the judge grades, and what a perfect answer looks like for it. */
|
|
141
|
+
interface JudgeCriterion {
|
|
142
|
+
name: string;
|
|
143
|
+
description: string;
|
|
144
|
+
}
|
|
145
|
+
type JudgePromptBuilder = (args: {
|
|
146
|
+
input: string;
|
|
147
|
+
output: string;
|
|
148
|
+
expected?: string;
|
|
149
|
+
criteria: readonly JudgeCriterion[];
|
|
150
|
+
scale: number;
|
|
151
|
+
}) => string;
|
|
152
|
+
type Judge<Datum, Output> = (args: {
|
|
153
|
+
input: Datum;
|
|
154
|
+
output: Output;
|
|
155
|
+
/** The gold answer, when the caller has one. */
|
|
156
|
+
expected?: Output;
|
|
157
|
+
signal?: AbortSignal;
|
|
158
|
+
}) => Promise<ScoreResult>;
|
|
159
|
+
/**
|
|
160
|
+
* A model-graded metric that returns written feedback alongside the score.
|
|
161
|
+
*
|
|
162
|
+
* The feedback is the point. A judge that returns only a number reduces a whole
|
|
163
|
+
* paragraph of diagnosis to one scalar, and reflective search — the thing most
|
|
164
|
+
* likely to be pointed at a task too open-ended to score by string match — runs
|
|
165
|
+
* on exactly that diagnosis. So the prompt demands feedback, and demands it be
|
|
166
|
+
* addressed to the instructions rather than to the graded output: "this answer
|
|
167
|
+
* should have mentioned the refund window" tells a rewriting model nothing that
|
|
168
|
+
* "the instruction never says to state the refund window" does not say better.
|
|
169
|
+
*
|
|
170
|
+
* Grades on a small integer scale and normalizes afterwards. Models discriminate
|
|
171
|
+
* between 2 and 4 far more reliably than between 0.4 and 0.8, and the scale is
|
|
172
|
+
* the caller's to widen once they have seen the judge bunch its answers.
|
|
173
|
+
*/
|
|
174
|
+
declare function createJudge<Datum = string, Output = string>(args: {
|
|
175
|
+
model: TextModel;
|
|
176
|
+
criteria: readonly JudgeCriterion[];
|
|
177
|
+
/** Highest grade the judge may award per criterion. Default 5. */
|
|
178
|
+
scale?: number;
|
|
179
|
+
renderInput?: (input: Datum) => string;
|
|
180
|
+
renderOutput?: (output: Output) => string;
|
|
181
|
+
buildPrompt?: JudgePromptBuilder;
|
|
182
|
+
}): Judge<Datum, Output>;
|
|
183
|
+
declare function buildJudgePrompt(args: {
|
|
184
|
+
input: string;
|
|
185
|
+
output: string;
|
|
186
|
+
expected?: string;
|
|
187
|
+
criteria: readonly JudgeCriterion[];
|
|
188
|
+
scale: number;
|
|
189
|
+
}): string;
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/text.d.ts
|
|
192
|
+
/**
|
|
193
|
+
* Pull the proposed text out of the reflection model's response.
|
|
194
|
+
*
|
|
195
|
+
* Spans the *first* fence to the *last* one rather than matching blocks
|
|
196
|
+
* individually: proposed instructions routinely contain their own fenced
|
|
197
|
+
* examples, and per-block matching would silently return only the trailing
|
|
198
|
+
* fragment. A response with a single fence was truncated mid-generation, so the
|
|
199
|
+
* stray fence is stripped and the partial text kept.
|
|
200
|
+
*/
|
|
201
|
+
declare function parseProposedText(response: string): string;
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/usage.d.ts
|
|
204
|
+
/** What a model charges, in the per-million-tokens units every vendor quotes. */
|
|
205
|
+
interface TokenPricing {
|
|
206
|
+
inputPerMillionUsd: number;
|
|
207
|
+
outputPerMillionUsd: number;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Costs a token reading, leaving it untouched when no price list is given.
|
|
211
|
+
*
|
|
212
|
+
* Prices belong to the caller rather than to this library: they change without
|
|
213
|
+
* warning, differ per account, and a stale table baked in here would report
|
|
214
|
+
* confident numbers that are quietly wrong.
|
|
215
|
+
*/
|
|
216
|
+
declare function priceUsage(args: {
|
|
217
|
+
usage: RolloutUsage;
|
|
218
|
+
pricing?: TokenPricing;
|
|
219
|
+
}): RolloutUsage;
|
|
220
|
+
//#endregion
|
|
221
|
+
export { type Adapter, type BatchSampler, type BootstrapResult, type Budget, BudgetExhausted, type CachedScore, type Candidate, type Comparison, type ComparisonRun, type ComparisonSummary, type Deadline, type Demo, type DemoRenderer, type EvaluateArgs, type EvaluateBatchArgs, type EvaluateTracedArgs, type EvaluationBatch, type EvaluationCache, type EvaluationContext, type EvaluationEvent, type EvaluationPhase, type EvaluationSplit, type Evaluator, type Judge, type JudgeCriterion, type JudgePromptBuilder, type Optimizer, type OptimizerResult, type OptimizerTask, type RetryPolicy, type Rng, type RolloutUsage, type ScoreResult, type ScoredBatch, type TextModel, type TokenPricing, type UsageTotals, assertResumable, bootstrapDemos, buildJudgePrompt, candidateFingerprint, compare, componentNames, costExhausted, createBudget, createDeadline, createEvaluator, createJudge, createMemoryCache, formatDemos, mapWithConcurrency, measuredMean, parseDemos, parseProposedText, priceUsage, requireMeasuredMean, runFingerprint };
|