evals-lab 0.12.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 +88 -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 +382 -84
- package/lab/flows/record.mjs +5 -4
- package/lab/flows/wdl.mjs +19 -0
- package/lab/kinds/list.mjs +77 -23
- package/lab/metrics/builtin.mjs +34 -7
- package/lab/run-evals.js +33 -12
- package/lab/server.py +668 -108
- 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-CFntjQFG.js +0 -5
- package/lab/web/dist/assets/main-BlTG2i20.css +0 -1
- package/lab/web/dist/assets/main-D6MNWqak.js +0 -17
- package/lab/web/dist/assets/tokens-DwzzdSIF.css +0 -1
- package/lab/web/dist/assets/tokens-bvuNFuYg.js +0 -60
package/lab/evals-core.mjs
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
import yaml from "./js-yaml.mjs";
|
|
36
36
|
|
|
37
|
-
import { evaluate, asText, WdlError, } from "./flows/wdl.mjs";
|
|
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";
|
|
40
40
|
import registerMetrics from "./metrics/builtin.mjs";
|
|
@@ -199,12 +199,23 @@ import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFil
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
202
207
|
|
|
203
208
|
|
|
204
209
|
|
|
205
210
|
|
|
206
211
|
|
|
207
212
|
|
|
213
|
+
/** One kept response of a dynamic Source's item (#596). */
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
208
219
|
|
|
209
220
|
|
|
210
221
|
|
|
@@ -347,17 +358,26 @@ import list, { ECHO, CASE_MODES, legacyTagsOut, legacyTagsModifiers, cleaningFil
|
|
|
347
358
|
* at. `filters` are tried in order -- each drop-item narrows the items the
|
|
348
359
|
* next reads, and the first reject-reply that fires gates the answer.
|
|
349
360
|
*/
|
|
350
|
-
/** Which side of Response Format a filter
|
|
351
|
-
it
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
361
|
+
/** Which side of Response Format a filter step sits on (#405): `text`
|
|
362
|
+
before it, on the raw reply (Pre-format), `items` after it (Post-format).
|
|
363
|
+
It also names what a filter reads: the reply's text (rewrite-text /
|
|
364
|
+
reject-text) or the items (drop-item, rewrite-item, keep-first,
|
|
365
|
+
reject-reply). A text filter runs at either stage, an item filter only
|
|
366
|
+
Post-format after a kind that reads items (#557, `stageReads`); a set
|
|
367
|
+
belongs to no stage (#432), and runs its share in each it is linked in. */
|
|
355
368
|
|
|
356
369
|
|
|
357
370
|
/** The stage a filter type's scope belongs to: text filters read the raw
|
|
358
371
|
reply; item, list and answer filters read the items it is read into. */
|
|
359
372
|
export const stageOfScope = (scope ) => (scope === "text" ? "text" : "items");
|
|
360
373
|
|
|
374
|
+
/** What job's [stage] filters read when it answers as [kind] (#557):
|
|
375
|
+
Pre-format the text; Post-format the items after a kind that reads
|
|
376
|
+
items, and the text after any other. A filter runs there when its
|
|
377
|
+
`stageOfScope` is this. */
|
|
378
|
+
export const stageReads = (stage , kind ) =>
|
|
379
|
+
stage === "items" && OUTPUT_KINDS[kind ?? ""]?.offers?.includes("items") ? "items" : "text";
|
|
380
|
+
|
|
361
381
|
/** A filter stage as the page names it: before the reply is formatted, or after. */
|
|
362
382
|
export const FILTER_STAGE_LABEL = { text: "Pre-format", items: "Post-format" };
|
|
363
383
|
|
|
@@ -555,6 +575,10 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
555
575
|
|
|
556
576
|
|
|
557
577
|
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
558
582
|
|
|
559
583
|
|
|
560
584
|
|
|
@@ -586,7 +610,15 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
586
610
|
|
|
587
611
|
|
|
588
612
|
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
|
|
589
617
|
|
|
618
|
+
|
|
619
|
+
/** A reply the reply-token limit stopped: the limit the request sent, or
|
|
620
|
+
null where it sent none and the server's own applied. */
|
|
621
|
+
|
|
590
622
|
|
|
591
623
|
/** The transport: a stage's prompt and image in, its reply out. */
|
|
592
624
|
|
|
@@ -607,6 +639,15 @@ export const FILTER_STAGE_LABEL = { text: "Pre-form
|
|
|
607
639
|
|
|
608
640
|
|
|
609
641
|
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
|
|
610
651
|
|
|
611
652
|
|
|
612
653
|
/** How much of one text a run keeps -- a reply's body, a grader's prompt or
|
|
@@ -672,6 +713,7 @@ export function kept(text , max = KEPT_CHARS) {
|
|
|
672
713
|
|
|
673
714
|
|
|
674
715
|
|
|
716
|
+
|
|
675
717
|
|
|
676
718
|
|
|
677
719
|
|
|
@@ -897,6 +939,14 @@ export function kept(text , max = KEPT_CHARS) {
|
|
|
897
939
|
|
|
898
940
|
|
|
899
941
|
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
|
|
900
950
|
|
|
901
951
|
|
|
902
952
|
|
|
@@ -1193,10 +1243,14 @@ const platformOf = (src
|
|
|
1193
1243
|
|
|
1194
1244
|
|
|
1195
1245
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
|
|
1199
1249
|
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
|
|
1200
1254
|
|
|
1201
1255
|
|
|
1202
1256
|
|
|
@@ -1686,13 +1740,25 @@ function datasetRules(body ) {
|
|
|
1686
1740
|
|
|
1687
1741
|
|
|
1688
1742
|
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
|
|
1746
|
+
|
|
1689
1747
|
|
|
1690
1748
|
|
|
1691
1749
|
|
|
1692
1750
|
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
|
|
1693
1755
|
|
|
1694
1756
|
|
|
1695
1757
|
|
|
1758
|
+
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
|
|
1696
1762
|
|
|
1697
1763
|
|
|
1698
1764
|
|
|
@@ -1993,6 +2059,35 @@ function asNumber(v ) {
|
|
|
1993
2059
|
// a tuned local engine is a llama.cpp profile with its settings.
|
|
1994
2060
|
const CONNECTION_TYPES = Object.create(null);
|
|
1995
2061
|
|
|
2062
|
+
// ---- documents (#558) ------------------------------------------------------
|
|
2063
|
+
// A file a model's API reads whole, sent unprocessed: the lab does not parse,
|
|
2064
|
+
// render or extract it. It travels where an image does -- the item's
|
|
2065
|
+
// attachment, a data URL -- carrying its name, which some APIs want beside
|
|
2066
|
+
// the bytes. Only a connection type marked `documents` is handed one.
|
|
2067
|
+
const DOCUMENT_TYPES = {
|
|
2068
|
+
".pdf": { media: "application/pdf", label: "PDF" },
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
/** The DOCUMENT_TYPES entry a file's name is, or null. */
|
|
2072
|
+
function documentTypeOf(name ) {
|
|
2073
|
+
const dot = String(name).lastIndexOf(".");
|
|
2074
|
+
return dot < 0 ? null : DOCUMENT_TYPES[String(name).slice(dot).toLowerCase()] ?? null;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/** A document's bytes, base64, as the attachment a stage sends. */
|
|
2078
|
+
function documentUrl(name , media , base64 ) {
|
|
2079
|
+
return `data:${media};name=${encodeURIComponent(name)};base64,${base64}`;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
/** An attachment's media type, bytes and name (null for an image, which
|
|
2083
|
+
carries none), or null for anything that is not a base64 data URL. */
|
|
2084
|
+
function attachmentOf(dataUrl ) {
|
|
2085
|
+
const m = /^data:([^;,]+)((?:;[^;,=]+=[^;,]*)*);base64,(.*)$/s.exec(dataUrl);
|
|
2086
|
+
if (!m) return null;
|
|
2087
|
+
const name = /;name=([^;,]*)/.exec(m[2] )?.[1];
|
|
2088
|
+
return { media: m[1] , data: m[3] , name: name == null ? null : decodeURIComponent(name) };
|
|
2089
|
+
}
|
|
2090
|
+
|
|
1996
2091
|
/** The key [key] as the header most endpoints expect, when there is one. */
|
|
1997
2092
|
const bearer = (key ) => (key ? { Authorization: "Bearer " + key } : {});
|
|
1998
2093
|
|
|
@@ -2058,6 +2153,7 @@ function ollamaType(id , label , cloud ) {
|
|
|
2058
2153
|
const r = j ;
|
|
2059
2154
|
return { raw: r?.message?.content ?? "", finishReason: r?.done_reason ?? null };
|
|
2060
2155
|
},
|
|
2156
|
+
cutOff: { reason: "length", limit: (body) => asNumber((body.options )?.num_predict) },
|
|
2061
2157
|
listModels: (base, key) => listModels(base, cloud ? key : null),
|
|
2062
2158
|
parseModels: idsFrom,
|
|
2063
2159
|
...(cloud ? { url: "https://ollama.com" } : { keyless: true }),
|
|
@@ -2105,6 +2201,13 @@ CONNECTION_TYPES.recorded = {
|
|
|
2105
2201
|
request: sendsNothing, parseReply: sendsNothing, listModels: sendsNothing, parseModels: () => [],
|
|
2106
2202
|
};
|
|
2107
2203
|
|
|
2204
|
+
/** Whether Setup and the connection pickers list a stored connection. A local
|
|
2205
|
+
type's (Echo, Recorded) is a target step's stand-in now (`localSteps`), so
|
|
2206
|
+
a row of one is kept only for the pipelines and runs that name it. */
|
|
2207
|
+
function listedConnection(conn ) {
|
|
2208
|
+
return !CONNECTION_TYPES[typeOf(conn )]?.local;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2108
2211
|
/** A local type's answer to one stage, as a model's would come back. An item
|
|
2109
2212
|
with no text of its own (Prompt only) is answered from the prompt, and
|
|
2110
2213
|
that reply is read against nothing -- the prompt is the reply, not an
|
|
@@ -2120,9 +2223,19 @@ function localAnswer(conn , text , sent , record
|
|
|
2120
2223
|
const chatSchema = (body , schema ) =>
|
|
2121
2224
|
({ ...body, response_format: { type: "json_schema", json_schema: { name: "reply", strict: true, schema } } });
|
|
2122
2225
|
|
|
2226
|
+
/** An item's attachment as a chat completions content part: an image as
|
|
2227
|
+
image_url, a document as a file part, named, its bytes a data URL. */
|
|
2228
|
+
function chatAttachment(dataUrl ) {
|
|
2229
|
+
const a = attachmentOf(dataUrl);
|
|
2230
|
+
if (!a || a.media.startsWith("image/")) return { type: "image_url", image_url: { url: dataUrl } };
|
|
2231
|
+
return { type: "file", file: { filename: a.name ?? "document", file_data: `data:${a.media};base64,${a.data}` } };
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2123
2234
|
CONNECTION_TYPES["openai-compatible"] = {
|
|
2124
2235
|
id: "openai-compatible", label: "OpenAI-compatible",
|
|
2125
2236
|
description: "OpenAI, OpenRouter, vLLM, LM Studio: any chat completions endpoint.",
|
|
2237
|
+
// A hosted provider wants a key; a server of your own may not.
|
|
2238
|
+
keyOptional: (conn) => !isHostedUrl(String(conn.url ?? "")),
|
|
2126
2239
|
settings: [
|
|
2127
2240
|
// A reasoning model refuses a temperature.
|
|
2128
2241
|
{ key: "temperature", label: "Temperature", control: "text", default: "", hint: "",
|
|
@@ -2145,13 +2258,17 @@ CONNECTION_TYPES["openai-compatible"] = {
|
|
|
2145
2258
|
...(predict == null ? {} : hosted ? { max_completion_tokens: predict } : { max_tokens: predict }),
|
|
2146
2259
|
messages: [{ role: "user", content: [
|
|
2147
2260
|
{ type: "text", text: prompt },
|
|
2148
|
-
...(dataUrl ? [
|
|
2261
|
+
...(dataUrl ? [chatAttachment(dataUrl)] : []),
|
|
2149
2262
|
] }],
|
|
2150
2263
|
},
|
|
2151
2264
|
};
|
|
2152
2265
|
},
|
|
2266
|
+
// OpenAI's file part; a compatible server that has none refuses the call,
|
|
2267
|
+
// and the refusal is that item's error.
|
|
2268
|
+
documents: true,
|
|
2153
2269
|
structured: chatSchema,
|
|
2154
2270
|
parseReply: chatReply,
|
|
2271
|
+
cutOff: { reason: "length", limit: (body) => asNumber(body.max_completion_tokens ?? body.max_tokens) },
|
|
2155
2272
|
listModels, parseModels: idsFrom,
|
|
2156
2273
|
};
|
|
2157
2274
|
|
|
@@ -2174,15 +2291,15 @@ CONNECTION_TYPES.anthropic = {
|
|
|
2174
2291
|
answers: ["prompt", "request"],
|
|
2175
2292
|
speaks: () => "anthropic",
|
|
2176
2293
|
// The Messages API: the x-api-key and anthropic-version headers, base64
|
|
2177
|
-
// image content blocks, and /v1/models for the list.
|
|
2294
|
+
// image and document content blocks, and /v1/models for the list.
|
|
2295
|
+
documents: true,
|
|
2178
2296
|
request(conn, prompt, dataUrl, base, key) {
|
|
2179
2297
|
const temp = asNumber(conn.temperature);
|
|
2180
2298
|
const predict = asNumber(conn.maxTokens) ?? ANTHROPIC_MAX_TOKENS;
|
|
2181
2299
|
const content = [];
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
}
|
|
2300
|
+
const a = dataUrl ? attachmentOf(dataUrl) : null;
|
|
2301
|
+
if (a?.media.startsWith("image/")) content.push({ type: "image", source: { type: "base64", media_type: a.media, data: a.data } });
|
|
2302
|
+
else if (a) content.push({ type: "document", source: { type: "base64", media_type: a.media, data: a.data } });
|
|
2186
2303
|
content.push({ type: "text", text: prompt });
|
|
2187
2304
|
return {
|
|
2188
2305
|
url: base + "/messages",
|
|
@@ -2203,6 +2320,7 @@ CONNECTION_TYPES.anthropic = {
|
|
|
2203
2320
|
const raw = (r?.content || []).filter(b => b.type === "text").map(b => b.text).join("");
|
|
2204
2321
|
return { raw, finishReason: r?.stop_reason ?? null };
|
|
2205
2322
|
},
|
|
2323
|
+
cutOff: { reason: "max_tokens", limit: (body) => asNumber(body.max_tokens) },
|
|
2206
2324
|
// A flow's request, keyed as the Messages API is -- what the HTTP endpoint
|
|
2207
2325
|
// type's Anthropic template sets. [base] is the /v1 base words hang off;
|
|
2208
2326
|
// the step's path is the whole path production called, so it goes to the
|
|
@@ -2227,6 +2345,7 @@ const SPEED_IMAGE = "before", SPEED_CACHE = "off", SPEED_THINKING = "off";
|
|
|
2227
2345
|
CONNECTION_TYPES["llama.cpp"] = {
|
|
2228
2346
|
id: "llama.cpp", label: "llama.cpp",
|
|
2229
2347
|
description: "A llama-server you run, with its sampler settings.",
|
|
2348
|
+
keyOptional: () => true,
|
|
2230
2349
|
settings: [
|
|
2231
2350
|
{ key: "temperature", label: "Temperature", control: "text", default: "", hint: "" },
|
|
2232
2351
|
{ key: "seed", label: "Seed", control: "text", default: "", hint: "" },
|
|
@@ -2285,6 +2404,7 @@ CONNECTION_TYPES["llama.cpp"] = {
|
|
|
2285
2404
|
// llama-server takes OpenAI's response_format, a schema and all.
|
|
2286
2405
|
structured: chatSchema,
|
|
2287
2406
|
parseReply: chatReply,
|
|
2407
|
+
cutOff: { reason: "length", limit: (body) => asNumber(body.n_predict) },
|
|
2288
2408
|
listModels, parseModels: idsFrom,
|
|
2289
2409
|
// A reply that arrived is still a failure when this profile asked for the
|
|
2290
2410
|
// Speed looping check (today's speedReplyError, now an opt-in setting).
|
|
@@ -2511,6 +2631,7 @@ CONNECTION_TYPES.http = {
|
|
|
2511
2631
|
...Object.entries(HTTP_APIS).map(([value, a]) => ({ value, label: a.label }))] },
|
|
2512
2632
|
],
|
|
2513
2633
|
answers: ["request"],
|
|
2634
|
+
keyOptional: (conn) => conn.auth === "none",
|
|
2514
2635
|
speaks(conn) {
|
|
2515
2636
|
const api = String(conn.requestFormat || "") || httpApiFor(originBase(conn.url));
|
|
2516
2637
|
return HTTP_APIS[api] && api !== "raw" ? api : null;
|
|
@@ -2658,6 +2779,18 @@ function connectionReply(conn , j )
|
|
|
2658
2779
|
}
|
|
2659
2780
|
|
|
2660
2781
|
/** Why a reply [conn] received is a failure though it arrived, or null. */
|
|
2782
|
+
/** How a reply finished, as a stage keeps it (#597): the reason [conn]'s
|
|
2783
|
+
API gave, and -- where that reason is its type's reply-token limit -- the
|
|
2784
|
+
limit [body], the request sent, carried. A type that says no reason
|
|
2785
|
+
keeps nothing. */
|
|
2786
|
+
function replyFinish(conn , body , finishReason ) {
|
|
2787
|
+
if (!finishReason) return {};
|
|
2788
|
+
const cut = typeEntry(conn).cutOff;
|
|
2789
|
+
if (!cut || finishReason !== cut.reason) return { finish: finishReason };
|
|
2790
|
+
const limit = body && typeof body === "object" ? cut.limit(body ) : null;
|
|
2791
|
+
return { finish: finishReason, stopped: { limit } };
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2661
2794
|
function connectionReplyProblem(conn , raw , finishReason ) {
|
|
2662
2795
|
return typeEntry(conn).replyProblem?.(conn, raw, finishReason) ?? null;
|
|
2663
2796
|
}
|
|
@@ -2797,7 +2930,7 @@ function readCase(kase , res , plain = fa
|
|
|
2797
2930
|
}
|
|
2798
2931
|
return r ? [r ] : [];
|
|
2799
2932
|
});
|
|
2800
|
-
const s = scoreOf(metrics) ?? { pass:
|
|
2933
|
+
const s = erroredScore(scoreOf(metrics), res.error) ?? { pass: !res.error, score: null, metrics };
|
|
2801
2934
|
const counted = metrics.filter(r => r.weight !== 0);
|
|
2802
2935
|
return { ...s, metrics, found: s.found ?? [], missed: s.missed ?? [], invented: s.invented ?? [],
|
|
2803
2936
|
discarded: !!res.error, count: res.error ? 0 : (res.terms || []).length,
|
|
@@ -3183,7 +3316,8 @@ async function runPipeline(stages , dataUrl , cal
|
|
|
3183
3316
|
total += res.ms;
|
|
3184
3317
|
transcript.push({ n: i + 1, kind, conn: res.conn ?? null, withImage, sent: res.sent ?? sent,
|
|
3185
3318
|
got: res.raw ?? "", ms: res.ms, error: res.error, ...(res.said != null ? { said: res.said } : {}),
|
|
3186
|
-
...(res.body != null ? { body: kept(res.body) } : {})
|
|
3319
|
+
...(res.body != null ? { body: kept(res.body) } : {}),
|
|
3320
|
+
...(res.finish ? { finish: res.finish } : {}), ...(res.stopped ? { stopped: res.stopped } : {}) });
|
|
3187
3321
|
if (res.error) return result({ error: named(res.error) });
|
|
3188
3322
|
// A stage that failed is the end of the job: nothing after it is asked.
|
|
3189
3323
|
// Handed nothing, the next stage still answers -- about an empty list --
|
|
@@ -3195,12 +3329,23 @@ async function runPipeline(stages , dataUrl , cal
|
|
|
3195
3329
|
const pre = applyFilterSet(st.before || [], { body: res.raw ?? "", items: [], instruction: against }, "text");
|
|
3196
3330
|
const by = (filter , stage , rejected ) => ({ filter, stage, job: i + 1, rejected });
|
|
3197
3331
|
if (pre.reject != null) return result({ error: named(`discarded: ${pre.reject}`), filteredBy: by(pre.reject, "text", true) });
|
|
3332
|
+
if (pre.rewrote) transcript[transcript.length - 1] .filtered = { got: pre.body, by: pre.rewrote };
|
|
3198
3333
|
const read = reader.read(pre.body, { echo: against, parsing: opts.parsing, settings: st.settings });
|
|
3199
3334
|
if (read.error) return result({ error: named(read.error) });
|
|
3200
3335
|
const mod = applyModifiers(mods[i], kind, read.value, { instruction: against, body: read.body ?? res.raw ?? "" });
|
|
3201
3336
|
if (mod.reject) {
|
|
3202
3337
|
return result({ error: named(`discarded: ${mod.reject}`), ...(mod.by ? { filteredBy: by(mod.by, "items", true) } : {}) });
|
|
3203
3338
|
}
|
|
3339
|
+
// A kind that reads no items runs its Post-format filters on its text,
|
|
3340
|
+
// once read (#557); what they leave is what is graded.
|
|
3341
|
+
if (st.after?.length && typeof mod.value === "string") {
|
|
3342
|
+
const post = applyFilterSet(st.after, { body: mod.value, items: [], instruction: against }, "text");
|
|
3343
|
+
if (post.reject != null) return result({ error: named(`discarded: ${post.reject}`), filteredBy: by(post.reject, "items", true) });
|
|
3344
|
+
if (post.rewrote) {
|
|
3345
|
+
transcript[transcript.length - 1] .filtered = { got: post.body, by: [...(pre.rewrote || []), ...post.rewrote] };
|
|
3346
|
+
mod.value = post.body;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3204
3349
|
// A filter that dropped the last item: what a zero result is explained by.
|
|
3205
3350
|
const emptied = mod.by && reader.terms && !reader.terms(mod.value).length ? { filteredBy: by(mod.by, "items", false) } : {};
|
|
3206
3351
|
const nothing = handsOn ? reader.handsOn?.(mod.value) : null;
|
|
@@ -3209,7 +3354,9 @@ async function runPipeline(stages , dataUrl , cal
|
|
|
3209
3354
|
held = { kind, value };
|
|
3210
3355
|
const gone = [...(read.dropped || []), ...mod.dropped];
|
|
3211
3356
|
echoed = (read.echoed || 0) + mod.dropped.filter(d => d.by === ECHO).length;
|
|
3212
|
-
|
|
3357
|
+
// {reply} and {stageN} hand on the text as its filters left it -- what
|
|
3358
|
+
// was graded -- never the raw reply a filter cleaned (#557).
|
|
3359
|
+
done.push({ reply: transcript[transcript.length - 1] .filtered?.got ?? res.raw ?? "", rendered: reader.render?.(value) || {} });
|
|
3213
3360
|
if (!handsOn) {
|
|
3214
3361
|
dropped = gone;
|
|
3215
3362
|
return result({ echoed, dropped, ...emptied });
|
|
@@ -3309,7 +3456,7 @@ function applyModifiers (list , kind ,
|
|
|
3309
3456
|
// 16: a target names an AI Connection and, optionally, a Connection Profile,
|
|
3310
3457
|
// where it named one Target profile; a run's table is keyed by the pair
|
|
3311
3458
|
// (answerKey) -- the Target profile split in the store as it opens (#445).
|
|
3312
|
-
const PIPELINE_VERSION =
|
|
3459
|
+
const PIPELINE_VERSION = 18 ;
|
|
3313
3460
|
|
|
3314
3461
|
// Plain objects, so an entry is added by assignment and a reader never needs
|
|
3315
3462
|
// to know which registered it.
|
|
@@ -3591,9 +3738,10 @@ function splitProfile(p )
|
|
|
3591
3738
|
}
|
|
3592
3739
|
|
|
3593
3740
|
/** [conn] with [profile]'s settings over it: the one set of settings a
|
|
3594
|
-
request is built from, the shape a Target profile had. A profile
|
|
3595
|
-
|
|
3596
|
-
|
|
3741
|
+
request is built from, the shape a Target profile had. A profile -- or
|
|
3742
|
+
(Default)'s settings, which are one without its id and name -- sets no
|
|
3743
|
+
connection field. */
|
|
3744
|
+
function mergeAnswer(conn , profile ) {
|
|
3597
3745
|
const out = { ...conn };
|
|
3598
3746
|
const scoped = connectionScoped(typeOf(conn ));
|
|
3599
3747
|
for (const [k, v] of Object.entries(profile ?? {})) {
|
|
@@ -3610,7 +3758,7 @@ function mergeAnswer(conn , profile
|
|
|
3610
3758
|
|
|
3611
3759
|
|
|
3612
3760
|
/** The key a run's table holds [connection] under, with [profile] -- or with
|
|
3613
|
-
none,
|
|
3761
|
+
none, (Default)'s settings. A grader names its connection bare, its id alone,
|
|
3614
3762
|
so it never meets a target's pair (see `answersOf`). */
|
|
3615
3763
|
const answerKey = (connection , profile ) => `${connection}.${profile ?? ""}`;
|
|
3616
3764
|
|
|
@@ -3645,9 +3793,13 @@ function targetAnswerKey(doc
|
|
|
3645
3793
|
* answer key resolved to the settings it sends -- the profile's connection
|
|
3646
3794
|
* where it names one, else the key's, with the profile's settings over it.
|
|
3647
3795
|
* A grader's bare id resolves as the Target profile it named did: its
|
|
3648
|
-
* connection under the profile split from it.
|
|
3796
|
+
* connection under the profile split from it. One that names no profile, or
|
|
3797
|
+
* a grader with none split beside it, takes [fallback] instead -- (Default)'s
|
|
3798
|
+
* settings, which sit under no named profile, so a profile is still what it
|
|
3799
|
+
* sets (Setup's Used by counts the same, `defaultUses`). Null where any part
|
|
3800
|
+
* is gone.
|
|
3649
3801
|
*/
|
|
3650
|
-
function answersOf(connections , profiles ) {
|
|
3802
|
+
function answersOf(connections , profiles , fallback ) {
|
|
3651
3803
|
const connection = (id ) => connections.find(c => c?.id === id) ?? null;
|
|
3652
3804
|
const profile = (id ) => profiles.find(p => p?.id === id) ?? null;
|
|
3653
3805
|
const split = (id ) => {
|
|
@@ -3660,7 +3812,7 @@ function answersOf(connections , profiles
|
|
|
3660
3812
|
const p = pid ? profile(pid) : null;
|
|
3661
3813
|
if (pid && !p) return null;
|
|
3662
3814
|
const c = connection(isRef(p?.connection) ? p .connection .id : at.connection);
|
|
3663
|
-
return c ? mergeAnswer(c, p) : null;
|
|
3815
|
+
return c ? mergeAnswer(c, p ?? fallback) : null;
|
|
3664
3816
|
};
|
|
3665
3817
|
return { connection, profile, split, resolve };
|
|
3666
3818
|
}
|
|
@@ -3731,16 +3883,17 @@ function onlyFields(obj , at , allowed , bad
|
|
|
3731
3883
|
|
|
3732
3884
|
// A Source is referenced, with the two numbers that decide how many items a
|
|
3733
3885
|
// run over it is: the first N files, and how many times over.
|
|
3734
|
-
// A folder of files: images sent as images,
|
|
3735
|
-
// extensions are server.py's FILE_TYPES, which the
|
|
3886
|
+
// A folder of files: images sent as images, documents whole (DOCUMENT_TYPES),
|
|
3887
|
+
// text files as {text}. The extensions are server.py's FILE_TYPES, which the
|
|
3888
|
+
// server holds uploads to.
|
|
3736
3889
|
const FILE_LIBRARY_TEXT = [".txt", ".md", ".csv"] ;
|
|
3737
3890
|
SOURCE_TYPES.files = {
|
|
3738
3891
|
label: "File Library",
|
|
3739
3892
|
icon: "folder",
|
|
3740
|
-
description: "A folder of images and text files you upload.",
|
|
3893
|
+
description: "A folder of images, PDFs and text files you upload.",
|
|
3741
3894
|
group: "Files",
|
|
3742
3895
|
noun: "file",
|
|
3743
|
-
uploads: [".jpg", ".jpeg", ".png", ".heic", ".heif", ".dng", ".tif", ".tiff", ...FILE_LIBRARY_TEXT],
|
|
3896
|
+
uploads: [".jpg", ".jpeg", ".png", ".heic", ".heif", ".dng", ".tif", ".tiff", ...Object.keys(DOCUMENT_TYPES), ...FILE_LIBRARY_TEXT],
|
|
3744
3897
|
sendsImage: true,
|
|
3745
3898
|
hasActions: false,
|
|
3746
3899
|
itemText: name => FILE_LIBRARY_TEXT.some(ext => name.toLowerCase().endsWith(ext)),
|
|
@@ -3757,7 +3910,9 @@ SOURCE_TYPES.workflow = {
|
|
|
3757
3910
|
icon: "workflow",
|
|
3758
3911
|
description: "A cloud flow, with records of its steps' calls from its run history.",
|
|
3759
3912
|
group: "Workflows",
|
|
3760
|
-
|
|
3913
|
+
// Each item reads as a call, as the Source's Calls group names them, though
|
|
3914
|
+
// the file holding one is a record.
|
|
3915
|
+
noun: "call",
|
|
3761
3916
|
uploads: [".json"],
|
|
3762
3917
|
sendsImage: false,
|
|
3763
3918
|
hasActions: true,
|
|
@@ -3810,15 +3965,15 @@ SOURCE_TYPES.dynamic = {
|
|
|
3810
3965
|
};
|
|
3811
3966
|
|
|
3812
3967
|
// HTTP GET (docs/dynamic-sources.md § 1): the safest pull provider. It fetches
|
|
3813
|
-
// a URL through the relay server-side --
|
|
3814
|
-
//
|
|
3968
|
+
// a URL through the relay server-side -- Fetch now keeps a response, Live
|
|
3969
|
+
// data fetches it again at submit -- so the page drives only the
|
|
3815
3970
|
// create dialog and the Call labels; the fetch, redaction and token-refusal are
|
|
3816
3971
|
// the server's (its SOURCE_PROVIDERS mirror in server.py).
|
|
3817
3972
|
DYNAMIC_PROVIDERS["http-get"] = { // provider: the HTTP GET adapter's own id
|
|
3818
3973
|
id: "http-get", // provider: its id, the row's config.provider
|
|
3819
3974
|
label: "HTTP GET",
|
|
3820
3975
|
icon: "square-code",
|
|
3821
|
-
noun: "
|
|
3976
|
+
noun: "response",
|
|
3822
3977
|
tier: "pull",
|
|
3823
3978
|
config: ["url"],
|
|
3824
3979
|
configProblems(config){
|
|
@@ -3846,8 +4001,8 @@ const API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
|
3846
4001
|
DYNAMIC_PROVIDERS["api"] = { // provider: the API call adapter's own id
|
|
3847
4002
|
id: "api", // provider: its id, the row's config.provider
|
|
3848
4003
|
label: "API call",
|
|
3849
|
-
icon: "
|
|
3850
|
-
noun: "
|
|
4004
|
+
icon: "arrow-left-right",
|
|
4005
|
+
noun: "response",
|
|
3851
4006
|
tier: "pull",
|
|
3852
4007
|
config: ["method", "url", "reqHeaders", "body", "connection"],
|
|
3853
4008
|
configProblems(config){
|
|
@@ -3876,7 +4031,7 @@ DYNAMIC_PROVIDERS["api"] = { // provider: the API call adapter's own id
|
|
|
3876
4031
|
// or an Upload… by hand, formalised as a provider. The lab fetches and executes
|
|
3877
4032
|
// nothing: a Supplied item is named by the id the caller gives it (its file
|
|
3878
4033
|
// name), which is what joins it to a case. It has no config and no server-side
|
|
3879
|
-
// production, so no
|
|
4034
|
+
// production, so no Fetch now and no live data; its Calls are the items handed in,
|
|
3880
4035
|
// redacted on the way in like any other (server.py's take_dynamic). A hosted run
|
|
3881
4036
|
// grades those Calls; a solely-Supplied Source with none is run only via the CLI.
|
|
3882
4037
|
DYNAMIC_PROVIDERS["supplied"] = { // provider: the Supplied adapter's own id
|
|
@@ -3906,6 +4061,7 @@ DYNAMIC_PROVIDERS["file"] = { // provider: the File on disk adapter's own id
|
|
|
3906
4061
|
label: "File on disk",
|
|
3907
4062
|
icon: "file",
|
|
3908
4063
|
noun: "file",
|
|
4064
|
+
produceLabel: "Read now",
|
|
3909
4065
|
tier: "local",
|
|
3910
4066
|
config: ["path"],
|
|
3911
4067
|
configProblems(config){
|
|
@@ -3929,6 +4085,7 @@ DYNAMIC_PROVIDERS["command"] = { // provider: the Command adapter's own id
|
|
|
3929
4085
|
label: "Command",
|
|
3930
4086
|
icon: "terminal",
|
|
3931
4087
|
noun: "call",
|
|
4088
|
+
produceLabel: "Run now",
|
|
3932
4089
|
tier: "local",
|
|
3933
4090
|
config: ["command", "args"],
|
|
3934
4091
|
configProblems(config){
|
|
@@ -4011,11 +4168,14 @@ CONTENT_TYPES.source = {
|
|
|
4011
4168
|
description: "Every file or record in a Source from the Library.",
|
|
4012
4169
|
// `live` is a dynamic Source's per-run snapshot-vs-live override
|
|
4013
4170
|
// (docs/dynamic-sources.md); meaningless for another kind, ignored there.
|
|
4014
|
-
fields: ["type", "ref", "first", "loops", "live"],
|
|
4171
|
+
fields: ["type", "ref", "first", "loops", "live", "pin"],
|
|
4015
4172
|
runFields: ["files", "revs"],
|
|
4016
4173
|
validate(c, ctx, bad){
|
|
4017
4174
|
if (!isRef(c.ref)) return void bad.push("a Source content has to name its Source as { id, name }");
|
|
4018
4175
|
if (c.live != null && typeof c.live !== "boolean") bad.push("a Source content's live is true or false");
|
|
4176
|
+
if (c.pin != null && !(isObj(c.pin) && isStr(c.pin.name) && c.pin.name && Number.isInteger(c.pin.n) && c.pin.n >= 0)) {
|
|
4177
|
+
bad.push("a Source content's pin names a response as { name, n }");
|
|
4178
|
+
}
|
|
4019
4179
|
if (c.first != null && !(Number.isInteger(c.first) && c.first > 0)) {
|
|
4020
4180
|
bad.push("Run first wants a positive number, or empty for all");
|
|
4021
4181
|
}
|
|
@@ -4116,7 +4276,7 @@ LEGACY_TESTS.single = {
|
|
|
4116
4276
|
const raws = [], terms = [];
|
|
4117
4277
|
for (const r of ress) {
|
|
4118
4278
|
if (!r) continue;
|
|
4119
|
-
raws.push(r
|
|
4279
|
+
raws.push(gradedText(r));
|
|
4120
4280
|
terms.push(...(r.terms || []));
|
|
4121
4281
|
}
|
|
4122
4282
|
const s = scoreSingle(t, terms, raws, !(kind != null && OUTPUT_KINDS[kind]?.terms));
|
|
@@ -4181,11 +4341,32 @@ function metricsProblems(list , at , bad ) {
|
|
|
4181
4341
|
/** Whether any of [list] asks a grader. */
|
|
4182
4342
|
const gradedIn = (list ) => Array.isArray(list) && list.some((m ) => !!METRICS[m?.type]?.graded);
|
|
4183
4343
|
|
|
4344
|
+
/** Whether [error] is a reply the job's rules threw away, as the Discarded
|
|
4345
|
+
metric reads it: a result that came back, where any other error is a
|
|
4346
|
+
request that never did. */
|
|
4347
|
+
const thrownAway = (error ) => !!error && error.startsWith("discarded: ");
|
|
4348
|
+
|
|
4349
|
+
/** [s] as an errored result's score: whatever its metrics read, a result
|
|
4350
|
+
with no reply fails, and only a discarded reply is left to them (#544) --
|
|
4351
|
+
or a refused call, to a case that scores whether it was (#559). */
|
|
4352
|
+
function erroredScore (s , error ) {
|
|
4353
|
+
const asksRefusal = !!s?.metrics?.some(r => r.weight !== 0 && METRICS[r.type]?.readsRefusal);
|
|
4354
|
+
return s && error != null && !thrownAway(error) && !asksRefusal ? { ...s, pass: false } : s;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
/** The reply a metric grades and Results shows (#546): the last stage's
|
|
4358
|
+
reply after its filters (#557), or the raw reply. */
|
|
4359
|
+
function gradedText(res ) {
|
|
4360
|
+
const last = res?.transcript?.at(-1);
|
|
4361
|
+
return last?.filtered?.got ?? last?.got ?? res?.raw ?? "";
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4184
4364
|
/** What a metric reads of one result. */
|
|
4185
4365
|
function metricInput(res , kase , more ) {
|
|
4186
4366
|
const last = res.transcript?.at(-1);
|
|
4187
|
-
const text =
|
|
4188
|
-
|
|
4367
|
+
const text = gradedText(res);
|
|
4368
|
+
// `of: "said"` reads the reply as it came back, before any filter.
|
|
4369
|
+
return { text, said: last?.said ?? (last?.filtered ? last.got ?? null : null), terms: res.terms ?? [], replies: [text], plain: !!more.plain,
|
|
4189
4370
|
asked: last?.sent ?? null,
|
|
4190
4371
|
// A case that has been given its own expected reply ("Take B as
|
|
4191
4372
|
// expected") is compared with that; otherwise the Source's recorded
|
|
@@ -4201,7 +4382,7 @@ function runInput(ress , plain )
|
|
|
4201
4382
|
let ms = 0;
|
|
4202
4383
|
for (const r of ress) {
|
|
4203
4384
|
if (!r) continue;
|
|
4204
|
-
replies.push(r
|
|
4385
|
+
replies.push(gradedText(r));
|
|
4205
4386
|
terms.push(...(r.terms || []));
|
|
4206
4387
|
ms += r.ms ?? 0;
|
|
4207
4388
|
}
|
|
@@ -4217,6 +4398,19 @@ function readMetric(m , input , ctx )
|
|
|
4217
4398
|
const label = (isStr(m.metric) && m.metric.trim()) || entry?.label || m.type;
|
|
4218
4399
|
if (!entry) return { type: m.type, label, weight, pass: false, score: 0, reason: `"${m.type}" is not a metric this lab has` };
|
|
4219
4400
|
const read = m.of === "said" ? { ...input, text: input.said ?? input.text } : input;
|
|
4401
|
+
// An errored result has no reply to read (#544): a metric that reads one
|
|
4402
|
+
// fails on the error, and a grader is never asked about the empty reply.
|
|
4403
|
+
if (input.error != null && !(entry.readsResult && (thrownAway(input.error) || entry.readsRefusal))) {
|
|
4404
|
+
const fail = { type: m.type, label, weight, pass: false, score: 0, reason: input.error };
|
|
4405
|
+
if (entry.graded) return fail;
|
|
4406
|
+
// What it would have missed still counts toward a run's totals.
|
|
4407
|
+
let o = null;
|
|
4408
|
+
try { o = entry.score(read, m, {}); } catch { o = null; }
|
|
4409
|
+
if (!o) return null;
|
|
4410
|
+
if (typeof (o ).then === "function") return fail;
|
|
4411
|
+
const { found, missed, invented } = o ;
|
|
4412
|
+
return { ...fail, ...(found ? { found } : {}), ...(missed ? { missed } : {}), ...(invented ? { invented } : {}) };
|
|
4413
|
+
}
|
|
4220
4414
|
const done = (o ) => {
|
|
4221
4415
|
if (!o) return null;
|
|
4222
4416
|
const turned = m.not ? { ...o, pass: !o.pass, score: entry.counts ? -o.score : 1 - o.score, reason: `not: ${o.reason}` } : o;
|
|
@@ -4249,6 +4443,18 @@ function readMetric(m , input , ctx )
|
|
|
4249
4443
|
} catch (e) { return timed(failed(e)); }
|
|
4250
4444
|
}
|
|
4251
4445
|
|
|
4446
|
+
/** What stops [grader] being asked, where the lookups can say: a grader is
|
|
4447
|
+
asked words, and needs a model to ask. */
|
|
4448
|
+
function graderProblems(grader , ctx , bad ) {
|
|
4449
|
+
const p = grader && ctx.profiles ? ctx.profiles(grader.id) : null;
|
|
4450
|
+
if (grader && ctx.profiles && !p) bad.push(`Grader ${grader.name || grader.id} not found`);
|
|
4451
|
+
else if (p) {
|
|
4452
|
+
const type = CONNECTION_TYPES[typeOf(p )];
|
|
4453
|
+
if (type && !(type.answers ?? ["prompt"]).includes("prompt")) bad.push(`the grader has to be asked words, and ${type.label} is not`);
|
|
4454
|
+
else if (!type?.local && !String((p ).model || "").trim()) bad.push("no model on the grader's connection");
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4252
4458
|
/** An eval's score from its metrics' readings, the way [mode] says: all must
|
|
4253
4459
|
pass, or weighted points against [threshold]. What a case metric found,
|
|
4254
4460
|
missed and invented is carried up, for a run's totals. Null for none. */
|
|
@@ -4277,7 +4483,7 @@ async function readMetrics(list , input , ctx
|
|
|
4277
4483
|
const r = await readMetric(m, input, ctx);
|
|
4278
4484
|
if (r) metrics.push(r);
|
|
4279
4485
|
}
|
|
4280
|
-
return scoreOf(metrics, mode, threshold);
|
|
4486
|
+
return erroredScore(scoreOf(metrics, mode, threshold), input.error);
|
|
4281
4487
|
}
|
|
4282
4488
|
|
|
4283
4489
|
/** [list] read over a whole run, synchronously: each metric over the run's
|
|
@@ -4331,14 +4537,7 @@ LEGACY_TESTS.metrics = {
|
|
|
4331
4537
|
// The eval's own grader, or the lab's.
|
|
4332
4538
|
const grader = isRef(t.grader) ? t.grader : isRef(ctx.grader) ? ctx.grader : null;
|
|
4333
4539
|
if (gradedIn(t.metrics) && !grader) bad.push("a model-graded metric needs a grader: pick one on the eval, or make a connection the lab's grader");
|
|
4334
|
-
|
|
4335
|
-
const p = grader && ctx.profiles ? ctx.profiles(grader.id) : null;
|
|
4336
|
-
if (grader && ctx.profiles && !p) bad.push(`Connection ${grader.name || grader.id} not found`);
|
|
4337
|
-
else if (p) {
|
|
4338
|
-
const type = CONNECTION_TYPES[typeOf(p )];
|
|
4339
|
-
if (type && !(type.answers ?? ["prompt"]).includes("prompt")) bad.push(`the grader has to be asked words, and ${type.label} is not`);
|
|
4340
|
-
else if (!type?.local && !String((p ).model || "").trim()) bad.push("no model on the grader's connection");
|
|
4341
|
-
}
|
|
4540
|
+
graderProblems(grader, ctx, bad);
|
|
4342
4541
|
},
|
|
4343
4542
|
rules: t => (Array.isArray(t.metrics) ? t.metrics : []).map((m , x ) => ({
|
|
4344
4543
|
key: `m${x}`, label: (isStr(m.metric) && m.metric) || METRICS[m.type]?.label || m.type,
|
|
@@ -4450,6 +4649,11 @@ EVAL_TYPES.group = {
|
|
|
4450
4649
|
if (held && t.pin != null && typeof held.versions === "number" && t.pin > held.versions) {
|
|
4451
4650
|
bad.push(`${g.name || g.id} has no version ${t.pin}`);
|
|
4452
4651
|
}
|
|
4652
|
+
// The grader the group asks: the run's copy of the link carries it, a
|
|
4653
|
+
// pipeline's reads it from the group as the lab lists it. One that is
|
|
4654
|
+
// gone would fail every item on a metric that cannot run (#545).
|
|
4655
|
+
const grader = isRef(t.grader) ? t.grader : isRef(held?.grader) ? held .grader : null;
|
|
4656
|
+
graderProblems(grader, ctx, bad);
|
|
4453
4657
|
return;
|
|
4454
4658
|
}
|
|
4455
4659
|
if (!isObj(own)) return void bad.push("an eval links a Library group, or holds its own");
|
|
@@ -4466,14 +4670,7 @@ EVAL_TYPES.group = {
|
|
|
4466
4670
|
// The group's own grader, or the lab's.
|
|
4467
4671
|
const grader = isRef(own.grader) ? own.grader : isRef(ctx.grader) ? ctx.grader : null;
|
|
4468
4672
|
if (gradedIn(own.every) && !grader) bad.push("a model-graded metric needs a grader: pick one on the eval, or make a connection the lab's grader");
|
|
4469
|
-
|
|
4470
|
-
const p = grader && ctx.profiles ? ctx.profiles(grader.id) : null;
|
|
4471
|
-
if (grader && ctx.profiles && !p) bad.push(`Connection ${grader.name || grader.id} not found`);
|
|
4472
|
-
else if (p) {
|
|
4473
|
-
const type = CONNECTION_TYPES[typeOf(p )];
|
|
4474
|
-
if (type && !(type.answers ?? ["prompt"]).includes("prompt")) bad.push(`the grader has to be asked words, and ${type.label} is not`);
|
|
4475
|
-
else if (!type?.local && !String((p ).model || "").trim()) bad.push("no model on the grader's connection");
|
|
4476
|
-
}
|
|
4673
|
+
graderProblems(grader, ctx, bad);
|
|
4477
4674
|
},
|
|
4478
4675
|
rules: t => ownList(t).map((m , x ) => ({
|
|
4479
4676
|
key: `m${x}`, label: (isStr(m.metric) && m.metric) || METRICS[m.type]?.label || m.type,
|
|
@@ -4864,8 +5061,12 @@ STEP_TYPES.httpRequest = {
|
|
|
4864
5061
|
}
|
|
4865
5062
|
}
|
|
4866
5063
|
if (isStr(step.prompt)) {
|
|
4867
|
-
|
|
4868
|
-
|
|
5064
|
+
// Built here as each call builds it, so an expression none of them
|
|
5065
|
+
// could read is refused before the run rather than failing every call.
|
|
5066
|
+
try {
|
|
5067
|
+
const why = templateProblem([step.path, step.query, api.place(step.body, step )]);
|
|
5068
|
+
if (why) bad.push(`${at}: the request could not be built -- ${why}`);
|
|
5069
|
+
} catch (e) { bad.push(`${at}: ${(e ).message}`); }
|
|
4869
5070
|
}
|
|
4870
5071
|
},
|
|
4871
5072
|
};
|
|
@@ -4890,7 +5091,6 @@ STEP_TYPES.echo = {
|
|
|
4890
5091
|
// the flow reads one. No profile, no prompt -- the record is the reply.
|
|
4891
5092
|
STEP_TYPES.recorded = {
|
|
4892
5093
|
label: "Recorded", slot: "target", asks: "nothing", local: true, replaces: "recorded",
|
|
4893
|
-
description: "Pre-recorded from previous workflow runs, replays only.",
|
|
4894
5094
|
firstJobOnly: "a call's recorded reply is job 1's",
|
|
4895
5095
|
in: "item", out: "text",
|
|
4896
5096
|
apply: "runPipeline",
|
|
@@ -5033,6 +5233,22 @@ function withContent (doc , content )
|
|
|
5033
5233
|
return { ...doc, jobs: [withStep(first, "attachContent", content ? { type: "attachContent", content } : null), ...rest] };
|
|
5034
5234
|
}
|
|
5035
5235
|
|
|
5236
|
+
/**
|
|
5237
|
+
* Whether target [i]'s words in job [k] have the item's text joined after
|
|
5238
|
+
* them (#597): job 1, a target that asks a prompt, text content -- a Source
|
|
5239
|
+
* of text items, or inline text, as its CONTENT_TYPES entry says -- and no
|
|
5240
|
+
* {text} in the words to place it (textPrompt). A later job's reply goes
|
|
5241
|
+
* only where a token places it, and an image or a document is sent as a part
|
|
5242
|
+
* of its own, so neither is joined. [ctx] reads a Source as validation does.
|
|
5243
|
+
*/
|
|
5244
|
+
function contentJoined(doc , i , k ,
|
|
5245
|
+
ctx = {}) {
|
|
5246
|
+
if (k !== 0 || targetEntryOf(doc, i, k)?.asks !== "prompt") return false;
|
|
5247
|
+
if (String(targetStepOf(doc, i, k)?.prompt ?? "").includes("{text}")) return false;
|
|
5248
|
+
const content = contentOf(doc);
|
|
5249
|
+
return !!content && (CONTENT_TYPES[content.type]?.text?.(content, ctx) ?? null) != null;
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5036
5252
|
/** A document's targets, in column order. */
|
|
5037
5253
|
function targetsOf(doc ) {
|
|
5038
5254
|
return Array.isArray(doc?.targets) ? doc .targets : [];
|
|
@@ -5252,6 +5468,15 @@ function stageModifiers(job , resolve
|
|
|
5252
5468
|
return out;
|
|
5253
5469
|
}
|
|
5254
5470
|
|
|
5471
|
+
/** A job's Post-format filters for a run when its kind reads no items
|
|
5472
|
+
(#557), in document order: every link's on its `items` filters step, which
|
|
5473
|
+
a run applies to the text after the kind reads it. A kind that reads items
|
|
5474
|
+
has none here -- `stageModifiers` runs its Post-format links. */
|
|
5475
|
+
function afterFilters(job , resolve ) {
|
|
5476
|
+
if (stageReads("items", outOf(job)?.kind) !== "text") return [];
|
|
5477
|
+
return (filterStepOf(job, "items")?.filterSets || []).flatMap(link => linkFilters(link, resolve));
|
|
5478
|
+
}
|
|
5479
|
+
|
|
5255
5480
|
/** A job's Pre-format (text) filters for a run (#405), in document order:
|
|
5256
5481
|
the filters of every link on its `text` filters step, which a run applies to
|
|
5257
5482
|
the raw reply before the kind reads it into items. [resolve] gives a Library
|
|
@@ -5419,6 +5644,12 @@ function versionProblem(doc ) {
|
|
|
5419
5644
|
|
|
5420
5645
|
|
|
5421
5646
|
|
|
5647
|
+
|
|
5648
|
+
|
|
5649
|
+
|
|
5650
|
+
|
|
5651
|
+
|
|
5652
|
+
|
|
5422
5653
|
|
|
5423
5654
|
|
|
5424
5655
|
/**
|
|
@@ -5710,7 +5941,8 @@ function ownBodiesNow(doc ) {
|
|
|
5710
5941
|
|
|
5711
5942
|
function upgradePipeline (doc , ctx = {}) {
|
|
5712
5943
|
// A current document is read as it is, but for a profile reference the Runs
|
|
5713
|
-
// tab saved whole (see profileRefs), which is cut back,
|
|
5944
|
+
// tab saved whole (see profileRefs), which is cut back, a Read Reply's
|
|
5945
|
+
// leftover `preamble: false` (withoutLeftoverPreamble), and a step on a
|
|
5714
5946
|
// profile a target step stands in for (localSteps).
|
|
5715
5947
|
if (isObj(doc) && doc.version === PIPELINE_VERSION) {
|
|
5716
5948
|
let out = doc ;
|
|
@@ -5718,12 +5950,16 @@ function upgradePipeline (doc , ctx = {}) {
|
|
|
5718
5950
|
out = clone(out) ;
|
|
5719
5951
|
profileRefs(out);
|
|
5720
5952
|
}
|
|
5721
|
-
return localSteps(ownBodiesNow(out), ctx) ;
|
|
5953
|
+
return localSteps(ownBodiesNow(withoutLeftoverPreamble(out)), ctx) ;
|
|
5722
5954
|
}
|
|
5723
|
-
if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].includes(doc.version )) return doc;
|
|
5955
|
+
if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].includes(doc.version )) return doc;
|
|
5956
|
+
// Every version reads as 17 first, then as 18: a saved-mode Source content
|
|
5957
|
+
// pins one response (savedPinned).
|
|
5958
|
+
const eighteen = (d ) => localSteps(ownBodiesNow(savedPinned(d, ctx)), ctx) ;
|
|
5959
|
+
if (doc.version === 17) return eighteen(cutRefs(clone(doc) ));
|
|
5724
5960
|
// Every version reads as 16 first, then as 17: its requests read by their
|
|
5725
5961
|
// job's HTTP API (requestsOnJobApi).
|
|
5726
|
-
const seventeen = (d ) =>
|
|
5962
|
+
const seventeen = (d ) => eighteen(requestsOnJobApi(d));
|
|
5727
5963
|
if (doc.version === 16) return seventeen(cutRefs(clone(doc) ));
|
|
5728
5964
|
// Every version reads as 15 first, then as 16: its Target profiles split
|
|
5729
5965
|
// into AI Connections and Connection Profiles (answersFromProfiles).
|
|
@@ -5769,11 +6005,13 @@ function filtersFromCleaning(next ) {
|
|
|
5769
6005
|
const isFilters = (st ) => isObj(st) && st.type === "filters";
|
|
5770
6006
|
next.jobs = next.jobs.map((job ) => {
|
|
5771
6007
|
if (!isObj(job) || !Array.isArray(job.steps)) return job;
|
|
5772
|
-
const
|
|
5773
|
-
const readReply = steps.find(st => isObj(st) && st.type === "readReply") ;
|
|
6008
|
+
const readReply = job.steps.find((st ) => isObj(st) && st.type === "readReply") ;
|
|
5774
6009
|
const preamble = !!(readReply && isObj(readReply.out) && readReply.out.preamble === true);
|
|
6010
|
+
// Stripped from every Read Reply, `false` included, before the job is
|
|
6011
|
+
// asked whether it has anything to migrate (#604).
|
|
6012
|
+
const steps = (job.steps ).map(withoutPreamble);
|
|
5775
6013
|
const stale = steps.some(st => isFilters(st) && (st ).stage == null);
|
|
5776
|
-
if (!steps.some(isCleaning) && !preamble && !stale) return job;
|
|
6014
|
+
if (!steps.some(isCleaning) && !preamble && !stale) return { ...job, steps };
|
|
5777
6015
|
const jid = isStr(job.id) ? job.id : "job";
|
|
5778
6016
|
|
|
5779
6017
|
// Walk the Responses steps, turning each into a chunk of items filters
|
|
@@ -5802,11 +6040,6 @@ function filtersFromCleaning(next ) {
|
|
|
5802
6040
|
const out = [];
|
|
5803
6041
|
let placed = false;
|
|
5804
6042
|
for (const st of steps) {
|
|
5805
|
-
if (isObj(st) && st.type === "readReply" && isObj(st.out) && "preamble" in st.out) {
|
|
5806
|
-
const { preamble: _p, ...rest } = st.out ;
|
|
5807
|
-
out.push({ ...st, out: rest });
|
|
5808
|
-
continue;
|
|
5809
|
-
}
|
|
5810
6043
|
if (isCleaning(st) || isFilters(st)) {
|
|
5811
6044
|
if (!placed) { if (textStep) out.push(textStep); if (itemsStep) out.push(itemsStep); placed = true; }
|
|
5812
6045
|
continue;
|
|
@@ -5819,6 +6052,30 @@ function filtersFromCleaning(next ) {
|
|
|
5819
6052
|
return next;
|
|
5820
6053
|
}
|
|
5821
6054
|
|
|
6055
|
+
/** [st] without its List `preamble` setting, when it is a Read Reply holding
|
|
6056
|
+
one; [st] itself otherwise. */
|
|
6057
|
+
function withoutPreamble(st ) {
|
|
6058
|
+
if (!isObj(st) || st.type !== "readReply" || !isObj(st.out) || !("preamble" in st.out)) return st;
|
|
6059
|
+
const { preamble: _p, ...out } = st.out ;
|
|
6060
|
+
return { ...st, out };
|
|
6061
|
+
}
|
|
6062
|
+
|
|
6063
|
+
/** Whether a current document still carries a `preamble: false` that an
|
|
6064
|
+
upgrade before #604 left behind. It did nothing, so it is cut as the
|
|
6065
|
+
document is read; a `preamble: true` the lab never wrote at this version is
|
|
6066
|
+
left for validation to refuse. */
|
|
6067
|
+
function leftoverPreamble(doc ) {
|
|
6068
|
+
return Array.isArray(doc.jobs) && doc.jobs.some((job ) => isObj(job) && Array.isArray(job.steps)
|
|
6069
|
+
&& job.steps.some((st ) => isObj(st) && st.type === "readReply" && isObj(st.out) && st.out.preamble === false));
|
|
6070
|
+
}
|
|
6071
|
+
|
|
6072
|
+
/** [doc] with each Read Reply's leftover `preamble: false` cut (leftoverPreamble). */
|
|
6073
|
+
function withoutLeftoverPreamble(doc ) {
|
|
6074
|
+
if (!leftoverPreamble(doc)) return doc;
|
|
6075
|
+
return { ...doc, jobs: doc.jobs.map((job ) => (!isObj(job) || !Array.isArray(job.steps) ? job
|
|
6076
|
+
: { ...job, steps: job.steps.map((st ) => (isObj(st) && isObj(st.out) && st.out.preamble === false ? withoutPreamble(st) : st)) })) };
|
|
6077
|
+
}
|
|
6078
|
+
|
|
5822
6079
|
/** [doc] with no Metrics eval holding the `case` metric: a dataset's cases
|
|
5823
6080
|
are metrics now (dataset body version 5), which an eval naming the
|
|
5824
6081
|
dataset adds for each item, so the case's own metrics carry what that
|
|
@@ -5872,6 +6129,24 @@ function localSteps(doc , ctx ) {
|
|
|
5872
6129
|
return next;
|
|
5873
6130
|
}
|
|
5874
6131
|
|
|
6132
|
+
/**
|
|
6133
|
+
* Version 17 to 18 (#596): Saved response pins one response. A Source content
|
|
6134
|
+
* graded as saved -- `live` false, or unset over a Source whose default is
|
|
6135
|
+
* saved, which [ctx] says -- pins the Source's newest successful response
|
|
6136
|
+
* (owner), so a Source holding several grades one where it graded all. Live
|
|
6137
|
+
* data, a content that is not a Source's, or one [ctx] cannot say of, pins
|
|
6138
|
+
* none; a run document carries its files, and needs none.
|
|
6139
|
+
*/
|
|
6140
|
+
function savedPinned(next , ctx ) {
|
|
6141
|
+
next.version = PIPELINE_VERSION;
|
|
6142
|
+
const content = contentOf(next ) ;
|
|
6143
|
+
if (!isObj(content) || content.type !== "source" || content.live === true || content.pin != null
|
|
6144
|
+
|| isObj(next.profiles) || !isRef(content.ref) || !ctx.pinFor) return next;
|
|
6145
|
+
const pin = ctx.pinFor(content.ref.id, typeof content.live === "boolean" ? content.live : undefined);
|
|
6146
|
+
if (pin) content.pin = { name: pin.name, n: pin.n };
|
|
6147
|
+
return next;
|
|
6148
|
+
}
|
|
6149
|
+
|
|
5875
6150
|
/**
|
|
5876
6151
|
* Version 16 to 17 (#477): a request step has no HTTP API of its own; its
|
|
5877
6152
|
* job's flow step says it. A step that named another -- Any endpoint over an
|
|
@@ -6082,6 +6357,10 @@ function validatePipeline(input , ctx = {}) {
|
|
|
6082
6357
|
? (c.sources?.(content.ref?.id)?.files ?? content.files ?? null) : null;
|
|
6083
6358
|
const nonText = (contentFiles || []).map(f => String((f )?.name ?? f))
|
|
6084
6359
|
.find(n => !/\.(?:txt|md|csv)$/i.test(n));
|
|
6360
|
+
// A document goes whole to the API, so only a type that reads one is
|
|
6361
|
+
// handed it -- never sent as an image or as text it is not.
|
|
6362
|
+
const docType = (contentFiles || []).map(f => documentTypeOf(String((f )?.name ?? f)))
|
|
6363
|
+
.find(Boolean) ?? null;
|
|
6085
6364
|
targets.forEach((_, i) => {
|
|
6086
6365
|
const conns = doc.jobs.map((_ , k ) => (targetEntryOf(doc, i, k)?.local ? localConnOf(targetEntryOf(doc, i, k)) : lookup(targetAnswerOf(doc, i, k) )));
|
|
6087
6366
|
conns.forEach((p , k ) => {
|
|
@@ -6111,6 +6390,10 @@ function validatePipeline(input , ctx = {}) {
|
|
|
6111
6390
|
const name = targetAnswerOf(doc, i, k)?.connection.name || type .label;
|
|
6112
6391
|
bad.push(`${n > 1 ? `${jobLabel(doc, k)}, ` : ""}${targetLabel(doc, i)}: ${name} can't send an ${HTTP_APIS[api]?.label ?? api} request`);
|
|
6113
6392
|
}
|
|
6393
|
+
if (docType && type && !type.documents && sendsImage(doc.jobs[k])) {
|
|
6394
|
+
const name = targetAnswerOf(doc, i, k)?.connection.name || type.label;
|
|
6395
|
+
bad.push(`${n > 1 ? `${jobLabel(doc, k)}, ` : ""}${targetLabel(doc, i)}: ${name} can't read ${docType.label}s`);
|
|
6396
|
+
}
|
|
6114
6397
|
});
|
|
6115
6398
|
// The model is the profile's unless the target's step carries it.
|
|
6116
6399
|
const bare = conns.findIndex((p , k ) => p && c.profiles && !local(p)
|
|
@@ -6317,7 +6600,11 @@ function resolvePipeline(doc ,
|
|
|
6317
6600
|
run.profiles[key] = { ...conn, connection: cid };
|
|
6318
6601
|
}
|
|
6319
6602
|
const content = contentOf(run) ;
|
|
6320
|
-
|
|
6603
|
+
// Saved response grades its one pinned response (#596), whatever else the
|
|
6604
|
+
// Source holds; the server keeps that response's bytes with the run.
|
|
6605
|
+
if (content?.type === "source") {
|
|
6606
|
+
content.files = CONTENT_TYPES.source .expand (content, isObj(content.pin) && content.live !== true ? [content.pin.name] : ctx.files);
|
|
6607
|
+
}
|
|
6321
6608
|
// Each Library group the evals read, as the version it grades with: the
|
|
6322
6609
|
// body's fingerprint and, where the lab numbers them, `n` -- a pinned
|
|
6323
6610
|
// link's pin.
|
|
@@ -6584,7 +6871,8 @@ const BUNDLE_DATASET = /^datasets\/([a-z0-9]+(?:-[a-z0-9]+)*)\.json$/;
|
|
|
6584
6871
|
* [doc] as a bundle's text files: `pipeline.yaml`, `profiles.yaml` and
|
|
6585
6872
|
* `datasets/<slug>.json`. [ctx.connections] and [ctx.connectionProfiles]
|
|
6586
6873
|
* are Setup's -- keys and all, none of which is written -- or
|
|
6587
|
-
* [ctx.profiles], Target profiles to split as the store was
|
|
6874
|
+
* [ctx.profiles], Target profiles to split as the store was, and
|
|
6875
|
+
* [ctx.profileDefault] (Default)'s settings; [ctx.grader]
|
|
6588
6876
|
* the lab's grader, written into each eval that would have asked it, and
|
|
6589
6877
|
* [ctx.datasets] each graded dataset by id, as `{ name, body }`. Each
|
|
6590
6878
|
* connection asked is written resolved, under its answer key with the
|
|
@@ -6593,12 +6881,13 @@ const BUNDLE_DATASET = /^datasets\/([a-z0-9]+(?:-[a-z0-9]+)*)\.json$/;
|
|
|
6593
6881
|
* something else.
|
|
6594
6882
|
*/
|
|
6595
6883
|
function exportBundle(doc , ctx
|
|
6596
|
-
|
|
6597
|
-
|
|
6884
|
+
|
|
6885
|
+
= {})
|
|
6598
6886
|
{
|
|
6599
6887
|
const split = (ctx.profiles ?? []).map(splitProfile);
|
|
6600
6888
|
const profiles = withSlugs([...(ctx.connections ?? []), ...split.map(x => x.connection)] );
|
|
6601
|
-
const answers = answersOf(profiles, [...(ctx.connectionProfiles ?? []), ...split.flatMap(x => (x.profile ? [x.profile] : []))]
|
|
6889
|
+
const answers = answersOf(profiles, [...(ctx.connectionProfiles ?? []), ...split.flatMap(x => (x.profile ? [x.profile] : []))],
|
|
6890
|
+
ctx.profileDefault);
|
|
6602
6891
|
// What the lab would supply at submit, written down: no lab supplies it later.
|
|
6603
6892
|
const out = clone(doc) ;
|
|
6604
6893
|
out.evals = (Array.isArray(out.evals) ? out.evals : [])
|
|
@@ -6774,7 +7063,7 @@ function stagesFor(run , i , resolveFilters
|
|
|
6774
7063
|
// without a resolver, a Library link reads nothing, a private one its own.
|
|
6775
7064
|
return { text: targetStepOf(run, i, k) .prompt, kind, withImage: sendsImage(ch),
|
|
6776
7065
|
verbatim: !!targetEntryOf(run, i, k)?.verbatim, before: beforeFilters(ch, resolveFilters),
|
|
6777
|
-
modifiers: stageModifiers(ch, resolveFilters), settings };
|
|
7066
|
+
after: afterFilters(ch, resolveFilters), modifiers: stageModifiers(ch, resolveFilters), settings };
|
|
6778
7067
|
});
|
|
6779
7068
|
// Each named by the connection whose key it is sent with.
|
|
6780
7069
|
const asked = (key , held ) => {
|
|
@@ -6913,7 +7202,9 @@ function scenarioEvals(run , i , items
|
|
|
6913
7202
|
return evals.map((t, j) => {
|
|
6914
7203
|
const type = EVAL_TYPES[t.type];
|
|
6915
7204
|
const whole = isWholeRun(type, t);
|
|
6916
|
-
|
|
7205
|
+
// An unnamed eval that links a group goes by the group's name, as the
|
|
7206
|
+
// Evals Summary names it, not "Eval 1" (#519).
|
|
7207
|
+
const base = { id: t.id, label: (t.name || "").trim() || t.group?.name || evalLabel({ evals }, j), whole, skipped: skipRest,
|
|
6917
7208
|
verdict: null , rules: type?.rules?.(t) ?? [], items: cells.map(() => null) ,
|
|
6918
7209
|
ran: 0, passed: 0, skippedItems: 0 };
|
|
6919
7210
|
if (skipRest) {
|
|
@@ -7053,6 +7344,13 @@ function validateEvals(ev , files
|
|
|
7053
7344
|
if (scored.length > 1) say("expects its answer discarded, and states something the answer should hold too");
|
|
7054
7345
|
continue;
|
|
7055
7346
|
}
|
|
7347
|
+
if (scored.some(m => m.type === "refused" && !m.not)) {
|
|
7348
|
+
// Nor does a call that was refused: there is no answer. Scored in
|
|
7349
|
+
// points, the other metrics are alternatives rather than more to hold
|
|
7350
|
+
// (#560: refused, or the reply says it can't read the file).
|
|
7351
|
+
if (scored.length > 1 && ev.scoring?.mode !== "weighted") say("expects its call refused, and states something the answer should hold too");
|
|
7352
|
+
continue;
|
|
7353
|
+
}
|
|
7056
7354
|
const wants = scored.filter(m => !m.not && (m.type === "contains-all" || m.type === "contains-any"));
|
|
7057
7355
|
const forbids = scored.filter(m => m.not && m.type === "contains");
|
|
7058
7356
|
for (const m of wants) for (const t of values(m)) if (!matchable(t)) say(`expects ${t}, which its own scorer cannot match`);
|
|
@@ -7166,7 +7464,7 @@ export {
|
|
|
7166
7464
|
loopReplyError, preparedSize,
|
|
7167
7465
|
termIn, forbiddenIn, readCase, gradedSetFrom, caseItem, caseMetrics, metricLines, upgradeDatasetBody, takeAsExpected, readGroup, readGroupRun, datasetRules, upgradeResults, emptyTally, addToTally, isHostedUrl,
|
|
7168
7466
|
SEED, REPLY_TOKENS_BEFORE, ANTHROPIC_MAX_TOKENS, asNumber, pinReplyTokens, mappingsFor,
|
|
7169
|
-
CONNECTION_TYPES, HTTP_APIS, httpApiFor, httpRequestOf, httpReplyOf, readFlowReply, WdlError, localAnswer, typeOf, profileType, convertProfile, splitOllama, DECODING_KEYS, apiBase,
|
|
7467
|
+
CONNECTION_TYPES, DOCUMENT_TYPES, documentTypeOf, documentUrl, attachmentOf, HTTP_APIS, httpApiFor, httpRequestOf, httpReplyOf, readFlowReply, WdlError, localAnswer, typeOf, listedConnection, profileType, convertProfile, splitOllama, DECODING_KEYS, apiBase,
|
|
7170
7468
|
EDGE_448, budgetLabel, IMAGE_FORMATS, encoderQuality,
|
|
7171
7469
|
tallyPercent, runPipeline, validateEvals, evalsWarnings, evalsJson, readList,
|
|
7172
7470
|
scoreSingle, singleRules, parseCount, lengthHolds, countHolds, COUNT_OPS, LENGTH_OPS,
|
|
@@ -7175,12 +7473,12 @@ export {
|
|
|
7175
7473
|
SOURCE_TYPES, DEFAULT_SOURCE_TYPE, sourceTypeOf, WORKFLOW_PLATFORMS, platformOf, DYNAMIC_PROVIDERS, providerOf, WIZARDS, wizardProgress,
|
|
7176
7474
|
registerKinds, defaultKind, jobDefaults, applyModifiers, keyVar, slugOf, uniqueSlug, isSlug, withSlugs, SLUG_MAX, connectionOf, addressOf, connectionSettings,
|
|
7177
7475
|
splitProfile, mergeAnswer, answerKey, answerOfKey, answersOf, runAnswer, answerConnectionId, connectionScoped,
|
|
7178
|
-
connectionRequest, connectionBase, connectionSend, connectionReply, connectionReplyProblem, connectionImage, connectionProblems,
|
|
7476
|
+
connectionRequest, connectionBase, connectionSend, connectionReply, connectionReplyProblem, replyFinish, contentJoined, connectionImage, connectionProblems,
|
|
7179
7477
|
CONNECTION_FIELDS, SETTING_KEYS, OPTION_FIELDS, jobLabel, scenarioLabel, jobWords, versionProblem,
|
|
7180
7478
|
contentOf, withContent, replyOf,
|
|
7181
7479
|
targetsOf, targetLetter, targetLabel, targetStepOf, targetEntryOf, jobTargetType, targetAnswerOf, targetAnswerKey, recordedTargetOf, withTarget,
|
|
7182
7480
|
tokensOf, withTokens, sendsImage, withImage, flowStepOf, requestStepOf, readAsOf, outOf, withOut, slotEntry, SLOTS,
|
|
7183
|
-
filterSetsOf, withFilterSets, stageModifiers, beforeFilters,
|
|
7481
|
+
filterSetsOf, withFilterSets, stageModifiers, beforeFilters, afterFilters, gradedText,
|
|
7184
7482
|
blankPipeline, upgradePipeline, fatProfileRef, newId, newEval, newLink, validatePipeline, resolvePipeline, pipelineOfRun, stagesFor, profileIds, answerKeys,
|
|
7185
7483
|
scenarioProfile, lastKind, lastModifier, modifierSummary, itemScores, scenarioEvals, scenarioPasses, groupPass, overallVerdict, passVerdict, isSkipped, failedScore,
|
|
7186
7484
|
evalLabel, evalsOf, evalsDataset, casesRef, groupOf, groupOfMetrics, pipelineWarnings, itemsFound, previewOf, isWholeRun, EVAL_FIELDS,
|