textopt 0.0.0 → 0.2.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/README.md +65 -25
- package/dist/bootstrap-search/index.cjs +159 -73
- package/dist/bootstrap-search/index.d.cts +32 -10
- package/dist/bootstrap-search/index.d.mts +32 -10
- package/dist/bootstrap-search/index.mjs +150 -66
- package/dist/demos-9v5ts7F3.cjs +244 -0
- package/dist/{demos-B0pVQjYC.d.mts → demos-ASsSXYXA.d.mts} +10 -3
- package/dist/demos-Brobjfuc.mjs +215 -0
- package/dist/{demos-BTuzFNsp.d.cts → demos-ByaLZy-Z.d.cts} +10 -3
- package/dist/file-cache.cjs +27 -8
- package/dist/file-cache.d.cts +13 -0
- package/dist/file-cache.d.mts +13 -0
- package/dist/file-cache.mjs +27 -8
- package/dist/gepa/index.cjs +128 -80
- package/dist/gepa/index.d.cts +15 -7
- package/dist/gepa/index.d.mts +15 -7
- package/dist/gepa/index.mjs +101 -55
- package/dist/index.cjs +157 -30
- package/dist/index.d.cts +177 -7
- package/dist/index.d.mts +177 -7
- package/dist/index.mjs +139 -18
- package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
- package/dist/math-Dqme4rYz.mjs +123 -0
- package/dist/mipro/index.cjs +104 -70
- package/dist/mipro/index.d.cts +17 -14
- package/dist/mipro/index.d.mts +17 -14
- package/dist/mipro/index.mjs +90 -58
- package/dist/opro/index.cjs +136 -51
- package/dist/opro/index.d.cts +17 -9
- package/dist/opro/index.d.mts +17 -9
- package/dist/opro/index.mjs +121 -38
- package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-4Zv-Zt2t.d.cts} +90 -5
- package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ds5mzYjz.d.mts} +90 -5
- package/dist/random-search/index.cjs +99 -49
- package/dist/random-search/index.d.cts +15 -13
- package/dist/random-search/index.d.mts +15 -13
- package/dist/random-search/index.mjs +89 -41
- package/dist/{reflection-Cr_upzU0.d.mts → reflection-CMezGu6u.d.mts} +38 -14
- package/dist/{reflection-CQToe-5B.d.cts → reflection-D0A7eahD.d.cts} +38 -14
- package/dist/reporting-bq007_2z.d.cts +294 -0
- package/dist/reporting-bq007_2z.d.mts +294 -0
- package/dist/simba/index.cjs +216 -83
- package/dist/simba/index.d.cts +53 -13
- package/dist/simba/index.d.mts +53 -13
- package/dist/simba/index.mjs +206 -75
- package/dist/testing.cjs +1 -0
- package/dist/testing.d.cts +5 -3
- package/dist/testing.d.mts +5 -3
- package/dist/testing.mjs +1 -1
- package/dist/{evaluation-OZOp6TB7.cjs → warnings-CWRJF-jA.cjs} +228 -5
- package/dist/{evaluation-BV0nSZVx.mjs → warnings-OxvDi9kN.mjs} +175 -6
- package/docs/adapters.md +169 -0
- package/docs/benchmark.md +90 -0
- package/docs/data-prep.md +113 -0
- package/docs/distillation.md +128 -0
- package/docs/evaluation.md +87 -0
- package/docs/metric-preflight.md +132 -0
- package/docs/optimizers.md +293 -0
- package/docs/tuning.md +130 -0
- package/package.json +6 -4
- package/dist/demos-B9BJiNKz.cjs +0 -143
- package/dist/demos-Degx6UmP.mjs +0 -126
- package/dist/math-DhrDmpFS.mjs +0 -78
- package/dist/types-CWv4IQFF.d.cts +0 -129
- package/dist/types-CWv4IQFF.d.mts +0 -129
package/dist/gepa/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { S as createBudget, _ as componentNames, a as instanceRow, b as defaultInstanceId, c as BudgetExhausted, d as measuredMean, g as runFingerprint, h as candidateFingerprint, i as flushReporters, l as costExhausted, m as assertResumable, n as seedScoreWarnings, o as isCandidateAccepted, p as createDeadline, r as createEmitter, s as isRunFinished, t as resolveValidationSet, u as createEvaluator, y as createMemoryCache } from "../warnings-OxvDi9kN.mjs";
|
|
2
2
|
import { t as mapWithConcurrency } from "../concurrency-D58PWeSk.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { a as sum, i as signFlipPValue, r as mean, t as argmax } from "../math-Dqme4rYz.mjs";
|
|
4
|
+
import { r as parseDemos, t as formatDemos } from "../demos-Brobjfuc.mjs";
|
|
5
5
|
import { t as createSeededRng } from "../rng-Dtc5eZ_W.mjs";
|
|
6
6
|
import { a as createDefaultProposer, i as buildSimplifyPrompt, n as buildReflectionPrompt, o as diverseReflectionStrategies, r as buildRewritePrompt, t as buildGeneralizePrompt } from "../reflection-mwMhrjs_.mjs";
|
|
7
7
|
import { t as createEpochShuffledSampler } from "../sampling-CfHt7Gue.mjs";
|
|
@@ -512,7 +512,9 @@ function hasSurvivor(args) {
|
|
|
512
512
|
* `frontier` chooses what the fronts are taken over. "instance" is GEPA as
|
|
513
513
|
* published. "objective" tracks candidates leading each named objective the
|
|
514
514
|
* adapter reports, and "hybrid" pools both — a candidate then earns selection
|
|
515
|
-
* weight for every instance it wins *and* every objective it leads.
|
|
515
|
+
* weight for every instance it wins *and* every objective it leads. The
|
|
516
|
+
* objectives are whatever the adapter put in `objectiveScores`, which for a
|
|
517
|
+
* judge is every criterion it graded, including any at `weight: 0`.
|
|
516
518
|
*/
|
|
517
519
|
function paretoSelector(args = {}) {
|
|
518
520
|
const { epsilon = 0, frontier = "instance" } = args;
|
|
@@ -659,7 +661,8 @@ function improvementAcceptance(args = {}) {
|
|
|
659
661
|
*/
|
|
660
662
|
function pairedPermutationAcceptance(args = {}) {
|
|
661
663
|
const { alpha = .2, maxExact = 16 } = args;
|
|
662
|
-
|
|
664
|
+
if (!Number.isFinite(alpha) || alpha <= 0 || alpha > 1) throw new Error(`alpha must be greater than 0 and at most 1, received ${alpha}`);
|
|
665
|
+
const policy = ({ parentScores, childScores }) => {
|
|
663
666
|
const differences = [];
|
|
664
667
|
for (let index = 0; index < parentScores.length; index += 1) differences.push(childScores[index] - parentScores[index]);
|
|
665
668
|
const observed = sum(differences);
|
|
@@ -670,6 +673,33 @@ function pairedPermutationAcceptance(args = {}) {
|
|
|
670
673
|
maxExact
|
|
671
674
|
}) <= alpha;
|
|
672
675
|
};
|
|
676
|
+
policy.minimumPairs = smallestAcceptableBatch({
|
|
677
|
+
alpha,
|
|
678
|
+
maxExact
|
|
679
|
+
});
|
|
680
|
+
return policy;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Smallest paired batch on which a sign-flip test at `alpha` could return a
|
|
684
|
+
* verdict of "accept" at all.
|
|
685
|
+
*
|
|
686
|
+
* Enumerating n non-zero differences gives 2^n equally likely sign
|
|
687
|
+
* assignments, so the smallest attainable p-value is 2^-n and no batch below
|
|
688
|
+
* `log2(1/alpha)` pairs can clear the bar. Past `maxExact` the p-value comes
|
|
689
|
+
* from a normal approximation, which has no such floor — so when the exact
|
|
690
|
+
* requirement is out of that regime's reach, the first batch that leaves the
|
|
691
|
+
* regime is the honest answer rather than an exact size that never applies.
|
|
692
|
+
*/
|
|
693
|
+
function smallestAcceptableBatch(args) {
|
|
694
|
+
const { alpha, maxExact } = args;
|
|
695
|
+
let pairs = 1;
|
|
696
|
+
let smallestPValue = .5;
|
|
697
|
+
while (smallestPValue > alpha) {
|
|
698
|
+
pairs += 1;
|
|
699
|
+
smallestPValue /= 2;
|
|
700
|
+
if (pairs > maxExact) return maxExact + 1;
|
|
701
|
+
}
|
|
702
|
+
return pairs;
|
|
673
703
|
}
|
|
674
704
|
/**
|
|
675
705
|
* Highest mean over the instances it was scored on, with wider coverage
|
|
@@ -738,16 +768,24 @@ var GepaOptimizer = class {
|
|
|
738
768
|
this.#config = config;
|
|
739
769
|
}
|
|
740
770
|
async optimize(task) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
771
|
+
try {
|
|
772
|
+
return await runGepa({
|
|
773
|
+
config: this.#config,
|
|
774
|
+
task
|
|
775
|
+
});
|
|
776
|
+
} finally {
|
|
777
|
+
await flushReporters(task.reporters ?? []);
|
|
778
|
+
}
|
|
745
779
|
}
|
|
746
780
|
};
|
|
747
781
|
async function runGepa(args) {
|
|
748
782
|
const { config, task } = args;
|
|
749
783
|
const { minibatchSize = DEFAULT_MINIBATCH_SIZE, maxIterations = Number.POSITIVE_INFINITY, seed = 0, candidateSelector = paretoSelector(), acceptance = improvementAcceptance(), merge, skipPerfectScore = true, perfectScore = 1, rejectedProposalMemory = DEFAULT_REJECTED_PROPOSAL_MEMORY, proposals, reflection, checkpointCache = true, trackBestOutputs = false, raiseOnError = true } = config;
|
|
750
|
-
const { seedCandidate, trainingSet, validationSet
|
|
784
|
+
const { seedCandidate, trainingSet, validationSet: requestedValidationSet, testSet, adapter, reflect, maxMetricCalls, componentSelector = roundRobinComponentSelector(), batchSampler = createEpochShuffledSampler({ minibatchSize }), valEvaluationPolicy = fullEvaluationPolicy(), cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
|
|
785
|
+
const { validationSet, warnings } = resolveValidationSet({
|
|
786
|
+
validationSet: requestedValidationSet,
|
|
787
|
+
trainingSet
|
|
788
|
+
});
|
|
751
789
|
const deadline = createDeadline({ maxWallClockMs });
|
|
752
790
|
const seedComponents = componentNames(seedCandidate);
|
|
753
791
|
const mergeConfig = {
|
|
@@ -827,8 +865,26 @@ async function runGepa(args) {
|
|
|
827
865
|
let mergesDue = resumeFrom?.merge.due ?? 0;
|
|
828
866
|
let totalMergesTested = resumeFrom?.merge.tested ?? 0;
|
|
829
867
|
let lastIterationAccepted = resumeFrom?.merge.lastIterationAccepted ?? false;
|
|
830
|
-
|
|
831
|
-
|
|
868
|
+
const emit = createEmitter(reporters);
|
|
869
|
+
/**
|
|
870
|
+
* Everything an acceptance means, in one event: the text, the aggregate, and
|
|
871
|
+
* the row it put on the frontier. Emitted from one place because the merge
|
|
872
|
+
* path and the mutation path accept candidates separately, and a payload
|
|
873
|
+
* assembled twice is a payload that drifts.
|
|
874
|
+
*/
|
|
875
|
+
function emitAccepted(record) {
|
|
876
|
+
const outputs = outputsByCandidate.get(record.id);
|
|
877
|
+
emit({
|
|
878
|
+
type: "candidateAccepted",
|
|
879
|
+
iteration,
|
|
880
|
+
candidateId: record.id,
|
|
881
|
+
parentIds: record.parentIds,
|
|
882
|
+
aggregateScore: record.aggregateScore,
|
|
883
|
+
source: record.source,
|
|
884
|
+
candidate: record.candidate,
|
|
885
|
+
instanceScores: record.instanceScores,
|
|
886
|
+
...outputs === void 0 ? {} : { outputs }
|
|
887
|
+
});
|
|
832
888
|
}
|
|
833
889
|
/**
|
|
834
890
|
* Copies everything mutable: a snapshot handed to `onCheckpoint` is a record
|
|
@@ -845,6 +901,7 @@ async function runGepa(args) {
|
|
|
845
901
|
metricCalls: budget.spent(),
|
|
846
902
|
reflectionCalls,
|
|
847
903
|
cacheHits: evaluator.cacheHits(),
|
|
904
|
+
usage: evaluator.usage(),
|
|
848
905
|
...samplerState === void 0 ? {} : { sampler: samplerState },
|
|
849
906
|
rejectedProposals: snapshotRejections({
|
|
850
907
|
rejections: rejectedProposals,
|
|
@@ -873,6 +930,7 @@ async function runGepa(args) {
|
|
|
873
930
|
...evaluationCache === void 0 ? {} : { cache: evaluationCache },
|
|
874
931
|
trackOutputs: trackBestOutputs,
|
|
875
932
|
cacheHits: resumeFrom?.cacheHits ?? 0,
|
|
933
|
+
...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
|
|
876
934
|
...signal === void 0 ? {} : { signal },
|
|
877
935
|
onEvaluation: (event) => emit({
|
|
878
936
|
type: "evaluation",
|
|
@@ -992,18 +1050,23 @@ async function runGepa(args) {
|
|
|
992
1050
|
if (records.length === 0) {
|
|
993
1051
|
const seedInstances = selectValInstances(seedCandidate);
|
|
994
1052
|
if (!budget.canAfford(seedInstances.length)) throw new Error(`maxMetricCalls (${maxMetricCalls}) is smaller than the ${seedInstances.length} validation instances selected for scoring; the seed candidate cannot be scored`);
|
|
995
|
-
|
|
1053
|
+
const seedEvaluation = await evaluateValidation({
|
|
1054
|
+
candidate: seedCandidate,
|
|
1055
|
+
instances: seedInstances,
|
|
1056
|
+
phase: "seed",
|
|
1057
|
+
candidateId: 0
|
|
1058
|
+
});
|
|
1059
|
+
emitAccepted(addCandidate({
|
|
996
1060
|
candidate: seedCandidate,
|
|
997
1061
|
parentIds: [],
|
|
998
|
-
evaluation:
|
|
999
|
-
candidate: seedCandidate,
|
|
1000
|
-
instances: seedInstances,
|
|
1001
|
-
phase: "seed",
|
|
1002
|
-
candidateId: 0
|
|
1003
|
-
}),
|
|
1062
|
+
evaluation: seedEvaluation,
|
|
1004
1063
|
source: "seed",
|
|
1005
1064
|
updatedComponents: []
|
|
1006
|
-
});
|
|
1065
|
+
}));
|
|
1066
|
+
warnings.push(...seedScoreWarnings({
|
|
1067
|
+
scores: seedEvaluation.scores,
|
|
1068
|
+
perfectScore
|
|
1069
|
+
}));
|
|
1007
1070
|
lastIterationAccepted = false;
|
|
1008
1071
|
mergesDue = 0;
|
|
1009
1072
|
await checkpoint();
|
|
@@ -1094,14 +1157,7 @@ async function runGepa(args) {
|
|
|
1094
1157
|
});
|
|
1095
1158
|
mergesDue -= 1;
|
|
1096
1159
|
totalMergesTested += 1;
|
|
1097
|
-
|
|
1098
|
-
type: "candidateAccepted",
|
|
1099
|
-
iteration,
|
|
1100
|
-
candidateId: record.id,
|
|
1101
|
-
parentIds: record.parentIds,
|
|
1102
|
-
aggregateScore: record.aggregateScore,
|
|
1103
|
-
source: "merge"
|
|
1104
|
-
});
|
|
1160
|
+
emitAccepted(record);
|
|
1105
1161
|
return "attempted";
|
|
1106
1162
|
}
|
|
1107
1163
|
/**
|
|
@@ -1346,21 +1402,13 @@ async function runGepa(args) {
|
|
|
1346
1402
|
stop ??= "budgetExhausted";
|
|
1347
1403
|
break;
|
|
1348
1404
|
}
|
|
1349
|
-
|
|
1405
|
+
emitAccepted(addCandidate({
|
|
1350
1406
|
candidate: item.outcome.child,
|
|
1351
1407
|
parentIds: [item.outcome.plan.parent.id],
|
|
1352
1408
|
evaluation,
|
|
1353
1409
|
source: "mutation",
|
|
1354
1410
|
updatedComponents: componentNames(item.outcome.proposed)
|
|
1355
|
-
});
|
|
1356
|
-
emit({
|
|
1357
|
-
type: "candidateAccepted",
|
|
1358
|
-
iteration,
|
|
1359
|
-
candidateId: record.id,
|
|
1360
|
-
parentIds: record.parentIds,
|
|
1361
|
-
aggregateScore: record.aggregateScore,
|
|
1362
|
-
source: "mutation"
|
|
1363
|
-
});
|
|
1411
|
+
}));
|
|
1364
1412
|
}
|
|
1365
1413
|
return stop;
|
|
1366
1414
|
}
|
|
@@ -1444,7 +1492,7 @@ async function runGepa(args) {
|
|
|
1444
1492
|
}
|
|
1445
1493
|
const bestCandidateId = valEvaluationPolicy.bestCandidate(records);
|
|
1446
1494
|
const best = records[bestCandidateId];
|
|
1447
|
-
const
|
|
1495
|
+
const heldOut = testSet === void 0 ? void 0 : await evaluateCached({
|
|
1448
1496
|
candidate: best.candidate,
|
|
1449
1497
|
batch: testSet,
|
|
1450
1498
|
ids: testIds,
|
|
@@ -1452,13 +1500,18 @@ async function runGepa(args) {
|
|
|
1452
1500
|
phase: "test",
|
|
1453
1501
|
candidateId: bestCandidateId,
|
|
1454
1502
|
charge: false
|
|
1455
|
-
})
|
|
1503
|
+
});
|
|
1504
|
+
const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
|
|
1456
1505
|
emit({
|
|
1457
1506
|
type: "finish",
|
|
1458
1507
|
reason: stopReason,
|
|
1508
|
+
warnings,
|
|
1459
1509
|
bestCandidateId,
|
|
1510
|
+
bestScore: best.aggregateScore,
|
|
1460
1511
|
metricCalls: budget.spent(),
|
|
1461
|
-
...testScore === void 0 ? {} : { testScore }
|
|
1512
|
+
...testScore === void 0 ? {} : { testScore },
|
|
1513
|
+
...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
|
|
1514
|
+
...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
|
|
1462
1515
|
});
|
|
1463
1516
|
const perObjectiveBest = collectPerObjectiveBest(records);
|
|
1464
1517
|
const bestOutputs = outputsByCandidate.get(bestCandidateId);
|
|
@@ -1469,7 +1522,8 @@ async function runGepa(args) {
|
|
|
1469
1522
|
bestCandidateId,
|
|
1470
1523
|
...testScore === void 0 ? {} : {
|
|
1471
1524
|
testScore,
|
|
1472
|
-
testMetricCalls: evaluator.unchargedCalls()
|
|
1525
|
+
testMetricCalls: evaluator.unchargedCalls(),
|
|
1526
|
+
testUsage: evaluator.unchargedUsage()
|
|
1473
1527
|
},
|
|
1474
1528
|
...bestOutputs === void 0 ? {} : { bestOutputs },
|
|
1475
1529
|
candidates: records,
|
|
@@ -1480,6 +1534,7 @@ async function runGepa(args) {
|
|
|
1480
1534
|
reflectionCalls,
|
|
1481
1535
|
cacheHits: evaluator.cacheHits(),
|
|
1482
1536
|
iterations: iteration,
|
|
1537
|
+
warnings,
|
|
1483
1538
|
stopReason,
|
|
1484
1539
|
snapshot: takeSnapshot()
|
|
1485
1540
|
};
|
|
@@ -1492,13 +1547,15 @@ async function runGepa(args) {
|
|
|
1492
1547
|
function assertGepaConfig(config) {
|
|
1493
1548
|
if (config.reflection?.buildPrompt !== void 0 && config.reflection.strategies !== void 0) throw new Error("reflection takes buildPrompt or strategies, not both");
|
|
1494
1549
|
if (config.reflection?.strategies?.length === 0) throw new Error("reflection.strategies must not be empty");
|
|
1495
|
-
const { minibatchSize = DEFAULT_MINIBATCH_SIZE, maxIterations = Number.POSITIVE_INFINITY, perfectScore = 1, rejectedProposalMemory = DEFAULT_REJECTED_PROPOSAL_MEMORY, proposals } = config;
|
|
1550
|
+
const { minibatchSize = DEFAULT_MINIBATCH_SIZE, maxIterations = Number.POSITIVE_INFINITY, acceptance, perfectScore = 1, rejectedProposalMemory = DEFAULT_REJECTED_PROPOSAL_MEMORY, proposals } = config;
|
|
1496
1551
|
const proposalsPerIteration = proposals?.perIteration ?? 1;
|
|
1497
1552
|
const proposalConcurrency = proposals?.concurrency ?? 1;
|
|
1498
1553
|
if (!Number.isInteger(proposalsPerIteration) || proposalsPerIteration < 1) throw new Error(`proposals.perIteration must be a positive integer, received ${proposalsPerIteration}`);
|
|
1499
1554
|
if (!Number.isInteger(proposalConcurrency) || proposalConcurrency < 1) throw new Error(`proposals.concurrency must be a positive integer, received ${proposalConcurrency}`);
|
|
1500
1555
|
keepCount(proposals?.selection ?? "all");
|
|
1501
1556
|
if (!Number.isInteger(minibatchSize) || minibatchSize < 1) throw new Error(`minibatchSize must be a positive integer, received ${minibatchSize}`);
|
|
1557
|
+
const minimumPairs = acceptance?.minimumPairs;
|
|
1558
|
+
if (minimumPairs !== void 0 && minibatchSize < minimumPairs) throw new Error(`this acceptance policy cannot accept anything on fewer than ${minimumPairs} instances, but minibatchSize is ${minibatchSize}; raise minibatchSize or loosen the policy`);
|
|
1502
1559
|
if (!Number.isFinite(perfectScore)) throw new Error(`perfectScore must be a finite number, received ${perfectScore}`);
|
|
1503
1560
|
if (!Number.isInteger(rejectedProposalMemory) || rejectedProposalMemory < 0) throw new Error(`rejectedProposalMemory must be a non-negative integer, received ${rejectedProposalMemory}`);
|
|
1504
1561
|
if (maxIterations !== Number.POSITIVE_INFINITY && (!Number.isInteger(maxIterations) || maxIterations < 0)) throw new Error(`maxIterations must be a non-negative integer or Infinity, received ${maxIterations}`);
|
|
@@ -1617,17 +1674,6 @@ function collectDominatorIds(records) {
|
|
|
1617
1674
|
return [...ids].sort((a, b) => a - b);
|
|
1618
1675
|
}
|
|
1619
1676
|
/**
|
|
1620
|
-
* Names an instance by a hash of its content rather than by the content
|
|
1621
|
-
* itself: the id ends up inside every cache key and inside the checkpoint
|
|
1622
|
-
* fingerprint, and embedding whole examples there costs memory proportional to
|
|
1623
|
-
* the dataset for no benefit. Data that will not serialize falls back to its
|
|
1624
|
-
* position, which is stable for as long as the dataset order is.
|
|
1625
|
-
*/
|
|
1626
|
-
function defaultInstanceId(args) {
|
|
1627
|
-
const hash = stableHash(args.datum);
|
|
1628
|
-
return hash === "" ? String(args.index) : hash;
|
|
1629
|
-
}
|
|
1630
|
-
/**
|
|
1631
1677
|
* The two rollout sets restricted to the instances both of them measured.
|
|
1632
1678
|
*
|
|
1633
1679
|
* Screening is a paired comparison over one minibatch: a transient row is a
|
|
@@ -1649,4 +1695,4 @@ function pairMeasured(args) {
|
|
|
1649
1695
|
};
|
|
1650
1696
|
}
|
|
1651
1697
|
//#endregion
|
|
1652
|
-
export { GepaOptimizer, allComponentsSelector, buildGeneralizePrompt, buildReflectionPrompt, buildRewritePrompt, buildSimplifyPrompt, createDemoProposer, createPipelineAdapter, currentBestSelector, diverseReflectionStrategies, epsilonGreedySelector, fullEvaluationPolicy, improvementAcceptance, lowerBoundEvaluationPolicy, pairedPermutationAcceptance, paretoSelector, roundRobinComponentSelector, subsampledEvaluationPolicy, topKParetoSelector };
|
|
1698
|
+
export { GepaOptimizer, allComponentsSelector, buildGeneralizePrompt, buildReflectionPrompt, buildRewritePrompt, buildSimplifyPrompt, createDemoProposer, createPipelineAdapter, currentBestSelector, diverseReflectionStrategies, epsilonGreedySelector, fullEvaluationPolicy, improvementAcceptance, isCandidateAccepted, isRunFinished, lowerBoundEvaluationPolicy, pairedPermutationAcceptance, paretoSelector, roundRobinComponentSelector, subsampledEvaluationPolicy, topKParetoSelector };
|
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_warnings = require("./warnings-CWRJF-jA.cjs");
|
|
3
3
|
const require_concurrency = require("./concurrency-C-cFzWW2.cjs");
|
|
4
|
-
const require_math = require("./math-
|
|
5
|
-
const require_demos = require("./demos-
|
|
4
|
+
const require_math = require("./math-BhlziRPc.cjs");
|
|
5
|
+
const require_demos = require("./demos-9v5ts7F3.cjs");
|
|
6
6
|
const require_text = require("./text-CK_HB3su.cjs");
|
|
7
7
|
//#region src/compare.ts
|
|
8
|
-
const EXACT_LIMIT =
|
|
8
|
+
const EXACT_LIMIT = 20;
|
|
9
|
+
/** Well above float subtraction noise (~1e-16), well below a real margin. */
|
|
10
|
+
const DEGENERACY_TOLERANCE = 1e-9;
|
|
9
11
|
/**
|
|
10
12
|
* Run several optimizers over the same seeds and report which one actually won.
|
|
11
13
|
*
|
|
@@ -20,7 +22,7 @@ const EXACT_LIMIT = 16;
|
|
|
20
22
|
* seed is constructor config and every optimizer here is deterministic given
|
|
21
23
|
* one — comparing two entrants at a single seed compares two anecdotes. It also
|
|
22
24
|
* leaves the task where the caller builds it, which is the only place the
|
|
23
|
-
* optimizer-specific parts of it (`reflect`, `cache`, `
|
|
25
|
+
* optimizer-specific parts of it (`reflect`, `cache`, `reporters`) are known.
|
|
24
26
|
*/
|
|
25
27
|
async function compare(args) {
|
|
26
28
|
const { entrants, seeds, concurrency = 1 } = args;
|
|
@@ -43,6 +45,8 @@ async function compare(args) {
|
|
|
43
45
|
bestScore: result.bestScore,
|
|
44
46
|
...result.testScore === void 0 ? {} : { testScore: result.testScore },
|
|
45
47
|
metricCalls: result.metricCalls,
|
|
48
|
+
cacheHits: result.cacheHits,
|
|
49
|
+
reflectionCalls: result.reflectionCalls ?? 0,
|
|
46
50
|
costUsd: result.usage.costUsd,
|
|
47
51
|
stopReason: result.stopReason
|
|
48
52
|
};
|
|
@@ -53,17 +57,25 @@ async function compare(args) {
|
|
|
53
57
|
runs: runs.filter((run) => run.entrant === entrant)
|
|
54
58
|
}));
|
|
55
59
|
const winner = summaries.reduce((best, summary) => summary.meanScore > best.meanScore ? summary : best);
|
|
60
|
+
const rawPValues = summaries.map((summary) => summary.entrant === winner.entrant ? void 0 : margin({
|
|
61
|
+
winner: winner.entrant,
|
|
62
|
+
entrant: summary.entrant,
|
|
63
|
+
runs,
|
|
64
|
+
seeds
|
|
65
|
+
}));
|
|
66
|
+
const holmAdjusted = holmAdjustSparse({
|
|
67
|
+
pValues: rawPValues,
|
|
68
|
+
familySize: names.length - 1
|
|
69
|
+
});
|
|
56
70
|
return {
|
|
57
71
|
winner: winner.entrant,
|
|
58
|
-
summaries: summaries.map((summary) =>
|
|
72
|
+
summaries: summaries.map((summary, index) => ({
|
|
59
73
|
...summary,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
66
|
-
}),
|
|
74
|
+
...rawPValues[index] === void 0 ? {} : {
|
|
75
|
+
pValueVsWinner: rawPValues[index],
|
|
76
|
+
pValueVsWinnerHolm: holmAdjusted[index]
|
|
77
|
+
}
|
|
78
|
+
})),
|
|
67
79
|
runs
|
|
68
80
|
};
|
|
69
81
|
}
|
|
@@ -78,13 +90,28 @@ function summarize(args) {
|
|
|
78
90
|
minScore: Math.min(...scores),
|
|
79
91
|
maxScore: Math.max(...scores),
|
|
80
92
|
meanMetricCalls: mean(runs.map((run) => run.metricCalls)),
|
|
81
|
-
meanCostUsd: mean(runs.map((run) => run.costUsd))
|
|
93
|
+
meanCostUsd: mean(runs.map((run) => run.costUsd)),
|
|
94
|
+
meanCacheHits: mean(runs.map((run) => run.cacheHits)),
|
|
95
|
+
meanReflectionCalls: mean(runs.map((run) => run.reflectionCalls)),
|
|
96
|
+
distinctScores: new Set(scores).size
|
|
82
97
|
};
|
|
83
98
|
}
|
|
84
99
|
/**
|
|
85
100
|
* Paired across seeds rather than pooled: the same seed puts both entrants on
|
|
86
101
|
* the same sampling order, so the difference at a seed is a comparison and the
|
|
87
102
|
* spread between seeds is not.
|
|
103
|
+
*
|
|
104
|
+
* Returns `undefined` when every seed produced the exact same nonzero margin.
|
|
105
|
+
* That is not evidence of an n-seed-strong result — it is one realization the
|
|
106
|
+
* seed never varied, and a sign-flip p-value would report a precision from n
|
|
107
|
+
* independent trials that never happened. A margin of exactly zero every seed
|
|
108
|
+
* is not this case: `signFlipPValue` already reports that honestly as 1, no
|
|
109
|
+
* significance claimed either way, which is not a fabricated number.
|
|
110
|
+
*
|
|
111
|
+
* "Exact same" is judged within `DEGENERACY_TOLERANCE`, not `===`: subtracting
|
|
112
|
+
* two scores that are equal in substance can still land a few ULPs apart
|
|
113
|
+
* (0.95 - 0.55 and 0.9 - 0.5 differ at the 16th digit), and treating that as
|
|
114
|
+
* n real trials would be the same fabrication this check exists to prevent.
|
|
88
115
|
*/
|
|
89
116
|
function margin(args) {
|
|
90
117
|
const { winner, entrant, runs, seeds } = args;
|
|
@@ -99,6 +126,10 @@ function margin(args) {
|
|
|
99
126
|
seed
|
|
100
127
|
});
|
|
101
128
|
});
|
|
129
|
+
const [first] = differences;
|
|
130
|
+
if (first !== void 0) {
|
|
131
|
+
if (differences.reduce((widest, difference) => Math.max(widest, Math.abs(difference - first)), 0) < DEGENERACY_TOLERANCE && Math.abs(first) > DEGENERACY_TOLERANCE) return;
|
|
132
|
+
}
|
|
102
133
|
return require_math.signFlipPValue({
|
|
103
134
|
differences,
|
|
104
135
|
observed: differences.reduce((total, value) => total + value, 0),
|
|
@@ -109,6 +140,27 @@ function scoreOf(args) {
|
|
|
109
140
|
const { runs, entrant, seed } = args;
|
|
110
141
|
return runs.find((candidate) => candidate.entrant === entrant && candidate.seed === seed)?.score ?? 0;
|
|
111
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* `holmAdjust` over the raw p-values that exist, skipping the slots a
|
|
145
|
+
* withheld comparison left `undefined` — those still count toward
|
|
146
|
+
* `familySize`, they just have nothing of their own to adjust.
|
|
147
|
+
*/
|
|
148
|
+
function holmAdjustSparse(args) {
|
|
149
|
+
const { pValues, familySize } = args;
|
|
150
|
+
const present = pValues.map((pValue, index) => ({
|
|
151
|
+
pValue,
|
|
152
|
+
index
|
|
153
|
+
})).filter((entry) => entry.pValue !== void 0);
|
|
154
|
+
const adjusted = require_math.holmAdjust({
|
|
155
|
+
pValues: present.map((entry) => entry.pValue),
|
|
156
|
+
familySize
|
|
157
|
+
});
|
|
158
|
+
const result = new Array(pValues.length).fill(void 0);
|
|
159
|
+
present.forEach((entry, rank) => {
|
|
160
|
+
result[entry.index] = adjusted[rank];
|
|
161
|
+
});
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
112
164
|
function mean(values) {
|
|
113
165
|
return values.reduce((total, value) => total + value, 0) / values.length;
|
|
114
166
|
}
|
|
@@ -119,6 +171,51 @@ function standardDeviation(values) {
|
|
|
119
171
|
return Math.sqrt(variance);
|
|
120
172
|
}
|
|
121
173
|
//#endregion
|
|
174
|
+
//#region src/distillation.ts
|
|
175
|
+
const ROLES = /* @__PURE__ */ new Set([
|
|
176
|
+
"system",
|
|
177
|
+
"user",
|
|
178
|
+
"assistant"
|
|
179
|
+
]);
|
|
180
|
+
/**
|
|
181
|
+
* Serialize harvested rollouts as JSONL, one training example per line.
|
|
182
|
+
*
|
|
183
|
+
* The chat-messages shape rather than any one vendor's: it is what Axolotl,
|
|
184
|
+
* Together, Fireworks and the Hugging Face trainers all ingest, and the
|
|
185
|
+
* providers that read it outlive the ones that do not.
|
|
186
|
+
*
|
|
187
|
+
* Returns the text rather than writing it. Only `file-cache` touches the
|
|
188
|
+
* filesystem, and a caller uploading this straight to a provider should not
|
|
189
|
+
* have to round-trip it through a file to do so.
|
|
190
|
+
*/
|
|
191
|
+
function toTrainingJsonl(args) {
|
|
192
|
+
const { rollouts, render } = args;
|
|
193
|
+
const lines = [];
|
|
194
|
+
for (const [index, rollout] of rollouts.entries()) {
|
|
195
|
+
const example = render({
|
|
196
|
+
rollout,
|
|
197
|
+
index
|
|
198
|
+
});
|
|
199
|
+
if (example === null) continue;
|
|
200
|
+
assertUsable(example, index);
|
|
201
|
+
lines.push(JSON.stringify(example));
|
|
202
|
+
}
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Checked here rather than left to the provider. A malformed line surfaces as
|
|
207
|
+
* a rejected upload hours later, naming a line number in a file the caller
|
|
208
|
+
* never wrote by hand; naming the rollout at the point it was rendered is the
|
|
209
|
+
* same error while it is still fixable.
|
|
210
|
+
*/
|
|
211
|
+
function assertUsable(example, index) {
|
|
212
|
+
if (!Array.isArray(example.messages) || example.messages.length === 0) throw new Error(`rollout ${index} rendered no messages`);
|
|
213
|
+
for (const message of example.messages) {
|
|
214
|
+
if (!ROLES.has(message.role)) throw new Error(`rollout ${index} rendered the unknown role ${message.role}`);
|
|
215
|
+
if (typeof message.content !== "string") throw new Error(`rollout ${index} rendered non-text content`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
122
219
|
//#region src/judge.ts
|
|
123
220
|
const DEFAULT_SCALE = 5;
|
|
124
221
|
const SCORE = /<score\s+name="([^"]+)"\s*>\s*([\d.]+)\s*<\/score>/g;
|
|
@@ -141,6 +238,11 @@ const FEEDBACK = /<feedback>([\s\S]*?)<\/feedback>/;
|
|
|
141
238
|
function createJudge(args) {
|
|
142
239
|
const { model, criteria, scale = DEFAULT_SCALE, renderInput = stringify, renderOutput = stringify, buildPrompt = buildJudgePrompt } = args;
|
|
143
240
|
if (criteria.length === 0) throw new Error("createJudge requires at least one criterion");
|
|
241
|
+
if (!Number.isFinite(scale) || scale <= 0) throw new Error(`scale must be a positive number, received ${scale}`);
|
|
242
|
+
assertCriteria({
|
|
243
|
+
criteria,
|
|
244
|
+
scale
|
|
245
|
+
});
|
|
144
246
|
return async ({ input, output, expected, signal }) => {
|
|
145
247
|
return readVerdict({
|
|
146
248
|
response: await model({
|
|
@@ -184,6 +286,7 @@ function buildJudgePrompt(args) {
|
|
|
184
286
|
`Grade each criterion from 0 to ${scale}, where ${scale} is a perfect answer.`,
|
|
185
287
|
"",
|
|
186
288
|
"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.",
|
|
289
|
+
...expected === void 0 ? [] : ["", "Do not restate the expected answer, or any fact drawn from it, in the feedback. The instruction it is rewritten into is reused on inputs whose answers you have not seen: a fact copied out of the expected answer becomes an answer key memorised in the prompt, which raises the score on this input and teaches the system nothing. Name the kind of thing the answer was missing, not the thing itself."],
|
|
187
290
|
"",
|
|
188
291
|
"Reply in exactly this format and nothing else:",
|
|
189
292
|
...criteria.map((criterion) => `<score name="${criterion.name}">…</score>`),
|
|
@@ -202,6 +305,9 @@ function readVerdict(args) {
|
|
|
202
305
|
const graded = /* @__PURE__ */ new Map();
|
|
203
306
|
for (const match of response.matchAll(SCORE)) graded.set(match[1], Number(match[2]));
|
|
204
307
|
const objectiveScores = {};
|
|
308
|
+
let gated = false;
|
|
309
|
+
let weighted = 0;
|
|
310
|
+
let totalWeight = 0;
|
|
205
311
|
for (const criterion of criteria) {
|
|
206
312
|
const grade = graded.get(criterion.name);
|
|
207
313
|
if (grade === void 0 || Number.isNaN(grade)) return {
|
|
@@ -209,15 +315,30 @@ function readVerdict(args) {
|
|
|
209
315
|
feedback: `Judge did not grade "${criterion.name}".`,
|
|
210
316
|
transient: true
|
|
211
317
|
};
|
|
212
|
-
|
|
318
|
+
const normalized = clamp(grade / scale);
|
|
319
|
+
objectiveScores[criterion.name] = normalized;
|
|
320
|
+
if (criterion.gate !== void 0 && grade < criterion.gate) gated = true;
|
|
321
|
+
const weight = criterion.weight ?? 1;
|
|
322
|
+
weighted += normalized * weight;
|
|
323
|
+
totalWeight += weight;
|
|
213
324
|
}
|
|
214
|
-
const grades = Object.values(objectiveScores);
|
|
215
325
|
return {
|
|
216
|
-
score:
|
|
326
|
+
score: gated ? 0 : weighted / totalWeight,
|
|
217
327
|
feedback,
|
|
218
328
|
objectiveScores
|
|
219
329
|
};
|
|
220
330
|
}
|
|
331
|
+
function assertCriteria(args) {
|
|
332
|
+
const { criteria, scale } = args;
|
|
333
|
+
let totalWeight = 0;
|
|
334
|
+
for (const { name, weight = 1, gate } of criteria) {
|
|
335
|
+
if (!Number.isFinite(weight) || weight < 0) throw new Error(`weight on criterion "${name}" must be a non-negative number, received ${weight}`);
|
|
336
|
+
totalWeight += weight;
|
|
337
|
+
if (gate !== void 0 && (!Number.isFinite(gate) || gate <= 0)) throw new Error(`gate on criterion "${name}" must be greater than 0, received ${gate}; no grade can fall below 0`);
|
|
338
|
+
if (gate !== void 0 && gate > scale) throw new Error(`gate on criterion "${name}" is ${gate}, above the scale of ${scale}; no grade can reach it`);
|
|
339
|
+
}
|
|
340
|
+
if (totalWeight <= 0) throw new Error("criteria weights must sum to more than 0; at least one criterion has to count towards the score");
|
|
341
|
+
}
|
|
221
342
|
function stringify(value) {
|
|
222
343
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
223
344
|
}
|
|
@@ -243,24 +364,30 @@ function priceUsage(args) {
|
|
|
243
364
|
};
|
|
244
365
|
}
|
|
245
366
|
//#endregion
|
|
246
|
-
exports.BudgetExhausted =
|
|
247
|
-
exports.assertResumable =
|
|
248
|
-
exports.bootstrapDemos = require_demos.bootstrapDemos;
|
|
367
|
+
exports.BudgetExhausted = require_warnings.BudgetExhausted;
|
|
368
|
+
exports.assertResumable = require_warnings.assertResumable;
|
|
249
369
|
exports.buildJudgePrompt = buildJudgePrompt;
|
|
250
|
-
exports.candidateFingerprint =
|
|
370
|
+
exports.candidateFingerprint = require_warnings.candidateFingerprint;
|
|
251
371
|
exports.compare = compare;
|
|
252
|
-
exports.componentNames =
|
|
253
|
-
exports.costExhausted =
|
|
254
|
-
exports.createBudget =
|
|
255
|
-
exports.createDeadline =
|
|
256
|
-
exports.createEvaluator =
|
|
372
|
+
exports.componentNames = require_warnings.componentNames;
|
|
373
|
+
exports.costExhausted = require_warnings.costExhausted;
|
|
374
|
+
exports.createBudget = require_warnings.createBudget;
|
|
375
|
+
exports.createDeadline = require_warnings.createDeadline;
|
|
376
|
+
exports.createEvaluator = require_warnings.createEvaluator;
|
|
257
377
|
exports.createJudge = createJudge;
|
|
258
|
-
exports.createMemoryCache =
|
|
378
|
+
exports.createMemoryCache = require_warnings.createMemoryCache;
|
|
259
379
|
exports.formatDemos = require_demos.formatDemos;
|
|
380
|
+
exports.harvestFewShotExamples = require_demos.harvestFewShotExamples;
|
|
381
|
+
exports.harvestRollouts = require_demos.harvestRollouts;
|
|
382
|
+
exports.isCandidateAccepted = require_warnings.isCandidateAccepted;
|
|
383
|
+
exports.isRunFinished = require_warnings.isRunFinished;
|
|
260
384
|
exports.mapWithConcurrency = require_concurrency.mapWithConcurrency;
|
|
261
|
-
exports.measuredMean =
|
|
385
|
+
exports.measuredMean = require_warnings.measuredMean;
|
|
262
386
|
exports.parseDemos = require_demos.parseDemos;
|
|
263
387
|
exports.parseProposedText = require_text.parseProposedText;
|
|
264
388
|
exports.priceUsage = priceUsage;
|
|
265
|
-
exports.requireMeasuredMean =
|
|
266
|
-
exports.
|
|
389
|
+
exports.requireMeasuredMean = require_warnings.requireMeasuredMean;
|
|
390
|
+
exports.resolveValidationSet = require_warnings.resolveValidationSet;
|
|
391
|
+
exports.runFingerprint = require_warnings.runFingerprint;
|
|
392
|
+
exports.seedScoreWarnings = require_warnings.seedScoreWarnings;
|
|
393
|
+
exports.toTrainingJsonl = toTrainingJsonl;
|