evals-lab 0.13.0 → 0.15.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 +83 -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 +17 -14
- package/lab/demo/pipelines/demo-2.json +17 -14
- package/lab/demo/pipelines/documents.json +101 -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 +408 -160
- package/lab/flows/record.mjs +5 -4
- package/lab/kinds/list.mjs +337 -182
- package/lab/run-evals.js +3 -1
- package/lab/server.py +661 -108
- package/lab/web/dist/assets/gallery-Cy8oycht.js +5 -0
- package/lab/web/dist/assets/gallery-eVJoaK-6.css +1 -0
- package/lab/web/dist/assets/main-B-LGmhPu.css +1 -0
- package/lab/web/dist/assets/main-wqNvXpWp.js +15 -0
- package/lab/web/dist/assets/tokens-CWVzmtid.css +1 -0
- package/lab/web/dist/assets/tokens-mCS6IZGh.js +64 -0
- package/lab/web/dist/gallery.html +4 -4
- package/lab/web/dist/index.html +4 -4
- package/package.json +1 -1
- package/lab/web/dist/assets/gallery-B_-TH0F-.css +0 -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/evals-core.mjs
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
// and that is a measurement.
|
|
34
34
|
|
|
35
35
|
import yaml from "./js-yaml.mjs";
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
import { evaluate, asText, templateProblem, WdlError, } from "./flows/wdl.mjs";
|
|
38
38
|
import { stepsOf as flowStepsOf, actionsByName, readerFor, readsOf, } from "./flows/record.mjs";
|
|
39
39
|
import { flowApi, } from "./flows/flowApi.mjs";
|
|
@@ -41,7 +41,7 @@ import registerMetrics from "./metrics/builtin.mjs";
|
|
|
41
41
|
import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFilters, preambleFilters, CLEANING_MODIFIERS,
|
|
42
42
|
splitItems, tidy, tryExamples, itemRulesProblems, dropBy, rejectBy, matcher,
|
|
43
43
|
FILTER_TYPES, FILTER_SEPARATORS, FILTER_MODIFIER, applyFilter, applyFilterSet, tryFilter, splitSample, filterProblems, filterFromItemRule,
|
|
44
|
-
|
|
44
|
+
filterV4, V3_TEXT_TYPES, } from "./kinds/list.mjs";
|
|
45
45
|
|
|
46
46
|
// ---- The types -------------------------------------------------------------
|
|
47
47
|
// docs/pipeline-model.md as types, and the shapes of the registries every
|
|
@@ -141,16 +141,18 @@ import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFil
|
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
|
|
144
|
-
/** The filter sets a job cleans and gates its reply by (#339, #
|
|
144
|
+
/** The filter sets a job cleans and gates its reply by (#339, #615): each a
|
|
145
145
|
link to a Library filter set (followed latest or pinned) or a private one
|
|
146
|
-
held inline,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
held inline, one list in run order. `format` is where the job's Response
|
|
147
|
+
Format reads the reply, when it reads items: before `filterSets[format]`
|
|
148
|
+
-- the filters above it run on the raw text, those below on each item.
|
|
149
|
+
Absent, a linked set's own `format` marks it (the first that has one),
|
|
150
|
+
or else the format reads after them all. A format that reads text reads
|
|
151
|
+
after them all whatever it says. A job holds one `filters` step. */
|
|
150
152
|
|
|
151
153
|
|
|
152
|
-
|
|
153
154
|
|
|
155
|
+
|
|
154
156
|
|
|
155
157
|
|
|
156
158
|
/** A job's steps: its Content stage, then its Responses (§16). */
|
|
@@ -199,12 +201,23 @@ import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFil
|
|
|
199
201
|
|
|
200
202
|
|
|
201
203
|
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
202
209
|
|
|
203
210
|
|
|
204
211
|
|
|
205
212
|
|
|
206
213
|
|
|
207
214
|
|
|
215
|
+
/** One kept response of a dynamic Source's item (#596). */
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
208
221
|
|
|
209
222
|
|
|
210
223
|
|
|
@@ -347,23 +360,20 @@ import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFil
|
|
|
347
360
|
* at. `filters` are tried in order -- each drop-item narrows the items the
|
|
348
361
|
* next reads, and the first reject-reply that fires gates the answer.
|
|
349
362
|
*/
|
|
350
|
-
/**
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
/** The stage a filter type's scope belongs to: text filters read the raw
|
|
358
|
-
reply; item, list and answer filters read the items it is read into. */
|
|
359
|
-
export const stageOfScope = (scope ) => (scope === "text" ? "text" : "items");
|
|
360
|
-
|
|
361
|
-
/** A filter stage as the page names it: before the reply is formatted, or after. */
|
|
362
|
-
export const FILTER_STAGE_LABEL = { text: "Pre-format", items: "Post-format" };
|
|
363
|
+
/** Whether a job answering as [kind] reads its reply into items, so that
|
|
364
|
+
where its Response Format sits among its filters matters (#615): the
|
|
365
|
+
filters above it run on the raw text, those below on each item. A format
|
|
366
|
+
that reads text reads after every filter. */
|
|
367
|
+
export const formatReadsItems = (kind ) =>
|
|
368
|
+
!!OUTPUT_KINDS[kind ?? ""]?.offers?.includes("items");
|
|
363
369
|
|
|
364
370
|
|
|
365
371
|
|
|
366
372
|
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
367
377
|
|
|
368
378
|
|
|
369
379
|
/** A Library filter set by reference; a run's copy says the version it used:
|
|
@@ -551,9 +561,9 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
551
561
|
|
|
552
562
|
|
|
553
563
|
|
|
564
|
+
|
|
554
565
|
|
|
555
|
-
|
|
556
|
-
|
|
566
|
+
|
|
557
567
|
|
|
558
568
|
|
|
559
569
|
|
|
@@ -586,7 +596,15 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
586
596
|
|
|
587
597
|
|
|
588
598
|
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|
|
589
603
|
|
|
604
|
+
|
|
605
|
+
/** A reply the reply-token limit stopped: the limit the request sent, or
|
|
606
|
+
null where it sent none and the server's own applied. */
|
|
607
|
+
|
|
590
608
|
|
|
591
609
|
/** The transport: a stage's prompt and image in, its reply out. */
|
|
592
610
|
|
|
@@ -607,9 +625,15 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
607
625
|
|
|
608
626
|
|
|
609
627
|
|
|
610
|
-
|
|
611
628
|
|
|
629
|
+
|
|
630
|
+
|
|
612
631
|
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
613
637
|
|
|
614
638
|
|
|
615
639
|
/** How much of one text a run keeps -- a reply's body, a grader's prompt or
|
|
@@ -630,17 +654,16 @@ export function kept(text , max = KEPT_CHARS) {
|
|
|
630
654
|
|
|
631
655
|
|
|
632
656
|
|
|
633
|
-
|
|
634
|
-
|
|
657
|
+
|
|
635
658
|
|
|
636
659
|
|
|
637
660
|
|
|
638
661
|
|
|
639
662
|
|
|
640
|
-
/** Which filter discarded a job's reply, or emptied it, and where (#435).
|
|
663
|
+
/** Which filter discarded a job's reply, or emptied it, and where (#435). A
|
|
664
|
+
run kept before #615 also says the stage it ran in, which nothing reads. */
|
|
641
665
|
|
|
642
666
|
|
|
643
|
-
|
|
644
667
|
|
|
645
668
|
|
|
646
669
|
|
|
@@ -1204,10 +1227,14 @@ const platformOf = (src
|
|
|
1204
1227
|
|
|
1205
1228
|
|
|
1206
1229
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
|
|
1210
1233
|
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
|
|
1211
1238
|
|
|
1212
1239
|
|
|
1213
1240
|
|
|
@@ -1506,24 +1533,42 @@ function upgradeDatasetBody (body ) {
|
|
|
1506
1533
|
`{ version, filters }` body #334 introduces. A later format adds a step to
|
|
1507
1534
|
`upgradeFilterSetBody`, as `upgradeDatasetBody` grew, with server.py's
|
|
1508
1535
|
`upgrade_filter_set_body` its twin. */
|
|
1509
|
-
const FILTER_SET_VERSION =
|
|
1510
|
-
|
|
1511
|
-
/** [body] as this version of a filter set (
|
|
1512
|
-
(#405) named the stage a set shaped
|
|
1513
|
-
filter's type
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1536
|
+
const FILTER_SET_VERSION = 4 ;
|
|
1537
|
+
|
|
1538
|
+
/** [body] as this version of a filter set (4), from any earlier one. Version 2
|
|
1539
|
+
(#405) named the stage a set shaped, which version 3 (#432) dropped: each
|
|
1540
|
+
filter's type decided where it ran. Version 4 (#615) has seven verbs that
|
|
1541
|
+
run wherever they sit, around a Response Format a set may straddle: each
|
|
1542
|
+
filter becomes the verb that reads a reply as it did (`filterV4`), and a
|
|
1543
|
+
set that held both text and item filters puts its text ones first, in
|
|
1544
|
+
their order, then its `format` marker, then its item ones -- what it ran
|
|
1545
|
+
linked both before and after the format. Anything else comes back
|
|
1546
|
+
untouched for `filterSetBodyProblems` to refuse. Pure: the same body gives
|
|
1547
|
+
the same answer, and server.py's upgrade_filter_set_body is its twin. */
|
|
1517
1548
|
function upgradeFilterSetBody (body ) {
|
|
1518
1549
|
if (!isObj(body)) return body;
|
|
1519
1550
|
if (body.version === FILTER_SET_VERSION) return body;
|
|
1520
|
-
if (body.version === 1 || body.version === 2) {
|
|
1551
|
+
if (body.version === 1 || body.version === 2 || body.version === 3) {
|
|
1521
1552
|
const { stage: _stage, ...rest } = body;
|
|
1522
|
-
return { ...rest, version: FILTER_SET_VERSION } ;
|
|
1553
|
+
if (!Array.isArray(rest.filters)) return { ...rest, version: FILTER_SET_VERSION } ;
|
|
1554
|
+
const isText = (f ) => isObj(f) && V3_TEXT_TYPES.includes(f.type );
|
|
1555
|
+
const text = rest.filters.filter(isText), items = rest.filters.filter((f ) => !isText(f));
|
|
1556
|
+
return { ...rest, version: FILTER_SET_VERSION, filters: [...text, ...items].map(filterV4),
|
|
1557
|
+
...(text.length && items.length ? { format: text.length } : {}) } ;
|
|
1523
1558
|
}
|
|
1524
1559
|
return body;
|
|
1525
1560
|
}
|
|
1526
1561
|
|
|
1562
|
+
/** A version-1 to -3 private filter set (#615) as a version-4 one that runs
|
|
1563
|
+
on the side of the format its link sits: each filter its verb, in its
|
|
1564
|
+
order, and no marker -- a private set held one stage's filters. A current
|
|
1565
|
+
body comes back as it was. */
|
|
1566
|
+
function ownBodyV4(own ) {
|
|
1567
|
+
if (!isObj(own) || ![1, 2, 3].includes(own.version )) return own;
|
|
1568
|
+
const { stage: _stage, ...rest } = own;
|
|
1569
|
+
return { ...rest, version: FILTER_SET_VERSION, ...(Array.isArray(rest.filters) ? { filters: rest.filters.map(filterV4) } : {}) };
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1527
1572
|
/** A filter set with no filters, at this version: what New filter set starts
|
|
1528
1573
|
from, and what a lab with none holds. */
|
|
1529
1574
|
function blankFilterSetBody() {
|
|
@@ -1538,7 +1583,16 @@ function filterSetBodyProblems(body , at = "the filter set") {
|
|
|
1538
1583
|
if (body.version !== FILTER_SET_VERSION) {
|
|
1539
1584
|
return [`${at} is version ${JSON.stringify(body.version)}, and the lab reads version ${FILTER_SET_VERSION}`];
|
|
1540
1585
|
}
|
|
1541
|
-
return filterProblems(body.filters, at)
|
|
1586
|
+
return [...filterProblems(body.filters, at),
|
|
1587
|
+
...formatProblems(body.format, Array.isArray(body.filters) ? body.filters.length : 0, at)];
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/** Why [format] is not a place among [n] entries the Response Format can
|
|
1591
|
+
read at (#615): a whole number from 0 to [n], or none. */
|
|
1592
|
+
function formatProblems(format , n , at ) {
|
|
1593
|
+
if (format == null) return [];
|
|
1594
|
+
return Number.isInteger(format) && (format ) >= 0 && (format ) <= n
|
|
1595
|
+
? [] : [`${at}: Response Format has to sit among its filters`];
|
|
1542
1596
|
}
|
|
1543
1597
|
|
|
1544
1598
|
/** Why [link] is not a filter-set link a run's Responses step may carry, one
|
|
@@ -1697,6 +1751,10 @@ function datasetRules(body ) {
|
|
|
1697
1751
|
|
|
1698
1752
|
|
|
1699
1753
|
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
|
|
1700
1758
|
|
|
1701
1759
|
|
|
1702
1760
|
|
|
@@ -2106,6 +2164,7 @@ function ollamaType(id , label , cloud ) {
|
|
|
2106
2164
|
const r = j ;
|
|
2107
2165
|
return { raw: r?.message?.content ?? "", finishReason: r?.done_reason ?? null };
|
|
2108
2166
|
},
|
|
2167
|
+
cutOff: { reason: "length", limit: (body) => asNumber((body.options )?.num_predict) },
|
|
2109
2168
|
listModels: (base, key) => listModels(base, cloud ? key : null),
|
|
2110
2169
|
parseModels: idsFrom,
|
|
2111
2170
|
...(cloud ? { url: "https://ollama.com" } : { keyless: true }),
|
|
@@ -2220,6 +2279,7 @@ CONNECTION_TYPES["openai-compatible"] = {
|
|
|
2220
2279
|
documents: true,
|
|
2221
2280
|
structured: chatSchema,
|
|
2222
2281
|
parseReply: chatReply,
|
|
2282
|
+
cutOff: { reason: "length", limit: (body) => asNumber(body.max_completion_tokens ?? body.max_tokens) },
|
|
2223
2283
|
listModels, parseModels: idsFrom,
|
|
2224
2284
|
};
|
|
2225
2285
|
|
|
@@ -2271,6 +2331,7 @@ CONNECTION_TYPES.anthropic = {
|
|
|
2271
2331
|
const raw = (r?.content || []).filter(b => b.type === "text").map(b => b.text).join("");
|
|
2272
2332
|
return { raw, finishReason: r?.stop_reason ?? null };
|
|
2273
2333
|
},
|
|
2334
|
+
cutOff: { reason: "max_tokens", limit: (body) => asNumber(body.max_tokens) },
|
|
2274
2335
|
// A flow's request, keyed as the Messages API is -- what the HTTP endpoint
|
|
2275
2336
|
// type's Anthropic template sets. [base] is the /v1 base words hang off;
|
|
2276
2337
|
// the step's path is the whole path production called, so it goes to the
|
|
@@ -2354,6 +2415,7 @@ CONNECTION_TYPES["llama.cpp"] = {
|
|
|
2354
2415
|
// llama-server takes OpenAI's response_format, a schema and all.
|
|
2355
2416
|
structured: chatSchema,
|
|
2356
2417
|
parseReply: chatReply,
|
|
2418
|
+
cutOff: { reason: "length", limit: (body) => asNumber(body.n_predict) },
|
|
2357
2419
|
listModels, parseModels: idsFrom,
|
|
2358
2420
|
// A reply that arrived is still a failure when this profile asked for the
|
|
2359
2421
|
// Speed looping check (today's speedReplyError, now an opt-in setting).
|
|
@@ -2728,6 +2790,18 @@ function connectionReply(conn , j )
|
|
|
2728
2790
|
}
|
|
2729
2791
|
|
|
2730
2792
|
/** Why a reply [conn] received is a failure though it arrived, or null. */
|
|
2793
|
+
/** How a reply finished, as a stage keeps it (#597): the reason [conn]'s
|
|
2794
|
+
API gave, and -- where that reason is its type's reply-token limit -- the
|
|
2795
|
+
limit [body], the request sent, carried. A type that says no reason
|
|
2796
|
+
keeps nothing. */
|
|
2797
|
+
function replyFinish(conn , body , finishReason ) {
|
|
2798
|
+
if (!finishReason) return {};
|
|
2799
|
+
const cut = typeEntry(conn).cutOff;
|
|
2800
|
+
if (!cut || finishReason !== cut.reason) return { finish: finishReason };
|
|
2801
|
+
const limit = body && typeof body === "object" ? cut.limit(body ) : null;
|
|
2802
|
+
return { finish: finishReason, stopped: { limit } };
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2731
2805
|
function connectionReplyProblem(conn , raw , finishReason ) {
|
|
2732
2806
|
return typeEntry(conn).replyProblem?.(conn, raw, finishReason) ?? null;
|
|
2733
2807
|
}
|
|
@@ -3253,34 +3327,37 @@ async function runPipeline(stages , dataUrl , cal
|
|
|
3253
3327
|
total += res.ms;
|
|
3254
3328
|
transcript.push({ n: i + 1, kind, conn: res.conn ?? null, withImage, sent: res.sent ?? sent,
|
|
3255
3329
|
got: res.raw ?? "", ms: res.ms, error: res.error, ...(res.said != null ? { said: res.said } : {}),
|
|
3256
|
-
...(res.body != null ? { body: kept(res.body) } : {})
|
|
3330
|
+
...(res.body != null ? { body: kept(res.body) } : {}),
|
|
3331
|
+
...(res.finish ? { finish: res.finish } : {}), ...(res.stopped ? { stopped: res.stopped } : {}) });
|
|
3257
3332
|
if (res.error) return result({ error: named(res.error) });
|
|
3258
3333
|
// A stage that failed is the end of the job: nothing after it is asked.
|
|
3259
3334
|
// Handed nothing, the next stage still answers -- about an empty list --
|
|
3260
3335
|
// and that answer is graded as though the job had worked.
|
|
3261
3336
|
const handsOn = i < stages.length - 1;
|
|
3262
3337
|
const against = res.readAgainst ?? echo;
|
|
3263
|
-
// The
|
|
3264
|
-
// reads it (#405):
|
|
3338
|
+
// The filters above the format shape the raw reply before the kind
|
|
3339
|
+
// reads it (#405, #615): they reshape it, or a Reject ends the job.
|
|
3265
3340
|
const pre = applyFilterSet(st.before || [], { body: res.raw ?? "", items: [], instruction: against }, "text");
|
|
3266
|
-
const by = (filter ,
|
|
3267
|
-
if (pre.reject != null) return result({ error: named(`discarded: ${pre.reject}`), filteredBy: by(pre.reject,
|
|
3341
|
+
const by = (filter , rejected ) => ({ filter, job: i + 1, rejected });
|
|
3342
|
+
if (pre.reject != null) return result({ error: named(`discarded: ${pre.reject}`), filteredBy: by(pre.reject, true) });
|
|
3268
3343
|
if (pre.rewrote) transcript[transcript.length - 1] .filtered = { got: pre.body, by: pre.rewrote };
|
|
3269
3344
|
const read = reader.read(pre.body, { echo: against, parsing: opts.parsing, settings: st.settings });
|
|
3270
3345
|
if (read.error) return result({ error: named(read.error) });
|
|
3271
3346
|
const mod = applyModifiers(mods[i], kind, read.value, { instruction: against, body: read.body ?? res.raw ?? "" });
|
|
3272
3347
|
if (mod.reject) {
|
|
3273
|
-
return result({ error: named(`discarded: ${mod.reject}`), ...(mod.by ? { filteredBy: by(mod.by,
|
|
3348
|
+
return result({ error: named(`discarded: ${mod.reject}`), ...(mod.by ? { filteredBy: by(mod.by, true) } : {}) });
|
|
3274
3349
|
}
|
|
3275
3350
|
// A filter that dropped the last item: what a zero result is explained by.
|
|
3276
|
-
const emptied = mod.by && reader.terms && !reader.terms(mod.value).length ? { filteredBy: by(mod.by,
|
|
3351
|
+
const emptied = mod.by && reader.terms && !reader.terms(mod.value).length ? { filteredBy: by(mod.by, false) } : {};
|
|
3277
3352
|
const nothing = handsOn ? reader.handsOn?.(mod.value) : null;
|
|
3278
3353
|
if (nothing) return result({ error: named(nothing), ...emptied });
|
|
3279
3354
|
const value = mod.value;
|
|
3280
3355
|
held = { kind, value };
|
|
3281
3356
|
const gone = [...(read.dropped || []), ...mod.dropped];
|
|
3282
3357
|
echoed = (read.echoed || 0) + mod.dropped.filter(d => d.by === ECHO).length;
|
|
3283
|
-
|
|
3358
|
+
// {reply} and {stageN} hand on the text as its filters left it -- what
|
|
3359
|
+
// was graded -- never the raw reply a filter cleaned (#557).
|
|
3360
|
+
done.push({ reply: transcript[transcript.length - 1] .filtered?.got ?? res.raw ?? "", rendered: reader.render?.(value) || {} });
|
|
3284
3361
|
if (!handsOn) {
|
|
3285
3362
|
dropped = gone;
|
|
3286
3363
|
return result({ echoed, dropped, ...emptied });
|
|
@@ -3301,8 +3378,8 @@ function readList(reply , out , instruction =
|
|
|
3301
3378
|
const reader = OUTPUT_KINDS[out.kind];
|
|
3302
3379
|
if (!reader) return { items: [], dropped: [], echoed: 0, error: `"${out.kind}" is not an output kind this lab has` };
|
|
3303
3380
|
const { kind, modifiers, before, ...settings } = out;
|
|
3304
|
-
// The
|
|
3305
|
-
//
|
|
3381
|
+
// The filters above the format shape the raw reply before the kind reads
|
|
3382
|
+
// it (#405, #615); a Reject ends the job.
|
|
3306
3383
|
const pre = applyFilterSet(before || [], { body: reply, items: [], instruction }, "text");
|
|
3307
3384
|
if (pre.reject != null) return { items: [], dropped: [], echoed: 0, error: `discarded: ${pre.reject}` };
|
|
3308
3385
|
const read = reader.read(pre.body, { echo: instruction, settings });
|
|
@@ -3380,7 +3457,11 @@ function applyModifiers (list , kind ,
|
|
|
3380
3457
|
// 16: a target names an AI Connection and, optionally, a Connection Profile,
|
|
3381
3458
|
// where it named one Target profile; a run's table is keyed by the pair
|
|
3382
3459
|
// (answerKey) -- the Target profile split in the store as it opens (#445).
|
|
3383
|
-
|
|
3460
|
+
// 17: a request reads by its job's HTTP API; 18: a saved-mode Source content
|
|
3461
|
+
// pins one response (#596).
|
|
3462
|
+
// 19: a job's filters are one list, run in order around its Response Format,
|
|
3463
|
+
// and filters are seven verbs (#615).
|
|
3464
|
+
const PIPELINE_VERSION = 19 ;
|
|
3384
3465
|
|
|
3385
3466
|
// Plain objects, so an entry is added by assignment and a reader never needs
|
|
3386
3467
|
// to know which registered it.
|
|
@@ -3662,9 +3743,10 @@ function splitProfile(p )
|
|
|
3662
3743
|
}
|
|
3663
3744
|
|
|
3664
3745
|
/** [conn] with [profile]'s settings over it: the one set of settings a
|
|
3665
|
-
request is built from, the shape a Target profile had. A profile
|
|
3666
|
-
|
|
3667
|
-
|
|
3746
|
+
request is built from, the shape a Target profile had. A profile -- or
|
|
3747
|
+
(Default)'s settings, which are one without its id and name -- sets no
|
|
3748
|
+
connection field. */
|
|
3749
|
+
function mergeAnswer(conn , profile ) {
|
|
3668
3750
|
const out = { ...conn };
|
|
3669
3751
|
const scoped = connectionScoped(typeOf(conn ));
|
|
3670
3752
|
for (const [k, v] of Object.entries(profile ?? {})) {
|
|
@@ -3681,7 +3763,7 @@ function mergeAnswer(conn , profile
|
|
|
3681
3763
|
|
|
3682
3764
|
|
|
3683
3765
|
/** The key a run's table holds [connection] under, with [profile] -- or with
|
|
3684
|
-
none,
|
|
3766
|
+
none, (Default)'s settings. A grader names its connection bare, its id alone,
|
|
3685
3767
|
so it never meets a target's pair (see `answersOf`). */
|
|
3686
3768
|
const answerKey = (connection , profile ) => `${connection}.${profile ?? ""}`;
|
|
3687
3769
|
|
|
@@ -3716,9 +3798,13 @@ function targetAnswerKey(doc
|
|
|
3716
3798
|
* answer key resolved to the settings it sends -- the profile's connection
|
|
3717
3799
|
* where it names one, else the key's, with the profile's settings over it.
|
|
3718
3800
|
* A grader's bare id resolves as the Target profile it named did: its
|
|
3719
|
-
* connection under the profile split from it.
|
|
3801
|
+
* connection under the profile split from it. One that names no profile, or
|
|
3802
|
+
* a grader with none split beside it, takes [fallback] instead -- (Default)'s
|
|
3803
|
+
* settings, which sit under no named profile, so a profile is still what it
|
|
3804
|
+
* sets (Setup's Used by counts the same, `defaultUses`). Null where any part
|
|
3805
|
+
* is gone.
|
|
3720
3806
|
*/
|
|
3721
|
-
function answersOf(connections , profiles ) {
|
|
3807
|
+
function answersOf(connections , profiles , fallback ) {
|
|
3722
3808
|
const connection = (id ) => connections.find(c => c?.id === id) ?? null;
|
|
3723
3809
|
const profile = (id ) => profiles.find(p => p?.id === id) ?? null;
|
|
3724
3810
|
const split = (id ) => {
|
|
@@ -3731,7 +3817,7 @@ function answersOf(connections , profiles
|
|
|
3731
3817
|
const p = pid ? profile(pid) : null;
|
|
3732
3818
|
if (pid && !p) return null;
|
|
3733
3819
|
const c = connection(isRef(p?.connection) ? p .connection .id : at.connection);
|
|
3734
|
-
return c ? mergeAnswer(c, p) : null;
|
|
3820
|
+
return c ? mergeAnswer(c, p ?? fallback) : null;
|
|
3735
3821
|
};
|
|
3736
3822
|
return { connection, profile, split, resolve };
|
|
3737
3823
|
}
|
|
@@ -3884,15 +3970,16 @@ SOURCE_TYPES.dynamic = {
|
|
|
3884
3970
|
};
|
|
3885
3971
|
|
|
3886
3972
|
// HTTP GET (docs/dynamic-sources.md § 1): the safest pull provider. It fetches
|
|
3887
|
-
// a URL through the relay server-side --
|
|
3888
|
-
//
|
|
3973
|
+
// a URL through the relay server-side -- Get response keeps a response, Live
|
|
3974
|
+
// data fetches it again at submit -- so the page drives only the
|
|
3889
3975
|
// create dialog and the Call labels; the fetch, redaction and token-refusal are
|
|
3890
3976
|
// the server's (its SOURCE_PROVIDERS mirror in server.py).
|
|
3891
3977
|
DYNAMIC_PROVIDERS["http-get"] = { // provider: the HTTP GET adapter's own id
|
|
3892
3978
|
id: "http-get", // provider: its id, the row's config.provider
|
|
3893
3979
|
label: "HTTP GET",
|
|
3894
3980
|
icon: "square-code",
|
|
3895
|
-
noun: "
|
|
3981
|
+
noun: "response",
|
|
3982
|
+
produceIcon: "cloud-sync",
|
|
3896
3983
|
tier: "pull",
|
|
3897
3984
|
config: ["url"],
|
|
3898
3985
|
configProblems(config){
|
|
@@ -3921,7 +4008,7 @@ DYNAMIC_PROVIDERS["api"] = { // provider: the API call adapter's own id
|
|
|
3921
4008
|
id: "api", // provider: its id, the row's config.provider
|
|
3922
4009
|
label: "API call",
|
|
3923
4010
|
icon: "arrow-left-right",
|
|
3924
|
-
noun: "
|
|
4011
|
+
noun: "response",
|
|
3925
4012
|
tier: "pull",
|
|
3926
4013
|
config: ["method", "url", "reqHeaders", "body", "connection"],
|
|
3927
4014
|
configProblems(config){
|
|
@@ -3950,7 +4037,7 @@ DYNAMIC_PROVIDERS["api"] = { // provider: the API call adapter's own id
|
|
|
3950
4037
|
// or an Upload… by hand, formalised as a provider. The lab fetches and executes
|
|
3951
4038
|
// nothing: a Supplied item is named by the id the caller gives it (its file
|
|
3952
4039
|
// name), which is what joins it to a case. It has no config and no server-side
|
|
3953
|
-
// production, so no
|
|
4040
|
+
// production, so no Get response and no live data; its Calls are the items handed in,
|
|
3954
4041
|
// redacted on the way in like any other (server.py's take_dynamic). A hosted run
|
|
3955
4042
|
// grades those Calls; a solely-Supplied Source with none is run only via the CLI.
|
|
3956
4043
|
DYNAMIC_PROVIDERS["supplied"] = { // provider: the Supplied adapter's own id
|
|
@@ -3980,6 +4067,7 @@ DYNAMIC_PROVIDERS["file"] = { // provider: the File on disk adapter's own id
|
|
|
3980
4067
|
label: "File on disk",
|
|
3981
4068
|
icon: "file",
|
|
3982
4069
|
noun: "file",
|
|
4070
|
+
produceIcon: "file-input",
|
|
3983
4071
|
tier: "local",
|
|
3984
4072
|
config: ["path"],
|
|
3985
4073
|
configProblems(config){
|
|
@@ -4003,6 +4091,7 @@ DYNAMIC_PROVIDERS["command"] = { // provider: the Command adapter's own id
|
|
|
4003
4091
|
label: "Command",
|
|
4004
4092
|
icon: "terminal",
|
|
4005
4093
|
noun: "call",
|
|
4094
|
+
produceIcon: "square-terminal",
|
|
4006
4095
|
tier: "local",
|
|
4007
4096
|
config: ["command", "args"],
|
|
4008
4097
|
configProblems(config){
|
|
@@ -4085,11 +4174,14 @@ CONTENT_TYPES.source = {
|
|
|
4085
4174
|
description: "Every file or record in a Source from the Library.",
|
|
4086
4175
|
// `live` is a dynamic Source's per-run snapshot-vs-live override
|
|
4087
4176
|
// (docs/dynamic-sources.md); meaningless for another kind, ignored there.
|
|
4088
|
-
fields: ["type", "ref", "first", "loops", "live"],
|
|
4177
|
+
fields: ["type", "ref", "first", "loops", "live", "pin"],
|
|
4089
4178
|
runFields: ["files", "revs"],
|
|
4090
4179
|
validate(c, ctx, bad){
|
|
4091
4180
|
if (!isRef(c.ref)) return void bad.push("a Source content has to name its Source as { id, name }");
|
|
4092
4181
|
if (c.live != null && typeof c.live !== "boolean") bad.push("a Source content's live is true or false");
|
|
4182
|
+
if (c.pin != null && !(isObj(c.pin) && isStr(c.pin.name) && c.pin.name && Number.isInteger(c.pin.n) && c.pin.n >= 0)) {
|
|
4183
|
+
bad.push("a Source content's pin names a response as { name, n }");
|
|
4184
|
+
}
|
|
4093
4185
|
if (c.first != null && !(Number.isInteger(c.first) && c.first > 0)) {
|
|
4094
4186
|
bad.push("Run first wants a positive number, or empty for all");
|
|
4095
4187
|
}
|
|
@@ -4269,7 +4361,7 @@ function erroredScore (s , error
|
|
|
4269
4361
|
}
|
|
4270
4362
|
|
|
4271
4363
|
/** The reply a metric grades and Results shows (#546): the last stage's
|
|
4272
|
-
reply after its
|
|
4364
|
+
reply after its filters (#557), or the raw reply. */
|
|
4273
4365
|
function gradedText(res ) {
|
|
4274
4366
|
const last = res?.transcript?.at(-1);
|
|
4275
4367
|
return last?.filtered?.got ?? last?.got ?? res?.raw ?? "";
|
|
@@ -5077,23 +5169,20 @@ STEP_TYPES.modifier = {
|
|
|
5077
5169
|
};
|
|
5078
5170
|
|
|
5079
5171
|
// A filters step: the filter sets a job cleans and gates its reply by, each a
|
|
5080
|
-
// link or a private set (#339
|
|
5081
|
-
//
|
|
5082
|
-
// the type is `many`; two of a stage is the job's to refuse. A set belongs to
|
|
5083
|
-
// no stage (#432): linked here, it runs the filters whose type fits the step's
|
|
5084
|
-
// stage and skips the rest. A private set's body is the core's to judge
|
|
5172
|
+
// link or a private set (#339), in run order around the job's Response Format
|
|
5173
|
+
// (#615, `format`). A private set's body is the core's to judge
|
|
5085
5174
|
// (filterSetBodyProblems); a link to a Library set is the server's to resolve,
|
|
5086
5175
|
// so only its shape is checked here.
|
|
5087
5176
|
STEP_TYPES.filters = {
|
|
5088
|
-
label: "Filter sets", slot: "responses", rank: 2,
|
|
5177
|
+
label: "Filter sets", slot: "responses", rank: 2,
|
|
5089
5178
|
description: "Cleans and gates the reply with linked or private filter sets.",
|
|
5090
5179
|
in: "value", out: "value",
|
|
5091
5180
|
apply: "runPipeline",
|
|
5092
|
-
fields: ["type", "
|
|
5181
|
+
fields: ["type", "filterSets", "format"],
|
|
5093
5182
|
validate(step, _ctx, bad, at){
|
|
5094
|
-
if (step.stage !== "text" && step.stage !== "items") bad.push(`${at}: a filters step shapes the text or the items stage`);
|
|
5095
5183
|
if (!Array.isArray(step.filterSets)) return void bad.push(`${at}: filterSets has to be a list`);
|
|
5096
5184
|
for (const link of step.filterSets ) bad.push(...filterSetLinkProblems(link, at));
|
|
5185
|
+
bad.push(...formatProblems(step.format, step.filterSets.length, at));
|
|
5097
5186
|
},
|
|
5098
5187
|
};
|
|
5099
5188
|
|
|
@@ -5147,6 +5236,22 @@ function withContent (doc , content )
|
|
|
5147
5236
|
return { ...doc, jobs: [withStep(first, "attachContent", content ? { type: "attachContent", content } : null), ...rest] };
|
|
5148
5237
|
}
|
|
5149
5238
|
|
|
5239
|
+
/**
|
|
5240
|
+
* Whether target [i]'s words in job [k] have the item's text joined after
|
|
5241
|
+
* them (#597): job 1, a target that asks a prompt, text content -- a Source
|
|
5242
|
+
* of text items, or inline text, as its CONTENT_TYPES entry says -- and no
|
|
5243
|
+
* {text} in the words to place it (textPrompt). A later job's reply goes
|
|
5244
|
+
* only where a token places it, and an image or a document is sent as a part
|
|
5245
|
+
* of its own, so neither is joined. [ctx] reads a Source as validation does.
|
|
5246
|
+
*/
|
|
5247
|
+
function contentJoined(doc , i , k ,
|
|
5248
|
+
ctx = {}) {
|
|
5249
|
+
if (k !== 0 || targetEntryOf(doc, i, k)?.asks !== "prompt") return false;
|
|
5250
|
+
if (String(targetStepOf(doc, i, k)?.prompt ?? "").includes("{text}")) return false;
|
|
5251
|
+
const content = contentOf(doc);
|
|
5252
|
+
return !!content && (CONTENT_TYPES[content.type]?.text?.(content, ctx) ?? null) != null;
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5150
5255
|
/** A document's targets, in column order. */
|
|
5151
5256
|
function targetsOf(doc ) {
|
|
5152
5257
|
return Array.isArray(doc?.targets) ? doc .targets : [];
|
|
@@ -5308,70 +5413,112 @@ function withOut(job , out ) {
|
|
|
5308
5413
|
}
|
|
5309
5414
|
|
|
5310
5415
|
/** [job] reading a reply as `legacyTagsOut` builds it, on the current model
|
|
5311
|
-
(#405): its output a List,
|
|
5312
|
-
the
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
directly. */
|
|
5416
|
+
(#405, #615): its output a List, and one filters list -- a private set
|
|
5417
|
+
above the format that reads the reply as the old `preamble` did, then the
|
|
5418
|
+
format, then a private set that cleans and gates its items. The filters
|
|
5419
|
+
are `legacyTagsOut`'s -- what the upgrade builds for a legacy list job, for
|
|
5420
|
+
a test to build directly. */
|
|
5316
5421
|
function legacyTagsJob(job , rules , mode = "LOWER") {
|
|
5317
5422
|
const legacy = legacyTagsOut(rules, mode);
|
|
5318
5423
|
const items = ((legacy.modifiers[0]?.filters ) || []);
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
}
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
const
|
|
5342
|
-
return
|
|
5343
|
-
}
|
|
5344
|
-
|
|
5345
|
-
/**
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5424
|
+
const j = withOut(job, { kind: "list", modifiers: [], parse: "csv" });
|
|
5425
|
+
const above = legacy.before.length
|
|
5426
|
+
? [{ id: `filters-${j.id}-before`, set: null, pin: null, own: { version: FILTER_SET_VERSION, filters: legacy.before } }] : [];
|
|
5427
|
+
const below = items.length
|
|
5428
|
+
? [{ id: `filters-${j.id}`, set: null, pin: null, own: { version: FILTER_SET_VERSION, filters: items } }] : [];
|
|
5429
|
+
return withFilterSets(j, [...above, ...below], above.length);
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5432
|
+
/** A job's filters step, when it holds one. */
|
|
5433
|
+
const filterStepOf = (job ) =>
|
|
5434
|
+
(job?.steps || []).find((st) => isObj(st) && st.type === "filters");
|
|
5435
|
+
|
|
5436
|
+
/** The filter-set links a job's Responses carries, in run order (#339,
|
|
5437
|
+
#615): read from its filters step, or none. */
|
|
5438
|
+
function filterSetsOf(job ) {
|
|
5439
|
+
return (filterStepOf(job)?.filterSets ?? []).slice();
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
/** Where job's own Response Format sits among its links (#615): before
|
|
5443
|
+
`filterSetsOf(job)[n]`, or undefined where it holds no place of its own --
|
|
5444
|
+
a straddling set's marker, or the end, is where it reads. */
|
|
5445
|
+
function filterFormatOf(job ) {
|
|
5446
|
+
const at = filterStepOf(job)?.format;
|
|
5447
|
+
return Number.isInteger(at) ? at : undefined;
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
/** [job] with its filter-set links set to [links], and its own Response
|
|
5451
|
+
Format at [format] among them (none: undefined). The filters step is
|
|
5452
|
+
added, replaced, or taken away when no link is left. */
|
|
5453
|
+
function withFilterSets(job , links , format ) {
|
|
5454
|
+
const steps = job.steps.filter(st => st.type !== "filters");
|
|
5455
|
+
const step = { type: "filters", filterSets: [...links], ...(format != null ? { format } : {}) };
|
|
5456
|
+
return { ...job, steps: ordered(links.length ? [...steps, step] : steps) };
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5459
|
+
/** The body a link resolves to, or none: a private (`own`) link its own body,
|
|
5460
|
+
a Library link the body [resolve] gives (the worker's kept bodies). */
|
|
5461
|
+
const linkBody = (link , resolve ) =>
|
|
5462
|
+
link.own ?? (link.set && resolve ? resolve(link.set) ?? null : null);
|
|
5463
|
+
|
|
5464
|
+
/** Where job's Response Format reads among its links (#615), as the index
|
|
5465
|
+
of the link it reads in and the place among that link's filters: its own
|
|
5466
|
+
place (`format`) when it has one, else the first linked set whose body
|
|
5467
|
+
straddles the format (its `format`), else after every link. A format that
|
|
5468
|
+
reads text reads after every link. [resolve] gives a Library link's body. */
|
|
5469
|
+
function formatPlace(job , resolve ) {
|
|
5470
|
+
const links = filterSetsOf(job);
|
|
5471
|
+
if (!formatReadsItems(outOf(job)?.kind)) return { link: links.length, at: 0 };
|
|
5472
|
+
const own = filterFormatOf(job);
|
|
5473
|
+
if (own != null) return { link: Math.min(own, links.length), at: 0 };
|
|
5474
|
+
for (let i = 0; i < links.length; i++) {
|
|
5475
|
+
const at = linkBody(links[i] , resolve)?.format;
|
|
5476
|
+
if (Number.isInteger(at)) return { link: i, at: at };
|
|
5477
|
+
}
|
|
5478
|
+
return { link: links.length, at: 0 };
|
|
5479
|
+
}
|
|
5480
|
+
|
|
5481
|
+
/** A job's filters on either side of its Response Format (#615): `above`,
|
|
5482
|
+
run on the raw reply before it is read, and `below`, each link's share
|
|
5483
|
+
run on the items after it -- one list a link, so a filter that empties a
|
|
5484
|
+
list is named as a link's always was. */
|
|
5485
|
+
function filterSides(job , resolve ) {
|
|
5486
|
+
const links = filterSetsOf(job);
|
|
5487
|
+
const place = formatPlace(job, resolve);
|
|
5488
|
+
const above = [], below = [];
|
|
5489
|
+
links.forEach((link, i) => {
|
|
5490
|
+
const filters = linkBody(link, resolve)?.filters || [];
|
|
5491
|
+
if (i < place.link) above.push(...filters);
|
|
5492
|
+
else if (i > place.link) below.push(filters);
|
|
5493
|
+
else { above.push(...filters.slice(0, place.at)); below.push(filters.slice(place.at)); }
|
|
5494
|
+
});
|
|
5495
|
+
return { above, below };
|
|
5496
|
+
}
|
|
5497
|
+
|
|
5498
|
+
/** A job's Responses below its Response Format as an ordered modifier list
|
|
5499
|
+
for a run (#339, #615): each modifier step as it is, and each link's share
|
|
5500
|
+
below the format as a synthetic modifier that applies its filters to the
|
|
5501
|
+
items where the filters step sits. The filters above the format are not
|
|
5502
|
+
modifiers -- `beforeFilters` gives them, and a run applies them to the raw
|
|
5503
|
+
reply before it is read. [resolve] gives a Library link's body. */
|
|
5357
5504
|
function stageModifiers(job , resolve ) {
|
|
5358
5505
|
const out = [];
|
|
5359
5506
|
for (const st of job?.steps || []) {
|
|
5360
5507
|
if (!isObj(st)) continue;
|
|
5361
5508
|
if (st.type === "modifier") out.push(st.modifier);
|
|
5362
|
-
else if (st.type === "filters"
|
|
5363
|
-
for (const
|
|
5509
|
+
else if (st.type === "filters") {
|
|
5510
|
+
for (const filters of filterSides(job, resolve).below) out.push({ type: FILTER_MODIFIER, filters });
|
|
5364
5511
|
}
|
|
5365
5512
|
}
|
|
5366
5513
|
return out;
|
|
5367
5514
|
}
|
|
5368
5515
|
|
|
5369
|
-
/** A job's
|
|
5370
|
-
|
|
5371
|
-
|
|
5516
|
+
/** A job's filters above its Response Format, for a run (#405, #615), in
|
|
5517
|
+
run order: a run applies them to the raw reply before the kind reads it.
|
|
5518
|
+
Every filter of a job whose format reads text. [resolve] gives a Library
|
|
5372
5519
|
link's body, as `stageModifiers` does. */
|
|
5373
5520
|
function beforeFilters(job , resolve ) {
|
|
5374
|
-
return (
|
|
5521
|
+
return filterSides(job, resolve).above;
|
|
5375
5522
|
}
|
|
5376
5523
|
|
|
5377
5524
|
/** A new id: random, so one minted in one browser never collides with one
|
|
@@ -5417,11 +5564,6 @@ STEP_TYPES.job = {
|
|
|
5417
5564
|
}
|
|
5418
5565
|
const twice = steps.find((st, x) => !STEP_TYPES[st.type] .many && steps.findIndex(o => o.type === st.type) !== x);
|
|
5419
5566
|
if (twice) return void bad.push(`${at} has two ${STEP_TYPES[twice.type] .label ?? twice.type} steps`);
|
|
5420
|
-
// A `filters` step is `many`, but only one per stage: a Pre-format and
|
|
5421
|
-
// a Post-format set at most (#405).
|
|
5422
|
-
const twoFilters = steps.find((st, x) => st.type === "filters"
|
|
5423
|
-
&& steps.findIndex(o => o.type === "filters" && o.stage === st.stage) !== x);
|
|
5424
|
-
if (twoFilters) return void bad.push(`${at} has two ${twoFilters.stage === "text" ? "Pre-format" : "Post-format"} filter steps`);
|
|
5425
5567
|
if (steps.some((st, x) => x > 0 && placeOf(st) < placeOf(steps[x - 1]))) {
|
|
5426
5568
|
return void bad.push(`${at}: steps are its Content, then its Responses, each in order`);
|
|
5427
5569
|
}
|
|
@@ -5533,6 +5675,12 @@ function versionProblem(doc ) {
|
|
|
5533
5675
|
|
|
5534
5676
|
|
|
5535
5677
|
|
|
5678
|
+
|
|
5679
|
+
|
|
5680
|
+
|
|
5681
|
+
|
|
5682
|
+
|
|
5683
|
+
|
|
5536
5684
|
|
|
5537
5685
|
|
|
5538
5686
|
/**
|
|
@@ -5803,9 +5951,9 @@ function groupsOf(next ) {
|
|
|
5803
5951
|
/** [doc] with each private group's body read as this version of a group
|
|
5804
5952
|
body (`upgradeDatasetBody`): a version-8 one's `source` goes (#396); and
|
|
5805
5953
|
each private filter set's as this version of a filter-set body
|
|
5806
|
-
(`
|
|
5807
|
-
carries its own version, so this runs on a current pipeline too;
|
|
5808
|
-
bodies are all current comes back as it was. */
|
|
5954
|
+
(`ownBodyV4`): its filters the verbs that read a reply as they did (#615).
|
|
5955
|
+
A body carries its own version, so this runs on a current pipeline too;
|
|
5956
|
+
one whose bodies are all current comes back as it was. */
|
|
5809
5957
|
function ownBodiesNow(doc ) {
|
|
5810
5958
|
const stale = (t ) => isObj(t) && isObj(t.own) && t.own.version !== DATASET_BODY_VERSION;
|
|
5811
5959
|
const staleSet = (l ) => isObj(l) && isObj(l.own) && l.own.version !== FILTER_SET_VERSION;
|
|
@@ -5817,14 +5965,15 @@ function ownBodiesNow(doc ) {
|
|
|
5817
5965
|
}
|
|
5818
5966
|
if (Array.isArray(out.jobs) && out.jobs.some(staleJob)) {
|
|
5819
5967
|
out = { ...out, jobs: out.jobs.map((j ) => (!staleJob(j) ? j : { ...j, steps: j.steps.map((st ) => (!staleStep(st) ? st
|
|
5820
|
-
: { ...st, filterSets: st.filterSets.map((l ) => (staleSet(l) ? { ...l, own:
|
|
5968
|
+
: { ...st, filterSets: st.filterSets.map((l ) => (staleSet(l) ? { ...l, own: ownBodyV4(l.own) } : l)) })) })) };
|
|
5821
5969
|
}
|
|
5822
5970
|
return out;
|
|
5823
5971
|
}
|
|
5824
5972
|
|
|
5825
5973
|
function upgradePipeline (doc , ctx = {}) {
|
|
5826
5974
|
// A current document is read as it is, but for a profile reference the Runs
|
|
5827
|
-
// tab saved whole (see profileRefs), which is cut back,
|
|
5975
|
+
// tab saved whole (see profileRefs), which is cut back, a Read Reply's
|
|
5976
|
+
// leftover `preamble: false` (withoutLeftoverPreamble), and a step on a
|
|
5828
5977
|
// profile a target step stands in for (localSteps).
|
|
5829
5978
|
if (isObj(doc) && doc.version === PIPELINE_VERSION) {
|
|
5830
5979
|
let out = doc ;
|
|
@@ -5832,12 +5981,20 @@ function upgradePipeline (doc , ctx = {}) {
|
|
|
5832
5981
|
out = clone(out) ;
|
|
5833
5982
|
profileRefs(out);
|
|
5834
5983
|
}
|
|
5835
|
-
return localSteps(ownBodiesNow(out), ctx) ;
|
|
5984
|
+
return localSteps(ownBodiesNow(withoutLeftoverPreamble(out)), ctx) ;
|
|
5836
5985
|
}
|
|
5837
|
-
if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].includes(doc.version )) return doc;
|
|
5986
|
+
if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].includes(doc.version )) return doc;
|
|
5987
|
+
// Every version reads as 18 first, then as 19: a job's filters are one list
|
|
5988
|
+
// around its Response Format (oneFilterList).
|
|
5989
|
+
const nineteen = (d ) => localSteps(ownBodiesNow(oneFilterList(d)), ctx) ;
|
|
5990
|
+
if (doc.version === 18) return nineteen(withoutLeftoverPreamble(cutRefs(clone(doc) )));
|
|
5991
|
+
// Every version reads as 17 first, then as 18: a saved-mode Source content
|
|
5992
|
+
// pins one response (savedPinned).
|
|
5993
|
+
const eighteen = (d ) => nineteen(savedPinned(d, ctx));
|
|
5994
|
+
if (doc.version === 17) return eighteen(cutRefs(clone(doc) ));
|
|
5838
5995
|
// Every version reads as 16 first, then as 17: its requests read by their
|
|
5839
5996
|
// job's HTTP API (requestsOnJobApi).
|
|
5840
|
-
const seventeen = (d ) =>
|
|
5997
|
+
const seventeen = (d ) => eighteen(requestsOnJobApi(d));
|
|
5841
5998
|
if (doc.version === 16) return seventeen(cutRefs(clone(doc) ));
|
|
5842
5999
|
// Every version reads as 15 first, then as 16: its Target profiles split
|
|
5843
6000
|
// into AI Connections and Connection Profiles (answersFromProfiles).
|
|
@@ -5883,11 +6040,13 @@ function filtersFromCleaning(next ) {
|
|
|
5883
6040
|
const isFilters = (st ) => isObj(st) && st.type === "filters";
|
|
5884
6041
|
next.jobs = next.jobs.map((job ) => {
|
|
5885
6042
|
if (!isObj(job) || !Array.isArray(job.steps)) return job;
|
|
5886
|
-
const
|
|
5887
|
-
const readReply = steps.find(st => isObj(st) && st.type === "readReply") ;
|
|
6043
|
+
const readReply = job.steps.find((st ) => isObj(st) && st.type === "readReply") ;
|
|
5888
6044
|
const preamble = !!(readReply && isObj(readReply.out) && readReply.out.preamble === true);
|
|
6045
|
+
// Stripped from every Read Reply, `false` included, before the job is
|
|
6046
|
+
// asked whether it has anything to migrate (#604).
|
|
6047
|
+
const steps = (job.steps ).map(withoutPreamble);
|
|
5889
6048
|
const stale = steps.some(st => isFilters(st) && (st ).stage == null);
|
|
5890
|
-
if (!steps.some(isCleaning) && !preamble && !stale) return job;
|
|
6049
|
+
if (!steps.some(isCleaning) && !preamble && !stale) return { ...job, steps };
|
|
5891
6050
|
const jid = isStr(job.id) ? job.id : "job";
|
|
5892
6051
|
|
|
5893
6052
|
// Walk the Responses steps, turning each into a chunk of items filters
|
|
@@ -5902,25 +6061,22 @@ function filtersFromCleaning(next ) {
|
|
|
5902
6061
|
if (isCleaning(st)) buffer = [...buffer, ...cleaningFilters((st ).modifier )];
|
|
5903
6062
|
else if (isFilters(st)) {
|
|
5904
6063
|
for (const link of ((st ).filterSets ) || []) {
|
|
5905
|
-
if (link.own) buffer = [...buffer, ...(link.own.filters || [])];
|
|
6064
|
+
if (link.own) buffer = [...buffer, ...((ownBodyV4(link.own) ).filters || [])];
|
|
5906
6065
|
else { flush(); links.push(link); }
|
|
5907
6066
|
}
|
|
5908
6067
|
}
|
|
5909
6068
|
}
|
|
5910
6069
|
flush();
|
|
5911
|
-
|
|
5912
|
-
|
|
6070
|
+
// Version 18's two filters steps, a stage each, which oneFilterList reads
|
|
6071
|
+
// on the way to 19.
|
|
6072
|
+
const itemsStep = links.length ? { type: "filters", stage: "items", filterSets: links } : null;
|
|
6073
|
+
const textStep = preamble
|
|
5913
6074
|
? { type: "filters", stage: "text", filterSets: [{ id: `filters-${jid}-before`, set: null, pin: null, own: { version: FILTER_SET_VERSION, filters: preambleFilters() } }] }
|
|
5914
6075
|
: null;
|
|
5915
6076
|
|
|
5916
6077
|
const out = [];
|
|
5917
6078
|
let placed = false;
|
|
5918
6079
|
for (const st of steps) {
|
|
5919
|
-
if (isObj(st) && st.type === "readReply" && isObj(st.out) && "preamble" in st.out) {
|
|
5920
|
-
const { preamble: _p, ...rest } = st.out ;
|
|
5921
|
-
out.push({ ...st, out: rest });
|
|
5922
|
-
continue;
|
|
5923
|
-
}
|
|
5924
6080
|
if (isCleaning(st) || isFilters(st)) {
|
|
5925
6081
|
if (!placed) { if (textStep) out.push(textStep); if (itemsStep) out.push(itemsStep); placed = true; }
|
|
5926
6082
|
continue;
|
|
@@ -5933,6 +6089,72 @@ function filtersFromCleaning(next ) {
|
|
|
5933
6089
|
return next;
|
|
5934
6090
|
}
|
|
5935
6091
|
|
|
6092
|
+
/** Version 18 to 19 (#615): each job's Pre-format and Post-format filters
|
|
6093
|
+
steps become one, in run order -- the Pre-format links, then the
|
|
6094
|
+
Post-format ones -- and where the job's format reads items, its Response
|
|
6095
|
+
Format sits between them (`format`), so every filter runs where it did. A
|
|
6096
|
+
Library set linked on both steps is linked once, between them, and its
|
|
6097
|
+
body (now straddling the format, `upgradeFilterSetBody`) says where the
|
|
6098
|
+
format reads: the job holds no place of its own. A job whose format reads
|
|
6099
|
+
text runs every filter on its text, so it holds no place either. Each
|
|
6100
|
+
private set's body is read as version 4 (`ownBodyV4`). Every version
|
|
6101
|
+
before 19 ends here. */
|
|
6102
|
+
function oneFilterList(next ) {
|
|
6103
|
+
next.version = PIPELINE_VERSION;
|
|
6104
|
+
if (!Array.isArray(next.jobs)) return next;
|
|
6105
|
+
const isFilters = (st ) => isObj(st) && st.type === "filters";
|
|
6106
|
+
next.jobs = next.jobs.map((job ) => {
|
|
6107
|
+
if (!isObj(job) || !Array.isArray(job.steps) || !job.steps.some(isFilters)) return job;
|
|
6108
|
+
const steps = job.steps ;
|
|
6109
|
+
const linksOf = (stage ) => steps.filter(st => isFilters(st) && (st.stage ?? "items") === stage)
|
|
6110
|
+
.flatMap(st => (Array.isArray(st.filterSets) ? st.filterSets : []) )
|
|
6111
|
+
.map(l => (isObj(l) && l.own ? { ...l, own: ownBodyV4(l.own) } : l));
|
|
6112
|
+
const pre = linksOf("text"), post = linksOf("items");
|
|
6113
|
+
const setId = (l ) => (isObj(l) && isObj(l.set) ? l.set.id : null);
|
|
6114
|
+
const both = pre.find(l => setId(l) && post.some(o => setId(o) === setId(l) && o.pin === l.pin));
|
|
6115
|
+
let links , format ;
|
|
6116
|
+
if (!formatReadsItems(outOf(job )?.kind)) { links = [...pre, ...post]; format = undefined; }
|
|
6117
|
+
else if (both) {
|
|
6118
|
+
links = [...pre.filter(l => l !== both), both, ...post.filter(o => !(setId(o) === setId(both) && o.pin === both.pin))];
|
|
6119
|
+
format = undefined;
|
|
6120
|
+
} else { links = [...pre, ...post]; format = post.length ? pre.length : undefined; }
|
|
6121
|
+
const out = [];
|
|
6122
|
+
let placed = false;
|
|
6123
|
+
for (const st of steps) {
|
|
6124
|
+
if (!isFilters(st)) { out.push(st); continue; }
|
|
6125
|
+
if (placed || !links.length) continue;
|
|
6126
|
+
out.push({ type: "filters", filterSets: links, ...(format != null ? { format } : {}) });
|
|
6127
|
+
placed = true;
|
|
6128
|
+
}
|
|
6129
|
+
return { ...job, steps: out };
|
|
6130
|
+
});
|
|
6131
|
+
return next;
|
|
6132
|
+
}
|
|
6133
|
+
|
|
6134
|
+
/** [st] without its List `preamble` setting, when it is a Read Reply holding
|
|
6135
|
+
one; [st] itself otherwise. */
|
|
6136
|
+
function withoutPreamble(st ) {
|
|
6137
|
+
if (!isObj(st) || st.type !== "readReply" || !isObj(st.out) || !("preamble" in st.out)) return st;
|
|
6138
|
+
const { preamble: _p, ...out } = st.out ;
|
|
6139
|
+
return { ...st, out };
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6142
|
+
/** Whether a current document still carries a `preamble: false` that an
|
|
6143
|
+
upgrade before #604 left behind. It did nothing, so it is cut as the
|
|
6144
|
+
document is read; a `preamble: true` the lab never wrote at this version is
|
|
6145
|
+
left for validation to refuse. */
|
|
6146
|
+
function leftoverPreamble(doc ) {
|
|
6147
|
+
return Array.isArray(doc.jobs) && doc.jobs.some((job ) => isObj(job) && Array.isArray(job.steps)
|
|
6148
|
+
&& job.steps.some((st ) => isObj(st) && st.type === "readReply" && isObj(st.out) && st.out.preamble === false));
|
|
6149
|
+
}
|
|
6150
|
+
|
|
6151
|
+
/** [doc] with each Read Reply's leftover `preamble: false` cut (leftoverPreamble). */
|
|
6152
|
+
function withoutLeftoverPreamble(doc ) {
|
|
6153
|
+
if (!leftoverPreamble(doc)) return doc;
|
|
6154
|
+
return { ...doc, jobs: doc.jobs.map((job ) => (!isObj(job) || !Array.isArray(job.steps) ? job
|
|
6155
|
+
: { ...job, steps: job.steps.map((st ) => (isObj(st) && isObj(st.out) && st.out.preamble === false ? withoutPreamble(st) : st)) })) };
|
|
6156
|
+
}
|
|
6157
|
+
|
|
5936
6158
|
/** [doc] with no Metrics eval holding the `case` metric: a dataset's cases
|
|
5937
6159
|
are metrics now (dataset body version 5), which an eval naming the
|
|
5938
6160
|
dataset adds for each item, so the case's own metrics carry what that
|
|
@@ -5986,6 +6208,24 @@ function localSteps(doc , ctx ) {
|
|
|
5986
6208
|
return next;
|
|
5987
6209
|
}
|
|
5988
6210
|
|
|
6211
|
+
/**
|
|
6212
|
+
* Version 17 to 18 (#596): Saved response pins one response. A Source content
|
|
6213
|
+
* graded as saved -- `live` false, or unset over a Source whose default is
|
|
6214
|
+
* saved, which [ctx] says -- pins the Source's newest successful response
|
|
6215
|
+
* (owner), so a Source holding several grades one where it graded all. Live
|
|
6216
|
+
* data, a content that is not a Source's, or one [ctx] cannot say of, pins
|
|
6217
|
+
* none; a run document carries its files, and needs none.
|
|
6218
|
+
*/
|
|
6219
|
+
function savedPinned(next , ctx ) {
|
|
6220
|
+
next.version = PIPELINE_VERSION;
|
|
6221
|
+
const content = contentOf(next ) ;
|
|
6222
|
+
if (!isObj(content) || content.type !== "source" || content.live === true || content.pin != null
|
|
6223
|
+
|| isObj(next.profiles) || !isRef(content.ref) || !ctx.pinFor) return next;
|
|
6224
|
+
const pin = ctx.pinFor(content.ref.id, typeof content.live === "boolean" ? content.live : undefined);
|
|
6225
|
+
if (pin) content.pin = { name: pin.name, n: pin.n };
|
|
6226
|
+
return next;
|
|
6227
|
+
}
|
|
6228
|
+
|
|
5989
6229
|
/**
|
|
5990
6230
|
* Version 16 to 17 (#477): a request step has no HTTP API of its own; its
|
|
5991
6231
|
* job's flow step says it. A step that named another -- Any endpoint over an
|
|
@@ -6439,7 +6679,11 @@ function resolvePipeline(doc ,
|
|
|
6439
6679
|
run.profiles[key] = { ...conn, connection: cid };
|
|
6440
6680
|
}
|
|
6441
6681
|
const content = contentOf(run) ;
|
|
6442
|
-
|
|
6682
|
+
// Saved response grades its one pinned response (#596), whatever else the
|
|
6683
|
+
// Source holds; the server keeps that response's bytes with the run.
|
|
6684
|
+
if (content?.type === "source") {
|
|
6685
|
+
content.files = CONTENT_TYPES.source .expand (content, isObj(content.pin) && content.live !== true ? [content.pin.name] : ctx.files);
|
|
6686
|
+
}
|
|
6443
6687
|
// Each Library group the evals read, as the version it grades with: the
|
|
6444
6688
|
// body's fingerprint and, where the lab numbers them, `n` -- a pinned
|
|
6445
6689
|
// link's pin.
|
|
@@ -6706,7 +6950,8 @@ const BUNDLE_DATASET = /^datasets\/([a-z0-9]+(?:-[a-z0-9]+)*)\.json$/;
|
|
|
6706
6950
|
* [doc] as a bundle's text files: `pipeline.yaml`, `profiles.yaml` and
|
|
6707
6951
|
* `datasets/<slug>.json`. [ctx.connections] and [ctx.connectionProfiles]
|
|
6708
6952
|
* are Setup's -- keys and all, none of which is written -- or
|
|
6709
|
-
* [ctx.profiles], Target profiles to split as the store was
|
|
6953
|
+
* [ctx.profiles], Target profiles to split as the store was, and
|
|
6954
|
+
* [ctx.profileDefault] (Default)'s settings; [ctx.grader]
|
|
6710
6955
|
* the lab's grader, written into each eval that would have asked it, and
|
|
6711
6956
|
* [ctx.datasets] each graded dataset by id, as `{ name, body }`. Each
|
|
6712
6957
|
* connection asked is written resolved, under its answer key with the
|
|
@@ -6715,12 +6960,13 @@ const BUNDLE_DATASET = /^datasets\/([a-z0-9]+(?:-[a-z0-9]+)*)\.json$/;
|
|
|
6715
6960
|
* something else.
|
|
6716
6961
|
*/
|
|
6717
6962
|
function exportBundle(doc , ctx
|
|
6718
|
-
|
|
6719
|
-
|
|
6963
|
+
|
|
6964
|
+
= {})
|
|
6720
6965
|
{
|
|
6721
6966
|
const split = (ctx.profiles ?? []).map(splitProfile);
|
|
6722
6967
|
const profiles = withSlugs([...(ctx.connections ?? []), ...split.map(x => x.connection)] );
|
|
6723
|
-
const answers = answersOf(profiles, [...(ctx.connectionProfiles ?? []), ...split.flatMap(x => (x.profile ? [x.profile] : []))]
|
|
6968
|
+
const answers = answersOf(profiles, [...(ctx.connectionProfiles ?? []), ...split.flatMap(x => (x.profile ? [x.profile] : []))],
|
|
6969
|
+
ctx.profileDefault);
|
|
6724
6970
|
// What the lab would supply at submit, written down: no lab supplies it later.
|
|
6725
6971
|
const out = clone(doc) ;
|
|
6726
6972
|
out.evals = (Array.isArray(out.evals) ? out.evals : [])
|
|
@@ -6890,9 +7136,9 @@ function stagesFor(run , i , resolveFilters
|
|
|
6890
7136
|
{
|
|
6891
7137
|
const stages = run.jobs.map((ch, k) => {
|
|
6892
7138
|
const { kind, modifiers: _mods, ...settings } = outOf(ch);
|
|
6893
|
-
// The modifiers a run applies, with
|
|
6894
|
-
// synthetic modifier where
|
|
6895
|
-
//
|
|
7139
|
+
// The modifiers a run applies, with the filters below the format a
|
|
7140
|
+
// synthetic modifier a link where the filters step sits (#339), and those
|
|
7141
|
+
// above it applied to the raw reply before it is read (#405, #615);
|
|
6896
7142
|
// without a resolver, a Library link reads nothing, a private one its own.
|
|
6897
7143
|
return { text: targetStepOf(run, i, k) .prompt, kind, withImage: sendsImage(ch),
|
|
6898
7144
|
verbatim: !!targetEntryOf(run, i, k)?.verbatim, before: beforeFilters(ch, resolveFilters),
|
|
@@ -7178,8 +7424,10 @@ function validateEvals(ev , files
|
|
|
7178
7424
|
continue;
|
|
7179
7425
|
}
|
|
7180
7426
|
if (scored.some(m => m.type === "refused" && !m.not)) {
|
|
7181
|
-
// Nor does a call that was refused: there is no answer.
|
|
7182
|
-
|
|
7427
|
+
// Nor does a call that was refused: there is no answer. Scored in
|
|
7428
|
+
// points, the other metrics are alternatives rather than more to hold
|
|
7429
|
+
// (#560: refused, or the reply says it can't read the file).
|
|
7430
|
+
if (scored.length > 1 && ev.scoring?.mode !== "weighted") say("expects its call refused, and states something the answer should hold too");
|
|
7183
7431
|
continue;
|
|
7184
7432
|
}
|
|
7185
7433
|
const wants = scored.filter(m => !m.not && (m.type === "contains-all" || m.type === "contains-any"));
|
|
@@ -7304,12 +7552,12 @@ export {
|
|
|
7304
7552
|
SOURCE_TYPES, DEFAULT_SOURCE_TYPE, sourceTypeOf, WORKFLOW_PLATFORMS, platformOf, DYNAMIC_PROVIDERS, providerOf, WIZARDS, wizardProgress,
|
|
7305
7553
|
registerKinds, defaultKind, jobDefaults, applyModifiers, keyVar, slugOf, uniqueSlug, isSlug, withSlugs, SLUG_MAX, connectionOf, addressOf, connectionSettings,
|
|
7306
7554
|
splitProfile, mergeAnswer, answerKey, answerOfKey, answersOf, runAnswer, answerConnectionId, connectionScoped,
|
|
7307
|
-
connectionRequest, connectionBase, connectionSend, connectionReply, connectionReplyProblem, connectionImage, connectionProblems,
|
|
7555
|
+
connectionRequest, connectionBase, connectionSend, connectionReply, connectionReplyProblem, replyFinish, contentJoined, connectionImage, connectionProblems,
|
|
7308
7556
|
CONNECTION_FIELDS, SETTING_KEYS, OPTION_FIELDS, jobLabel, scenarioLabel, jobWords, versionProblem,
|
|
7309
7557
|
contentOf, withContent, replyOf,
|
|
7310
7558
|
targetsOf, targetLetter, targetLabel, targetStepOf, targetEntryOf, jobTargetType, targetAnswerOf, targetAnswerKey, recordedTargetOf, withTarget,
|
|
7311
7559
|
tokensOf, withTokens, sendsImage, withImage, flowStepOf, requestStepOf, readAsOf, outOf, withOut, slotEntry, SLOTS,
|
|
7312
|
-
filterSetsOf, withFilterSets, stageModifiers, beforeFilters, gradedText,
|
|
7560
|
+
filterSetsOf, filterFormatOf, withFilterSets, formatPlace, stageModifiers, beforeFilters, gradedText,
|
|
7313
7561
|
blankPipeline, upgradePipeline, fatProfileRef, newId, newEval, newLink, validatePipeline, resolvePipeline, pipelineOfRun, stagesFor, profileIds, answerKeys,
|
|
7314
7562
|
scenarioProfile, lastKind, lastModifier, modifierSummary, itemScores, scenarioEvals, scenarioPasses, groupPass, overallVerdict, passVerdict, isSkipped, failedScore,
|
|
7315
7563
|
evalLabel, evalsOf, evalsDataset, casesRef, groupOf, groupOfMetrics, pipelineWarnings, itemsFound, previewOf, isWholeRun, EVAL_FIELDS,
|