evals-lab 0.8.0 → 0.9.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 CHANGED
@@ -5,6 +5,48 @@ Newest first. Read a release's **Upgrade notes** before installing it: an
5
5
  upgrade can rewrite what the lab keeps in your data directory, and an older
6
6
  version cannot always read it back.
7
7
 
8
+ ## 0.9.0
9
+
10
+ ### Upgrade notes
11
+
12
+ - **Copy your data directory before upgrading** (see "User data" in the
13
+ README). A pipeline 0.9.0 saves is pipeline version 14, which 0.8.x
14
+ cannot read. To go back, reinstall 0.8.0 and restore the copy.
15
+ - A job's Drop items and Reject the answer rules become one private filter
16
+ set in that job, which keeps and rejects exactly what they did. Pipelines
17
+ read as version 14, and are saved as it at their next edit.
18
+ - On first start, 0.9.0 adds the tables filter sets are kept in, and each
19
+ run keeps the filter sets it ran with.
20
+
21
+ ### Added
22
+
23
+ - Library › Filter sets: named, versioned lists of filters that clean a list
24
+ reply. A Drop item filter drops the items its conditions match; a Reject
25
+ reply filter rejects the whole reply. Filters run in their list's order.
26
+ - A job's Responses stage links filter sets, following each one's latest
27
+ version or pinned to one, or holds a private set of its own.
28
+ - Each Target profile shows its connection status: Not tested, Connected,
29
+ Key needed or Unreachable, checked quietly when Setup opens. The dot opens
30
+ the Test connection result.
31
+ - Target profiles list each profile's Model and what uses it, with quick
32
+ filters: All, Connected, Needs attention and Grader. A profile's edit
33
+ sheet links the pipelines and eval groups that use it.
34
+ - Selected profiles can be tested, or have their workspaces changed, all
35
+ at once.
36
+ - An HTTP Request target's prompt can be edited. Insert field… adds one of
37
+ the item's fields, and the preview highlights the words changed from the
38
+ recorded prompt.
39
+ - The Recorded target shows the flow's original prompt.
40
+
41
+ ### Changed
42
+
43
+ - Target profiles no longer show Type, Address and Key columns, or the
44
+ Type, Key and Sort selects.
45
+ - The Edited target's request template is in Flow's request, folded until
46
+ opened.
47
+ - Drop items and Reject the answer are no longer offered under a job's
48
+ Responses: link a filter set instead.
49
+
8
50
  ## 0.8.0
9
51
 
10
52
  ### Upgrade notes
package/README.md CHANGED
@@ -11,6 +11,7 @@ Runs are kept in history which can be exported.
11
11
  Currently in early release. Runs locally and doesn't send your data anywhere else.
12
12
 
13
13
  Example use-cases:
14
+ - Keywording images using a model's vision capability, and running evals on the tags it returns
14
15
  - Providing images to an LLM and comparing responses for accuracy
15
16
  - Checking which model can meet evals in a Power Automate workflow
16
17
  - Determining which prompt gets the highest score in metrics
package/lab/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0 (2026.10.07-484)
1
+ 0.9.0 (2026.10.07-516)
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 13,
2
+ "version": 14,
3
3
  "id": "demo-1",
4
4
  "name": "Demo 1",
5
5
  "jobs": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 13,
2
+ "version": 14,
3
3
  "id": "demo-2",
4
4
  "name": "Demo 2",
