@hmharness/evolution 0.14.11 → 0.14.13

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.
@@ -64,3 +64,28 @@ export declare function exportDpoPairs(labels: HumanLabel[], insights: InsightLi
64
64
  crossBucketPrefix?: number;
65
65
  sources?: Map<string, 'human' | 'judge'>;
66
66
  }): DpoPair[];
67
+ /** Deterministic ~80/20 train/eval split by pair identity. Pure. */
68
+ export declare function splitDpoPairs(pairs: DpoPair[], salt?: string): {
69
+ train: DpoPair[];
70
+ eval: DpoPair[];
71
+ };
72
+ /** Leakage/quality audit before training consumes the pairs: empty sides,
73
+ * exact duplicates, and eval pairs whose prompt ALSO appears in train
74
+ * (prompt leakage inflates offline metrics). Pure. */
75
+ export declare function auditDpoPairs(train: DpoPair[], evalP: DpoPair[]): {
76
+ emptyPrompt: number;
77
+ emptyAnswer: number;
78
+ duplicatePairs: number;
79
+ evalPromptsLeakedIntoTrain: number;
80
+ };
81
+ /** The versioned manifest written beside the exported pairs file - the
82
+ * dataset's birth certificate: counts by source, split sizes, audit. */
83
+ export interface DpoManifest {
84
+ version: string;
85
+ pairsTotal: number;
86
+ bySource: Record<string, number>;
87
+ train: number;
88
+ eval: number;
89
+ audit: ReturnType<typeof auditDpoPairs>;
90
+ exportedAt: string;
91
+ }
@@ -146,7 +146,13 @@ export function exportDpoPairs(labels, insights, taskOf, answerOf, opts = {}) {
146
146
  for (const b of rows) {
147
147
  if (a.session >= b.session)
148
148
  continue;
149
- const gap = a.y - b.y;
149
+ // round: 3/5 - 1/5 = 0.39999999999999997 in floats, and the epsilon
150
+ // silently ate every 2-step in-bucket pair (★1 vs ★3) - the judge's
151
+ // whole variance band
152
+ // abs: rows iterate in label-file order - the lower score can come first
153
+ // (judge labels do), which made a.y - b.y negative and silently dropped
154
+ // the pair; rounding kills the 3/5-1/5 float epsilon
155
+ const gap = Math.round(Math.abs(a.y - b.y) * 1000) / 1000;
150
156
  if (gap < minGap)
151
157
  continue;
152
158
  const hi = gap > 0 ? a : b;
@@ -185,3 +191,48 @@ export function exportDpoPairs(labels, insights, taskOf, answerOf, opts = {}) {
185
191
  }
186
192
  return pairs;
187
193
  }
194
+ /* ---------------- DPO dataset form (J1 follow-up) ---------------- */
195
+ /** Deterministic zero-dep FNV-1a - the split must not reshuffle between
196
+ * runs: a pair that was in eval yesterday must stay in eval today. */
197
+ function fnv1a(str) {
198
+ let h = 0x811c9dc5;
199
+ for (let i = 0; i < str.length; i++) {
200
+ h ^= str.charCodeAt(i);
201
+ h = (h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
202
+ }
203
+ return h >>> 0;
204
+ }
205
+ /** Deterministic ~80/20 train/eval split by pair identity. Pure. */
206
+ export function splitDpoPairs(pairs, salt = 'dpo') {
207
+ const train = [];
208
+ const evalP = [];
209
+ for (const p of pairs) {
210
+ (fnv1a(salt + '|' + p.prompt + '|' + p.chosenSession + '|' + p.rejectedSession) % 5 === 0 ? evalP : train).push(p);
211
+ }
212
+ return { train, eval: evalP };
213
+ }
214
+ /** Leakage/quality audit before training consumes the pairs: empty sides,
215
+ * exact duplicates, and eval pairs whose prompt ALSO appears in train
216
+ * (prompt leakage inflates offline metrics). Pure. */
217
+ export function auditDpoPairs(train, evalP) {
218
+ let emptyPrompt = 0, emptyAnswer = 0;
219
+ const seen = new Set();
220
+ let duplicatePairs = 0;
221
+ for (const p of [...train, ...evalP]) {
222
+ if (!p.prompt.trim())
223
+ emptyPrompt++;
224
+ if (!p.chosen.trim() || !p.rejected.trim())
225
+ emptyAnswer++;
226
+ const key = p.prompt + '\u0000' + p.chosen + '\u0000' + p.rejected;
227
+ if (seen.has(key))
228
+ duplicatePairs++;
229
+ else
230
+ seen.add(key);
231
+ }
232
+ const trainPrompts = new Set(train.map((p) => p.prompt));
233
+ let evalPromptsLeakedIntoTrain = 0;
234
+ for (const p of evalP)
235
+ if (trainPrompts.has(p.prompt))
236
+ evalPromptsLeakedIntoTrain++;
237
+ return { emptyPrompt, emptyAnswer, duplicatePairs, evalPromptsLeakedIntoTrain };
238
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/evolution",
3
- "version": "0.14.11",
3
+ "version": "0.14.13",
4
4
  "description": "hmharness evolution subsystem: persistent memory, insight capture, skill library, and the bench that gives evolution its fitness signal. First-class kernel citizen, not a plugin.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",