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,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.mjs";
2
+ import { n as EvaluationCache, r as createMemoryCache, t as CachedScore } from "./cache-CuSo0NJ8.mjs";
3
+ import { t as Rng } from "./rng-BR5MOedA.mjs";
4
+ import { a as formatDemos, i as bootstrapDemos, n as Demo, o as parseDemos, r as DemoRenderer, t as BootstrapResult } from "./demos-B0pVQjYC.mjs";
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-DqCoth_w.mjs";
6
+ import { t as BatchSampler } from "./sampling-DFo_7RNJ.mjs";
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 };
package/dist/index.mjs ADDED
@@ -0,0 +1,245 @@
1
+ import { a as requireMeasuredMean, c as candidateFingerprint, d as createMemoryCache, 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 { r as signFlipPValue } from "./math-DhrDmpFS.mjs";
4
+ import { n as formatDemos, r as parseDemos, t as bootstrapDemos } from "./demos-Degx6UmP.mjs";
5
+ import { t as parseProposedText } from "./text--v4Ffbus.mjs";
6
+ //#region src/compare.ts
7
+ const EXACT_LIMIT = 16;
8
+ /**
9
+ * Run several optimizers over the same seeds and report which one actually won.
10
+ *
11
+ * Two things make this worth a helper rather than a for-loop. The first is that
12
+ * it ranks on `testScore` where a run reports one: the validation score is the
13
+ * number the search selected against for its whole run, so an entrant that
14
+ * overfits looks strongest on exactly the number it fitted. The second is that
15
+ * a difference in means over a handful of seeds is usually noise, and the
16
+ * paired sign-flip p-value against the winner is what says whether it is.
17
+ *
18
+ * Entrants are functions of a seed rather than optimizer instances, because the
19
+ * seed is constructor config and every optimizer here is deterministic given
20
+ * one — comparing two entrants at a single seed compares two anecdotes. It also
21
+ * leaves the task where the caller builds it, which is the only place the
22
+ * optimizer-specific parts of it (`reflect`, `cache`, `onEvent`) are known.
23
+ */
24
+ async function compare(args) {
25
+ const { entrants, seeds, concurrency = 1 } = args;
26
+ const names = Object.keys(entrants);
27
+ if (names.length === 0) throw new Error("compare requires at least one entrant");
28
+ if (seeds.length === 0) throw new Error("compare requires at least one seed");
29
+ const grid = names.flatMap((entrant) => seeds.map((seed) => ({
30
+ entrant,
31
+ seed
32
+ })));
33
+ const runs = await mapWithConcurrency({
34
+ items: grid,
35
+ limit: concurrency,
36
+ task: async ({ entrant, seed }) => {
37
+ const result = await entrants[entrant]({ seed });
38
+ return {
39
+ entrant,
40
+ seed,
41
+ score: result.testScore ?? result.bestScore,
42
+ bestScore: result.bestScore,
43
+ ...result.testScore === void 0 ? {} : { testScore: result.testScore },
44
+ metricCalls: result.metricCalls,
45
+ costUsd: result.usage.costUsd,
46
+ stopReason: result.stopReason
47
+ };
48
+ }
49
+ });
50
+ const summaries = names.map((entrant) => summarize({
51
+ entrant,
52
+ runs: runs.filter((run) => run.entrant === entrant)
53
+ }));
54
+ const winner = summaries.reduce((best, summary) => summary.meanScore > best.meanScore ? summary : best);
55
+ return {
56
+ winner: winner.entrant,
57
+ summaries: summaries.map((summary) => summary.entrant === winner.entrant ? summary : {
58
+ ...summary,
59
+ pValueVsWinner: margin({
60
+ winner: winner.entrant,
61
+ entrant: summary.entrant,
62
+ runs,
63
+ seeds
64
+ })
65
+ }),
66
+ runs
67
+ };
68
+ }
69
+ function summarize(args) {
70
+ const { entrant, runs } = args;
71
+ const scores = runs.map((run) => run.score);
72
+ return {
73
+ entrant,
74
+ runs: runs.length,
75
+ meanScore: mean(scores),
76
+ sdScore: standardDeviation(scores),
77
+ minScore: Math.min(...scores),
78
+ maxScore: Math.max(...scores),
79
+ meanMetricCalls: mean(runs.map((run) => run.metricCalls)),
80
+ meanCostUsd: mean(runs.map((run) => run.costUsd))
81
+ };
82
+ }
83
+ /**
84
+ * Paired across seeds rather than pooled: the same seed puts both entrants on
85
+ * the same sampling order, so the difference at a seed is a comparison and the
86
+ * spread between seeds is not.
87
+ */
88
+ function margin(args) {
89
+ const { winner, entrant, runs, seeds } = args;
90
+ const differences = seeds.map((seed) => {
91
+ return scoreOf({
92
+ runs,
93
+ entrant: winner,
94
+ seed
95
+ }) - scoreOf({
96
+ runs,
97
+ entrant,
98
+ seed
99
+ });
100
+ });
101
+ return signFlipPValue({
102
+ differences,
103
+ observed: differences.reduce((total, value) => total + value, 0),
104
+ maxExact: EXACT_LIMIT
105
+ });
106
+ }
107
+ function scoreOf(args) {
108
+ const { runs, entrant, seed } = args;
109
+ return runs.find((candidate) => candidate.entrant === entrant && candidate.seed === seed)?.score ?? 0;
110
+ }
111
+ function mean(values) {
112
+ return values.reduce((total, value) => total + value, 0) / values.length;
113
+ }
114
+ function standardDeviation(values) {
115
+ if (values.length < 2) return 0;
116
+ const average = mean(values);
117
+ const variance = values.reduce((total, value) => total + (value - average) ** 2, 0) / (values.length - 1);
118
+ return Math.sqrt(variance);
119
+ }
120
+ //#endregion
121
+ //#region src/judge.ts
122
+ const DEFAULT_SCALE = 5;
123
+ const SCORE = /<score\s+name="([^"]+)"\s*>\s*([\d.]+)\s*<\/score>/g;
124
+ const FEEDBACK = /<feedback>([\s\S]*?)<\/feedback>/;
125
+ /**
126
+ * A model-graded metric that returns written feedback alongside the score.
127
+ *
128
+ * The feedback is the point. A judge that returns only a number reduces a whole
129
+ * paragraph of diagnosis to one scalar, and reflective search — the thing most
130
+ * likely to be pointed at a task too open-ended to score by string match — runs
131
+ * on exactly that diagnosis. So the prompt demands feedback, and demands it be
132
+ * addressed to the instructions rather than to the graded output: "this answer
133
+ * should have mentioned the refund window" tells a rewriting model nothing that
134
+ * "the instruction never says to state the refund window" does not say better.
135
+ *
136
+ * Grades on a small integer scale and normalizes afterwards. Models discriminate
137
+ * between 2 and 4 far more reliably than between 0.4 and 0.8, and the scale is
138
+ * the caller's to widen once they have seen the judge bunch its answers.
139
+ */
140
+ function createJudge(args) {
141
+ const { model, criteria, scale = DEFAULT_SCALE, renderInput = stringify, renderOutput = stringify, buildPrompt = buildJudgePrompt } = args;
142
+ if (criteria.length === 0) throw new Error("createJudge requires at least one criterion");
143
+ return async ({ input, output, expected, signal }) => {
144
+ return readVerdict({
145
+ response: await model({
146
+ prompt: buildPrompt({
147
+ input: renderInput(input),
148
+ output: renderOutput(output),
149
+ expected: expected === void 0 ? void 0 : renderOutput(expected),
150
+ criteria,
151
+ scale
152
+ }),
153
+ signal
154
+ }),
155
+ criteria,
156
+ scale
157
+ });
158
+ };
159
+ }
160
+ function buildJudgePrompt(args) {
161
+ const { input, output, expected, criteria, scale } = args;
162
+ return [
163
+ "You are grading one output of an automated system against the criteria below.",
164
+ "",
165
+ "<input>",
166
+ input,
167
+ "</input>",
168
+ "",
169
+ "<output>",
170
+ output,
171
+ "</output>",
172
+ ...expected === void 0 ? [] : [
173
+ "",
174
+ "<expected_answer>",
175
+ expected,
176
+ "</expected_answer>"
177
+ ],
178
+ "",
179
+ "<criteria>",
180
+ criteria.map((criterion) => `<${criterion.name}>${criterion.description}</${criterion.name}>`).join("\n"),
181
+ "</criteria>",
182
+ "",
183
+ `Grade each criterion from 0 to ${scale}, where ${scale} is a perfect answer.`,
184
+ "",
185
+ "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.",
186
+ "",
187
+ "Reply in exactly this format and nothing else:",
188
+ ...criteria.map((criterion) => `<score name="${criterion.name}">…</score>`),
189
+ "<feedback>…</feedback>"
190
+ ].join("\n");
191
+ }
192
+ /**
193
+ * A judge that answered off-format graded nothing, so the result is unknown
194
+ * rather than bad. Reporting it as transient is what keeps a formatting failure
195
+ * out of the cache and off the candidate's record — the same treatment a rate
196
+ * limit gets, for the same reason.
197
+ */
198
+ function readVerdict(args) {
199
+ const { response, criteria, scale } = args;
200
+ const feedback = response.match(FEEDBACK)?.[1]?.trim() ?? "";
201
+ const graded = /* @__PURE__ */ new Map();
202
+ for (const match of response.matchAll(SCORE)) graded.set(match[1], Number(match[2]));
203
+ const objectiveScores = {};
204
+ for (const criterion of criteria) {
205
+ const grade = graded.get(criterion.name);
206
+ if (grade === void 0 || Number.isNaN(grade)) return {
207
+ score: 0,
208
+ feedback: `Judge did not grade "${criterion.name}".`,
209
+ transient: true
210
+ };
211
+ objectiveScores[criterion.name] = clamp(grade / scale);
212
+ }
213
+ const grades = Object.values(objectiveScores);
214
+ return {
215
+ score: grades.reduce((total, grade) => total + grade, 0) / grades.length,
216
+ feedback,
217
+ objectiveScores
218
+ };
219
+ }
220
+ function stringify(value) {
221
+ return typeof value === "string" ? value : JSON.stringify(value);
222
+ }
223
+ function clamp(value) {
224
+ return Math.min(1, Math.max(0, value));
225
+ }
226
+ //#endregion
227
+ //#region src/usage.ts
228
+ const TOKENS_PER_PRICED_UNIT = 1e6;
229
+ /**
230
+ * Costs a token reading, leaving it untouched when no price list is given.
231
+ *
232
+ * Prices belong to the caller rather than to this library: they change without
233
+ * warning, differ per account, and a stale table baked in here would report
234
+ * confident numbers that are quietly wrong.
235
+ */
236
+ function priceUsage(args) {
237
+ const { usage, pricing } = args;
238
+ if (pricing === void 0) return usage;
239
+ return {
240
+ ...usage,
241
+ costUsd: ((usage.inputTokens ?? 0) * pricing.inputPerMillionUsd + (usage.outputTokens ?? 0) * pricing.outputPerMillionUsd) / TOKENS_PER_PRICED_UNIT
242
+ };
243
+ }
244
+ //#endregion
245
+ export { BudgetExhausted, assertResumable, bootstrapDemos, buildJudgePrompt, candidateFingerprint, compare, componentNames, costExhausted, createBudget, createDeadline, createEvaluator, createJudge, createMemoryCache, formatDemos, mapWithConcurrency, measuredMean, parseDemos, parseProposedText, priceUsage, requireMeasuredMean, runFingerprint };
@@ -0,0 +1,101 @@
1
+ //#region src/math.ts
2
+ function argmax(values) {
3
+ let bestIndex = 0;
4
+ let bestValue = Number.NEGATIVE_INFINITY;
5
+ for (let index = 0; index < values.length; index += 1) {
6
+ const value = values[index];
7
+ if (value > bestValue) {
8
+ bestValue = value;
9
+ bestIndex = index;
10
+ }
11
+ }
12
+ return bestIndex;
13
+ }
14
+ /** Mean over the values that exist; unscored instances are not zeros. */
15
+ function mean(values) {
16
+ let total = 0;
17
+ let count = 0;
18
+ for (const value of values) if (value !== void 0) {
19
+ total += value;
20
+ count += 1;
21
+ }
22
+ return count === 0 ? 0 : total / count;
23
+ }
24
+ function sum(values) {
25
+ let total = 0;
26
+ for (const value of values) total += value;
27
+ return total;
28
+ }
29
+ /**
30
+ * One-sided p-value from a paired sign-flip (permutation) test: how often a
31
+ * total this large arises when each difference is equally likely to have come
32
+ * out the other way. Exact by enumeration for small samples, normal-approximated
33
+ * past `maxExact` where 2^n stops being cheap.
34
+ *
35
+ * Paired and distribution-free, which is what a comparison of two runs over the
36
+ * same instances needs — the scores are not independent draws and are not
37
+ * normal, so a t-test on them is answering a different question.
38
+ */
39
+ function signFlipPValue(args) {
40
+ const { differences, observed, maxExact } = args;
41
+ const moved = differences.filter((difference) => difference !== 0);
42
+ if (moved.length === 0) return 1;
43
+ if (moved.length > maxExact) return normalTailProbability({
44
+ differences: moved,
45
+ observed
46
+ });
47
+ const assignments = 2 ** moved.length;
48
+ let atLeastAsExtreme = 0;
49
+ for (let mask = 0; mask < assignments; mask += 1) {
50
+ let total = 0;
51
+ for (let index = 0; index < moved.length; index += 1) {
52
+ const sign = mask >> index & 1 ? -1 : 1;
53
+ total += sign * moved[index];
54
+ }
55
+ if (total >= observed) atLeastAsExtreme += 1;
56
+ }
57
+ return atLeastAsExtreme / assignments;
58
+ }
59
+ /**
60
+ * The same tail probability from a normal approximation, for batches too large
61
+ * to enumerate. Under the sign-flip null each difference has mean zero and
62
+ * variance equal to its square, so the total's variance is their sum.
63
+ */
64
+ function normalTailProbability(args) {
65
+ const { differences, observed } = args;
66
+ const variance = differences.reduce((total, difference) => total + difference * difference, 0);
67
+ if (variance === 0) return observed > 0 ? 0 : 1;
68
+ return 1 - standardNormalCdf(observed / Math.sqrt(variance));
69
+ }
70
+ /** Abramowitz and Stegun 7.1.26, which is accurate to about 1e-7. */
71
+ function standardNormalCdf(value) {
72
+ const sign = value < 0 ? -1 : 1;
73
+ const scaled = Math.abs(value) / Math.SQRT2;
74
+ const t = 1 / (1 + .3275911 * scaled);
75
+ return .5 * (1 + sign * (1 - t * (.254829592 + t * (-.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429)))) * Math.exp(-scaled * scaled)));
76
+ }
77
+ //#endregion
78
+ Object.defineProperty(exports, "argmax", {
79
+ enumerable: true,
80
+ get: function() {
81
+ return argmax;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "mean", {
85
+ enumerable: true,
86
+ get: function() {
87
+ return mean;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "signFlipPValue", {
91
+ enumerable: true,
92
+ get: function() {
93
+ return signFlipPValue;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "sum", {
97
+ enumerable: true,
98
+ get: function() {
99
+ return sum;
100
+ }
101
+ });
@@ -0,0 +1,78 @@
1
+ //#region src/math.ts
2
+ function argmax(values) {
3
+ let bestIndex = 0;
4
+ let bestValue = Number.NEGATIVE_INFINITY;
5
+ for (let index = 0; index < values.length; index += 1) {
6
+ const value = values[index];
7
+ if (value > bestValue) {
8
+ bestValue = value;
9
+ bestIndex = index;
10
+ }
11
+ }
12
+ return bestIndex;
13
+ }
14
+ /** Mean over the values that exist; unscored instances are not zeros. */
15
+ function mean(values) {
16
+ let total = 0;
17
+ let count = 0;
18
+ for (const value of values) if (value !== void 0) {
19
+ total += value;
20
+ count += 1;
21
+ }
22
+ return count === 0 ? 0 : total / count;
23
+ }
24
+ function sum(values) {
25
+ let total = 0;
26
+ for (const value of values) total += value;
27
+ return total;
28
+ }
29
+ /**
30
+ * One-sided p-value from a paired sign-flip (permutation) test: how often a
31
+ * total this large arises when each difference is equally likely to have come
32
+ * out the other way. Exact by enumeration for small samples, normal-approximated
33
+ * past `maxExact` where 2^n stops being cheap.
34
+ *
35
+ * Paired and distribution-free, which is what a comparison of two runs over the
36
+ * same instances needs — the scores are not independent draws and are not
37
+ * normal, so a t-test on them is answering a different question.
38
+ */
39
+ function signFlipPValue(args) {
40
+ const { differences, observed, maxExact } = args;
41
+ const moved = differences.filter((difference) => difference !== 0);
42
+ if (moved.length === 0) return 1;
43
+ if (moved.length > maxExact) return normalTailProbability({
44
+ differences: moved,
45
+ observed
46
+ });
47
+ const assignments = 2 ** moved.length;
48
+ let atLeastAsExtreme = 0;
49
+ for (let mask = 0; mask < assignments; mask += 1) {
50
+ let total = 0;
51
+ for (let index = 0; index < moved.length; index += 1) {
52
+ const sign = mask >> index & 1 ? -1 : 1;
53
+ total += sign * moved[index];
54
+ }
55
+ if (total >= observed) atLeastAsExtreme += 1;
56
+ }
57
+ return atLeastAsExtreme / assignments;
58
+ }
59
+ /**
60
+ * The same tail probability from a normal approximation, for batches too large
61
+ * to enumerate. Under the sign-flip null each difference has mean zero and
62
+ * variance equal to its square, so the total's variance is their sum.
63
+ */
64
+ function normalTailProbability(args) {
65
+ const { differences, observed } = args;
66
+ const variance = differences.reduce((total, difference) => total + difference * difference, 0);
67
+ if (variance === 0) return observed > 0 ? 0 : 1;
68
+ return 1 - standardNormalCdf(observed / Math.sqrt(variance));
69
+ }
70
+ /** Abramowitz and Stegun 7.1.26, which is accurate to about 1e-7. */
71
+ function standardNormalCdf(value) {
72
+ const sign = value < 0 ? -1 : 1;
73
+ const scaled = Math.abs(value) / Math.SQRT2;
74
+ const t = 1 / (1 + .3275911 * scaled);
75
+ return .5 * (1 + sign * (1 - t * (.254829592 + t * (-.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429)))) * Math.exp(-scaled * scaled)));
76
+ }
77
+ //#endregion
78
+ export { sum as i, mean as n, signFlipPValue as r, argmax as t };