5
5
  "jobs": [
@@ -33,12 +33,14 @@
33
33
  // and that is a measurement.
34
34
 
35
35
  import yaml from "./js-yaml.mjs";
36
-
36
+
37
37
  import { evaluate, asText, 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";
41
- import list, { ECHO, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProblems, dropBy, rejectBy, matcher } from "./kinds/list.mjs";
41
+ import list, { ECHO, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProblems, dropBy, rejectBy, matcher,
42
+ FILTER_TYPES, FILTER_SEPARATORS, FILTER_MODIFIER, applyFilter, applyFilterSet, tryFilter, splitSample, filterProblems, filterFromItemRule,
43
+ } from "./kinds/list.mjs";
42
44
 
43
45
  // ---- The types -------------------------------------------------------------
44
46
  // docs/pipeline-model.md as types, and the shapes of the registries every
@@ -138,9 +140,19 @@ import list, { ECHO, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProb
138
140
 
139
141
 
140
142
 
143
+ /** The filter sets a job cleans and gates its reply by (#339): each a link to
144
+ a Library filter set (followed latest or pinned) or a private one held
145
+ inline. A Responses step beside the modifiers, at the same place, so its
146
+ sets' filters apply in document order with them -- what the Drop items and
147
+ Reject the answer modifiers did inline, lifted into linked sets. */
148
+
149
+
150
+
151
+
152
+
141
153
  /** A job's steps: its Content stage, then its Responses (§16). */
142
154
 
143
-
155
+
144
156
 
145
157
  /** A target step that prompts a model: its words, resolved under the job's
146
158
  token mappings, and a profile of its own where it names one. */
@@ -316,6 +328,45 @@ import list, { ECHO, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProb
316
328
  /** A pipeline's evals, in the order they read a run. */
317
329
 
318
330
 
331
+ /**
332
+ * A filter set (issue #335): a versioned Library body holding the filters
333
+ * that shape a list reply, mirroring DatasetBody / the eval group. The lab
334
+ * will keep each as a row and a run grade against the body kept with it, as a
335
+ * dataset does (#336); for now it is the format a filter set reads and writes
336
+ * at. `filters` are tried in order -- each drop-item narrows the items the
337
+ * next reads, and the first reject-reply that fires gates the answer.
338
+ */
339
+
340
+
341
+
342
+
343
+
344
+ /** A Library filter set by reference; a run's copy says the version it used:
345
+ the body's fingerprint, and its number. Mirrors GroupRef. */
346
+
347
+
348
+ /** A private filter set: a filter-set body held in the pipeline rather than
349
+ the Library -- what Runs makes when a link is not to a Library set (#339),
350
+ as a private eval group (OwnGroup) is. A filter set has no cases, so this
351
+ is the body itself. */
352
+
353
+
354
+ /**
355
+ * A pipeline's link to a filter set in Responses (issue #335, mirrors
356
+ * GroupEval / OwnGroup): `set` names a Library set, followed at its newest
357
+ * version (`pin: null`) or pinned at one (`pin: n`); a private set is `own`,
358
+ * with `set: null`. From version 14 a job's Responses stage carries these on
359
+ * a `filters` step (#339); the upgrade from 13 migrates the inline Drop items
360
+ * / Reject rules into a private one, and the worker cleans and gates a run's
361
+ * replies against the kept bodies.
362
+ */
363
+
364
+
365
+
366
+
367
+
368
+
369
+
319
370
 
320
371
 
321
372
 
@@ -874,6 +925,11 @@ import list, { ECHO, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProb
874
925
 
875
926
 
876
927
 
928
+
929
+
930
+
931
+
932
+
877
933
 
878
934
 
879
935
 
@@ -1233,6 +1289,57 @@ function upgradeDatasetBody (body ) {
1233
1289
  return recordedIdsV7(groupOfV6({ source: null, cases: raw.map(caseOfV4).map(caseOfV5) }));
1234
1290
  }
1235
1291
 
1292
+ /** The filter-set body's version (issue #335). Version 1 is the first: the
1293
+ `{ version, filters }` body #334 introduces. A later format adds a step to
1294
+ `upgradeFilterSetBody`, as `upgradeDatasetBody` grew, with server.py's
1295
+ `upgrade_filter_set_body` its twin. */
1296
+ const FILTER_SET_VERSION = 1 ;
1297
+
1298
+ /** [body] as this version of a filter set (1), from any earlier one. There is
1299
+ no earlier version yet, so a version-1 body reads as it is and anything
1300
+ else comes back untouched for `filterSetBodyProblems` to refuse. Pure: the
1301
+ same body gives the same answer, and server.py's upgrade_filter_set_body is
1302
+ its twin. */
1303
+ function upgradeFilterSetBody (body ) {
1304
+ if (!isObj(body)) return body;
1305
+ if (body.version === FILTER_SET_VERSION) return body;
1306
+ return body;
1307
+ }
1308
+
1309
+ /** A filter set with no filters, at this version: what New filter set starts
1310
+ from, and what a lab with none holds. */
1311
+ function blankFilterSetBody() {
1312
+ return { version: FILTER_SET_VERSION, filters: [] };
1313
+ }
1314
+
1315
+ /** Why [body] is not a filter set the lab reads, one sentence each: an object
1316
+ at this version whose filters are a valid filter list (filterProblems). For
1317
+ the store (#336) and the pipeline link (#339). */
1318
+ function filterSetBodyProblems(body , at = "the filter set") {
1319
+ if (!isObj(body)) return [`${at} has to be a JSON object`];
1320
+ if (body.version !== FILTER_SET_VERSION) {
1321
+ return [`${at} is version ${JSON.stringify(body.version)}, and the lab reads version ${FILTER_SET_VERSION}`];
1322
+ }
1323
+ return filterProblems(body.filters, at);
1324
+ }
1325
+
1326
+ /** Why [link] is not a filter-set link a run's Responses step may carry, one
1327
+ sentence each (#339): an id, exactly one of a Library `set` reference or a
1328
+ private `own` body (whose shape is the core's to judge), and a `pin` that
1329
+ is null or a whole number. A Library set is the server's to resolve, so its
1330
+ body is not checked here. */
1331
+ function filterSetLinkProblems(link , at = "a filter set") {
1332
+ if (!isObj(link)) return [`${at}: a filter-set link has to be a JSON object`];
1333
+ const bad = [];
1334
+ if (!isStr(link.id) || !link.id.trim()) bad.push(`${at}: a filter-set link has no id`);
1335
+ const hasSet = isObj(link.set) && isStr(link.set.id);
1336
+ const hasOwn = link.own != null;
1337
+ if (hasSet === hasOwn) bad.push(`${at}: a filter-set link names a Library set or holds a private one, not both`);
1338
+ if (hasOwn) bad.push(...filterSetBodyProblems(link.own, `${at}: the private filter set`));
1339
+ if (link.pin != null && !Number.isInteger(link.pin)) bad.push(`${at}: a pin is a whole number or none`);
1340
+ return bad;
1341
+ }
1342
+
1236
1343
  /** A version-6 body as a version-8 eval group: scored All, the lab's
1237
1344
  grader, and no metrics of its own for every item or the whole run --
1238
1345
  what a Metrics eval naming the dataset with none of its own graded, which
@@ -2936,7 +3043,7 @@ function applyModifiers (list , kind ,
2936
3043
  // 12: a Contains metric's Ignore case holds item by item too.
2937
3044
  // 13: an eval is a link to an eval group, or a group of the pipeline's own,
2938
3045
  // and the document has an overall pass rule (pipeline-model §17).
2939
- const PIPELINE_VERSION = 13 ;
3046
+ const PIPELINE_VERSION = 14 ;
2940
3047
 
2941
3048
  // Plain objects, so an entry is added by assignment and a reader never needs
2942
3049
  // to know which registered it.
@@ -4234,6 +4341,23 @@ STEP_TYPES.modifier = {
4234
4341
  },
4235
4342
  };
4236
4343
 
4344
+ // A filters step: the filter sets a job cleans and gates its reply by, each a
4345
+ // link or a private set (#339). It sits with the modifiers (rank 2), so its
4346
+ // sets apply in document order among them. A private set's body is the core's
4347
+ // to judge (filterSetBodyProblems); a link to a Library set is the server's to
4348
+ // resolve, so only its shape is checked here.
4349
+ STEP_TYPES.filters = {
4350
+ label: "Filter sets", slot: "responses", rank: 2, many: true,
4351
+ description: "Cleans and gates the reply with linked or private filter sets.",
4352
+ in: "value", out: "value",
4353
+ apply: "runPipeline",
4354
+ fields: ["type", "filterSets"],
4355
+ validate(step, _ctx, bad, at){
4356
+ if (!Array.isArray(step.filterSets)) return void bad.push(`${at}: filterSets has to be a list`);
4357
+ for (const link of step.filterSets ) bad.push(...filterSetLinkProblems(link, at));
4358
+ },
4359
+ };
4360
+
4237
4361
  // ---- reading a job's steps, and a target's -----------------------------------
4238
4362
  // Every reader asks these, never a step's position or the document's shape:
4239
4363
  // which steps a job holds is its own, and a version that moves a field
@@ -4429,12 +4553,58 @@ function outOf(job ) {
4429
4553
  return { ...read, modifiers } ;
4430
4554
  }
4431
4555
 
4432
- /** [job] reading its reply as [out]: its Format Validation and modifiers. */
4556
+ /** [job] reading its reply as [out]: its Format Validation and modifiers.
4557
+ The modifiers replace the job's modifier steps in place, so a modifier step
4558
+ that interleaves with a filters step (#339) keeps its place among them --
4559
+ an edit to one modifier never reorders another step past it. Extra
4560
+ modifiers (one added) come after the last, and a step with no modifier left
4561
+ (one removed) drops out. */
4433
4562
  function withOut(job , out ) {
4434
4563
  const { modifiers, ...read } = out;
4435
- const steps = job.steps.filter(st => st.type !== "readReply" && st.type !== "modifier");
4436
- return { ...job, steps: ordered([...steps, { type: "readReply", out: read },
4437
- ...(modifiers || []).map(modifier => ({ type: "modifier", modifier }) )]) };
4564
+ const mods = modifiers || [];
4565
+ let mi = 0;
4566
+ const steps = [];
4567
+ for (const st of job.steps) {
4568
+ if (st.type === "readReply") continue; // re-added below, in its place
4569
+ if (st.type === "modifier") { if (mi < mods.length) steps.push({ type: "modifier", modifier: mods[mi++] }); }
4570
+ else steps.push(st); // readAs, filters, content -- kept where they are
4571
+ }
4572
+ for (; mi < mods.length; mi++) steps.push({ type: "modifier", modifier: mods[mi] });
4573
+ return { ...job, steps: ordered([...steps, { type: "readReply", out: read }]) };
4574
+ }
4575
+
4576
+ /** The filter-set links a job's Responses stage carries, in document order
4577
+ (#339): read from its one filters step, or none. */
4578
+ function filterSetsOf(job ) {
4579
+ return (stepOf (job, "filters")?.filterSets ?? []).slice();
4580
+ }
4581
+
4582
+ /** [job] with its filter-set links set to [links]: its filters step added,
4583
+ replaced, or taken away when none are left. */
4584
+ function withFilterSets(job , links ) {
4585
+ return withStep(job, "filters", links.length ? { type: "filters", filterSets: [...links] } : null);
4586
+ }
4587
+
4588
+ /** A job's Responses as an ordered modifier list for a run (#339): each
4589
+ modifier step as it is, and each filter-set link as a synthetic modifier
4590
+ that applies the set's filters where the link sits -- so a linked or
4591
+ private set cleans and gates a list reply exactly as the Drop items /
4592
+ Reject modifiers it replaced did, in the same order. [resolve] gives a
4593
+ Library link's body (the worker's kept bodies); a private (`own`) link
4594
+ carries its own, and a Library link with no body resolves to nothing. */
4595
+ function stageModifiers(job , resolve ) {
4596
+ const out = [];
4597
+ for (const st of job?.steps || []) {
4598
+ if (!isObj(st)) continue;
4599
+ if (st.type === "modifier") out.push(st.modifier);
4600
+ else if (st.type === "filters") {
4601
+ for (const link of (st.filterSets ) || []) {
4602
+ const body = link.own ?? (link.set && resolve ? resolve(link.set) : null);
4603
+ out.push({ type: FILTER_MODIFIER, filters: body?.filters || [] });
4604
+ }
4605
+ }
4606
+ }
4607
+ return out;
4438
4608
  }
4439
4609
 
4440
4610
  /** A new id: random, so one minted in one browser never collides with one
@@ -4676,6 +4846,13 @@ function newLink(group , name = "") {
4676
4846
  return { type: "group", id: newId(), name, continueOnFailure: true, group: { id: group.id, name: group.name }, pin: null };
4677
4847
  }
4678
4848
 
4849
+ /** A new pipeline link to the Library filter set [set], followed at its newest
4850
+ version. Mirrors newLink; for Runs to use when it links a set in Responses
4851
+ (#339). */
4852
+ function newFilterLink(set ) {
4853
+ return { id: newId(), set: { id: set.id, name: set.name }, pin: null };
4854
+ }
4855
+
4679
4856
  function jobsOf(next ) {
4680
4857
  next.version = PIPELINE_VERSION;
4681
4858
  if (Array.isArray(next.chains)) {
@@ -4822,16 +4999,62 @@ function upgradePipeline (doc , ctx = {}) {
4822
4999
  }
4823
5000
  return localSteps(out, ctx) ;
4824
5001
  }
4825
- if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].includes(doc.version )) return doc;
5002
+ if (!isObj(doc) || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13].includes(doc.version )) return doc;
5003
+ // Version 13 reads as 14 by migrating a job's inline Drop items / Reject
5004
+ // rules into a private filter set (#339); nothing else about it changes.
5005
+ if (doc.version === 13) {
5006
+ return localSteps(filtersFromRules(clone(doc) ), ctx) ;
5007
+ }
4826
5008
  if (doc.version === 11 || doc.version === 12) {
4827
- return localSteps(groupsOf(withoutCaseMetric(caseAsWritten(cutRefs(clone(doc) )))), ctx) ;
5009
+ return localSteps(filtersFromRules(groupsOf(withoutCaseMetric(caseAsWritten(cutRefs(clone(doc) ))))), ctx) ;
4828
5010
  }
4829
5011
  // Every version before 10 reads as version 9 first, then as 10, then as 11,
4830
- // 12 and 13.
5012
+ // 12, 13 and 14.
4831
5013
  // A version-10 document is cut as a current one was (profileRefs); the
4832
5014
  // earlier ones are cut on their way through nineOf.
4833
5015
  const ten = doc.version === 10 ? cutRefs(clone(doc) ) : targetsFromScenarios(nineOf(clone(doc) , ctx));
4834
- return localSteps(groupsOf(withoutCaseMetric(caseAsWritten(evalsKey(ten)))), ctx) ;
5016
+ return localSteps(filtersFromRules(groupsOf(withoutCaseMetric(caseAsWritten(evalsKey(ten))))), ctx) ;
5017
+ }
5018
+
5019
+ /** [doc] with each job's inline Drop items / Reject modifier steps migrated
5020
+ into one private filter set on a `filters` step, version 13 to 14 (#339).
5021
+ Every drop/reject modifier's rules become filters in document order -- a
5022
+ `drop` modifier's rules drop-item filters, a `reject` modifier's
5023
+ reject-reply filters (filterFromItemRule, which carries the matcher
5024
+ unchanged) -- in one `own` set, and the step takes the place of the first
5025
+ of those modifiers, so the reply is cleaned and gated as it was. A job with
5026
+ no such modifier, or only empty ones, is left as it is; this is a no-op on
5027
+ every path that reaches version 14, so it is safe to run on all of them. */
5028
+ function filtersFromRules(next ) {
5029
+ next.version = PIPELINE_VERSION;
5030
+ if (!Array.isArray(next.jobs)) return next;
5031
+ const TYPE = { drop: "drop-item", reject: "reject-reply" };
5032
+ const isRule = (st ) =>
5033
+ isObj(st) && st.type === "modifier" && isObj(st.modifier) && (st.modifier.type ) in TYPE;
5034
+ next.jobs = next.jobs.map((job ) => {
5035
+ if (!isObj(job) || !Array.isArray(job.steps) || !job.steps.some(isRule)) return job;
5036
+ const filters = [];
5037
+ for (const st of job.steps ) {
5038
+ if (!isRule(st)) continue;
5039
+ const m = (st ).modifier ;
5040
+ for (const r of Array.isArray(m.rules) ? m.rules : []) {
5041
+ filters.push(filterFromItemRule(r, TYPE[m.type ] ));
5042
+ }
5043
+ }
5044
+ // Empty Drop items / Reject modifiers held nothing, so they simply drop.
5045
+ // The link's id is the job's, so re-upgrading a document mints the same
5046
+ // one (the migration is idempotent) and a run keyed by it reads the same.
5047
+ const link = filters.length
5048
+ ? { id: `filters-${isStr(job.id) ? job.id : "job"}`, set: null, pin: null, own: { version: FILTER_SET_VERSION, filters } } : null;
5049
+ let placed = false;
5050
+ const steps = [];
5051
+ for (const st of job.steps ) {
5052
+ if (!isRule(st)) { steps.push(st); continue; }
5053
+ if (link && !placed) { steps.push({ type: "filters", filterSets: [link] }); placed = true; }
5054
+ }
5055
+ return { ...job, steps };
5056
+ });
5057
+ return next;
4835
5058
  }
4836
5059
 
4837
5060
  /** [doc] with no Metrics eval holding the `case` metric: a dataset's cases
@@ -5636,13 +5859,16 @@ function readBundle(files , ctx = {})
5636
5859
  * the job it sits in, and the connection it asks -- its own profile, or
5637
5860
  * its scenario's.
5638
5861
  */
5639
- function stagesFor(run , i )
5862
+ function stagesFor(run , i , resolveFilters )
5640
5863
 
5641
5864
  {
5642
5865
  const stages = run.jobs.map((ch, k) => {
5643
- const { kind, modifiers, ...settings } = outOf(ch);
5866
+ const { kind, modifiers: _mods, ...settings } = outOf(ch);
5867
+ // The modifiers a run applies, with each linked or private filter set a
5868
+ // synthetic modifier where its link sits (#339); without a resolver, a
5869
+ // Library link reads nothing, a private one its own body.
5644
5870
  return { text: targetStepOf(run, i, k) .prompt, kind, withImage: sendsImage(ch),
5645
- verbatim: !!targetEntryOf(run, i, k)?.verbatim, modifiers: modifiers || [], settings };
5871
+ verbatim: !!targetEntryOf(run, i, k)?.verbatim, modifiers: stageModifiers(ch, resolveFilters), settings };
5646
5872
  });
5647
5873
  const connections = run.jobs.map((_, k) => {
5648
5874
  // A step answered here asks nothing: the profile a run made before it
@@ -6039,9 +6265,13 @@ CONNECTION_FIELDS, SETTING_KEYS, OPTION_FIELDS, jobLabel, scenarioLabel, jobWord
6039
6265
  contentOf, withContent, replyOf,
6040
6266
  targetsOf, targetLetter, targetLabel, targetStepOf, targetEntryOf, jobTargetType, targetProfileOf, recordedTargetOf, withTarget,
6041
6267
  tokensOf, withTokens, sendsImage, withImage, flowStepOf, readAsOf, outOf, withOut, slotEntry, SLOTS,
6268
+ filterSetsOf, withFilterSets, stageModifiers,
6042
6269
  blankPipeline, upgradePipeline, fatProfileRef, newId, newEval, newLink, validatePipeline, resolvePipeline, pipelineOfRun, stagesFor, profileIds,
6043
6270
  scenarioProfile, lastKind, lastModifier, modifierSummary, itemScores, scenarioEvals, scenarioPasses, groupPass, overallVerdict, passVerdict, isSkipped, failedScore,
6044
6271
  evalLabel, evalsOf, evalsDataset, casesRef, groupOf, groupOfMetrics, pipelineWarnings, isWholeRun, EVAL_FIELDS,
6045
6272
  pipelineToYaml, importPipeline, yamlToPipeline, exportBundle, readBundle, bundleProblems, junitXml,
6046
6273
  pluginHost, legacyTagsOut, splitItems, tidy, tryExamples, itemRulesProblems, dropBy, rejectBy, matcher,
6274
+ FILTER_SET_VERSION, FILTER_TYPES, FILTER_SEPARATORS, applyFilter, applyFilterSet, tryFilter, splitSample,
6275
+ filterProblems, filterFromItemRule, upgradeFilterSetBody, blankFilterSetBody, filterSetBodyProblems, filterSetLinkProblems,
6276
+ newFilterLink, FILTER_MODIFIER,
6047
6277
  };