evals-lab 0.13.0 → 0.14.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/CHANGELOG.md +39 -0
- package/README.md +2 -2
- package/bin/run.js +4 -2
- package/lab/VERSION +1 -1
- package/lab/demo/CREDITS.md +14 -0
- package/lab/demo/datasets/demo-1.json +2 -2
- package/lab/demo/datasets/demo-2.json +2 -2
- package/lab/demo/datasets/documents-type.json +79 -0
- package/lab/demo/datasets/documents-valid.json +117 -0
- package/lab/demo/datasets/sentiment.json +2 -2
- package/lab/demo/datasets/weather-alerts.json +2 -2
- package/lab/demo/datasets/wordings.json +1 -1
- package/lab/demo/manifest.json +3 -3
- package/lab/demo/pipelines/demo-1.json +4 -4
- package/lab/demo/pipelines/demo-2.json +4 -4
- package/lab/demo/pipelines/documents.json +102 -0
- package/lab/demo/pipelines/sentiment.json +4 -4
- package/lab/demo/pipelines/weather-alerts.json +9 -5
- package/lab/demo/pipelines/wordings.json +3 -3
- package/lab/demo/sources/Demo 1/source.json +1 -1
- package/lab/demo/sources/Demo 2/source.json +1 -1
- package/lab/demo/sources/documents/invoice.pdf +65 -0
- package/lab/demo/sources/documents/letter.pdf +63 -0
- package/lab/demo/sources/documents/not-a-pdf.pdf +2 -0
- package/lab/demo/sources/documents/receipt.pdf +62 -0
- package/lab/demo/sources/documents/report.pdf +59 -0
- package/lab/demo/sources/documents/source.json +3 -0
- package/lab/demo/sources/documents/truncated.pdf +19 -0
- package/lab/demo/sources/sentiment/source.json +1 -1
- package/lab/demo/sources/weather-alerts/source.json +1 -1
- package/lab/evals-core.mjs +217 -48
- package/lab/flows/record.mjs +5 -4
- package/lab/kinds/list.mjs +72 -21
- package/lab/run-evals.js +3 -1
- package/lab/server.py +536 -85
- package/lab/web/dist/assets/gallery-qrf0ZGXK.js +5 -0
- package/lab/web/dist/assets/main-D6xARg2A.js +16 -0
- package/lab/web/dist/assets/main-xVU6J7Gi.css +1 -0
- package/lab/web/dist/assets/tokens-C8dqXDHN.js +62 -0
- package/lab/web/dist/assets/tokens-tQ7zTPkd.css +1 -0
- package/lab/web/dist/gallery.html +3 -3
- package/lab/web/dist/index.html +4 -4
- package/package.json +1 -1
- package/lab/web/dist/assets/gallery-DpaqOg9Z.js +0 -5
- package/lab/web/dist/assets/main-BSw-jyYD.css +0 -1
- package/lab/web/dist/assets/main-CrB8wPZn.js +0 -17
- package/lab/web/dist/assets/tokens-CrjDMCbz.js +0 -60
- package/lab/web/dist/assets/tokens-DDOONQon.css +0 -1
package/lab/kinds/list.mjs
CHANGED
|
@@ -304,10 +304,10 @@ export const FILTER_SEPARATORS = ["comma", "lines", "semicolon", "none"]
|
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
311
|
|
|
312
312
|
|
|
313
313
|
|
|
@@ -342,32 +342,81 @@ function rewriteItem(op )
|
|
|
342
342
|
return t => t;
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
/** The function a Rewrite text filter's `op` names: Remove thinking,
|
|
346
|
-
preamble,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
/** The function a Rewrite text filter's `op` names (#557): Remove thinking,
|
|
346
|
+
Remove preamble, Trim, Replace text (literal, every occurrence), Replace
|
|
347
|
+
pattern (a regex, global, and its replacement), Strip code fences, Change
|
|
348
|
+
case, Keep first (N lines or characters), or Extract (a pattern's first
|
|
349
|
+
match, its first group if it has one). It answers null where nothing is
|
|
350
|
+
left to keep -- Extract found no match -- which rejects the reply. An
|
|
351
|
+
unknown op leaves the text as it is; a pattern that will not compile
|
|
352
|
+
throws, so Ignore failures can carry a run past it. */
|
|
353
|
+
function rewriteText(op ) {
|
|
354
|
+
switch (op?.kind) {
|
|
355
|
+
case "thinking": return removeThinking;
|
|
356
|
+
case "preamble": return removePreamble;
|
|
357
|
+
case "trim": return text => text.trim();
|
|
358
|
+
case "literal": {
|
|
359
|
+
const find = String(op.find ?? "");
|
|
360
|
+
const to = typeof op.replacement === "string" ? op.replacement : "";
|
|
361
|
+
return text => (find ? text.split(find).join(to) : text);
|
|
362
|
+
}
|
|
363
|
+
case "replace": {
|
|
364
|
+
const re = new RegExp(String(op.pattern ?? ""), "g");
|
|
365
|
+
const to = typeof op.replacement === "string" ? op.replacement : "";
|
|
366
|
+
return text => text.replace(re, to);
|
|
367
|
+
}
|
|
368
|
+
// A fence's own lines go -- the opening one with its language, and the
|
|
369
|
+
// closing one -- wherever they sit, so a reply fenced whole or a fence
|
|
370
|
+
// after a line of preamble both read as what was inside.
|
|
371
|
+
case "fences": return text => text.replace(FENCE_LINE, "");
|
|
372
|
+
case "case": { const mode = op.mode ; return text => applyCase(text, mode); }
|
|
373
|
+
case "first": {
|
|
374
|
+
const n = op.n ;
|
|
375
|
+
return op.unit === "chars" ? text => text.slice(0, n) : text => text.split("\n").slice(0, n).join("\n");
|
|
376
|
+
}
|
|
377
|
+
case "extract": {
|
|
378
|
+
const re = new RegExp(String(op.pattern ?? ""));
|
|
379
|
+
return text => { const m = re.exec(text); return m ? (m.length > 1 ? m[1] ?? "" : m[0]) : null; };
|
|
380
|
+
}
|
|
381
|
+
default: return text => text;
|
|
356
382
|
}
|
|
357
|
-
return text => text;
|
|
358
383
|
}
|
|
359
384
|
|
|
385
|
+
const FENCE_LINE = /^[ \t]*```[^`\n]*(?:\n|$)/gm;
|
|
386
|
+
|
|
387
|
+
/** Keep first's units, for a Rewrite text: lines, or characters. */
|
|
388
|
+
const TEXT_UNITS = ["lines", "chars"] ;
|
|
389
|
+
|
|
390
|
+
/** Why a Rewrite text op's pattern does not compile, as a sentence, or none. */
|
|
391
|
+
const patternProblems = (pattern , at ) => {
|
|
392
|
+
if (typeof pattern !== "string" || !pattern) return [`${at} needs a pattern`];
|
|
393
|
+
try { new RegExp(pattern); return []; } catch { return [`${at}'s pattern does not compile`]; }
|
|
394
|
+
};
|
|
395
|
+
|
|
360
396
|
/** The lab's base filter types (#405). Generic code asks this registry by
|
|
361
397
|
`filter.type`; it never names an id. `rewrite-text`/`reject-text` read the
|
|
362
|
-
|
|
363
|
-
|
|
398
|
+
reply's text: Pre-format, or Post-format after a kind that reads no items
|
|
399
|
+
(#557); the rest read the items, Post-format after a kind that does. */
|
|
364
400
|
export const FILTER_TYPES = {
|
|
365
401
|
"rewrite-text": {
|
|
366
|
-
label: "Rewrite text", about: "
|
|
402
|
+
label: "Rewrite text", about: "Trim, replace, unfence, recase, shorten or extract from the reply's text.", scope: "text", conditions: [],
|
|
367
403
|
opProblems: (f, at) => opKindProblems(f.op, at, {
|
|
368
|
-
thinking: [], preamble: [], replace: ["pattern", "replacement"],
|
|
404
|
+
thinking: [], preamble: [], trim: [], literal: ["find", "replacement"], replace: ["pattern", "replacement"],
|
|
405
|
+
fences: [], case: ["mode"], first: ["n", "unit"], extract: ["pattern"],
|
|
406
|
+
}, op => {
|
|
407
|
+
if (op.kind === "literal" && (typeof op.find !== "string" || !op.find)) return [`${at} needs text to find`];
|
|
408
|
+
if (op.kind === "extract") return patternProblems(op.pattern, at);
|
|
409
|
+
if (op.kind === "case" && !CASE_MODES.includes(op.mode )) return [`${at}'s case needs a mode`];
|
|
410
|
+
if (op.kind === "first") {
|
|
411
|
+
return [...(Number.isInteger(op.n) && (op.n ) >= 1 ? [] : [`${at} needs a whole number of 1 or more`]),
|
|
412
|
+
...(TEXT_UNITS.includes(op.unit ) ? [] : [`${at} keeps lines or characters`])];
|
|
413
|
+
}
|
|
414
|
+
return [];
|
|
369
415
|
}),
|
|
370
|
-
apply
|
|
416
|
+
apply(filter, reply) {
|
|
417
|
+
const body = rewriteText(filter.op)(reply.body);
|
|
418
|
+
return { body: body ?? reply.body, items: reply.items, dropped: [], reject: body == null ? filter.id : null };
|
|
419
|
+
},
|
|
371
420
|
},
|
|
372
421
|
"reject-text": {
|
|
373
422
|
label: "Reject text", about: "Discard the reply when it is blank or matches a pattern.", scope: "text", conditions: ["blank", "regex"], rejects: true,
|
|
@@ -522,6 +571,8 @@ export function tryFilter(filter )
|
|
|
522
571
|
return { test, ok: rejected === !test.expectedOutput.trim(), got: rejected ? "rejected" : (scope === "text" ? out.body : out.items.join(", ")) };
|
|
523
572
|
}
|
|
524
573
|
if (scope === "text") {
|
|
574
|
+
// A Rewrite text that rejects (Extract, matching nothing) leaves nothing.
|
|
575
|
+
if (out.reject != null) return { test, ok: !test.expectedOutput.trim(), got: "rejected" };
|
|
525
576
|
const got = out.body.trim();
|
|
526
577
|
return { test, ok: got === test.expectedOutput.trim(), got };
|
|
527
578
|
}
|
package/lab/run-evals.js
CHANGED
|
@@ -724,7 +724,9 @@ async function ask(to, c, prompt, dataUrl, schema = null) {
|
|
|
724
724
|
if (!r.ok) return said({ error: j.error?.message || j.error || `HTTP ${r.status}` });
|
|
725
725
|
const { raw, finishReason } = core.connectionReply(c, j);
|
|
726
726
|
const error = core.connectionReplyProblem(c, raw, finishReason);
|
|
727
|
-
|
|
727
|
+
// Why it stopped, and the limit sent where the limit stopped it (#597).
|
|
728
|
+
const finish = core.replyFinish(c, req.body, finishReason);
|
|
729
|
+
return said(error ? { raw, error, ...finish } : { raw, ...finish });
|
|
728
730
|
} catch (e) {
|
|
729
731
|
if (e?.name === "TimeoutError" || e?.name === "AbortError") {
|
|
730
732
|
return said({ error: `the model gave no reply within ${seconds}s — timed out` });
|