@cef-ai/cli 4.2.0 → 4.3.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/README.md +1 -1
- package/dist/build/lints.d.ts +12 -0
- package/dist/build/lints.d.ts.map +1 -1
- package/dist/build/lints.js +23 -0
- package/dist/build/lints.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +11 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/eval.d.ts +96 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +329 -2
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/eval.test.js +267 -3
- package/dist/commands/eval.test.js.map +1 -1
- package/dist/lib/eval-target.d.ts +2 -0
- package/dist/lib/eval-target.d.ts.map +1 -1
- package/dist/lib/eval-target.js +2 -1
- package/dist/lib/eval-target.js.map +1 -1
- package/package.json +5 -5
package/dist/commands/eval.js
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
* cef eval run <dataset> [--dataset-version N] [--workflow-version v] [--repeats 3] [--concurrency 4] [--baseline exp]
|
|
9
9
|
* cef eval experiments [dataset] experiment history
|
|
10
10
|
* cef eval compare <a> <b> how experiment b differs from a
|
|
11
|
+
* cef eval show <experiment> one experiment's results, per case
|
|
12
|
+
* cef eval cancel <experiment> --yes mark a stuck/running experiment cancelled (ROC's "Mark cancelled")
|
|
13
|
+
* cef eval baseline <dataset> [exp] [--reset] set (or clear) the dataset's baseline
|
|
14
|
+
* cef eval versions <dataset> the dataset's version history
|
|
15
|
+
* cef eval archive <dataset> <caseId> [--undo] hide a case from new versions
|
|
16
|
+
* cef eval check <dataset> [--workflow-version v] Result-compatibility check (what run prints first)
|
|
17
|
+
* cef eval capture <dataset> <context...> [--id c] [--workflow-version v] [--dry-run]
|
|
18
|
+
* build cases from real runs (ROC's "Add from executions")
|
|
11
19
|
*
|
|
12
20
|
* Every command is about one workflow: `--workflow <id>`, by default the one
|
|
13
21
|
* `cef.config.ts` (or the build output) in the current directory declares.
|
|
@@ -35,11 +43,11 @@
|
|
|
35
43
|
*/
|
|
36
44
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
37
45
|
import path from "node:path";
|
|
38
|
-
import { canonicalJson, casesFromTable, checkResultCompatibility, compareExperiments, createDataset, cutVersion, deleteCase, findExperiment, getCaseRevision, getHeader, getVersion, listArchivedCaseIds, listCaseRevisions, listDatasets, listDeletedCaseIds, listExperiments, listRunRecords, listVersions, listWorkflows, loadWorkingSet, parseCase, parseDelimited, parseHeader, resultFieldsFromGraph, runExperiment, saveCase, sha256Hex, unarchiveCase, undeleteCase, updateHeader, } from "@cef-ai/eval";
|
|
46
|
+
import { addCase, archiveCase, canonicalJson, caseFromRun, capturedFromEvents, casesFromTable, checkResultCompatibility, compareExperiments, createDataset, cutVersion, deleteCase, findExperiment, getCaseRevision, getHeader, isCaseId, listCaseIds, slugCaseId, uniqueCaseId, getVersion, listArchivedCaseIds, listCaseRevisions, listDatasets, listDeletedCaseIds, listExperiments, listRunRecords, listVersions, listWorkflows, loadWorkingSet, markCancelled, parseCase, parseDelimited, parseHeader, resultFieldsFromGraph, runExperiment, saveCase, setBaseline, sha256Hex, unarchiveCase, undeleteCase, updateHeader, versionHistory, } from "@cef-ai/eval";
|
|
39
47
|
import { Browse, Events } from "@cef-ai/vault-sdk/internal";
|
|
40
48
|
import { loadWallet } from "../lib/eval-auth.js";
|
|
41
49
|
import { openVault, vaultEvalStore } from "../lib/eval-vault.js";
|
|
42
|
-
import { fetchOrchestrator, getDeploymentSet, liveOf, nodeWorkflowTarget, workflowAgentId, } from "../lib/eval-target.js";
|
|
50
|
+
import { allEvents, fetchOrchestrator, getDeploymentSet, liveOf, nodeWorkflowTarget, workflowAgentId, } from "../lib/eval-target.js";
|
|
43
51
|
import { DEFAULT_WORKFLOW_SCOPE, localGraph, localWorkflowScope, resolveWorkflowId, } from "../lib/eval-workflow.js";
|
|
44
52
|
import { envConfig } from "../lib/env.js";
|
|
45
53
|
/* ------------------------------------------------------------------------ */
|
|
@@ -147,6 +155,88 @@ export async function evalImport(opts) {
|
|
|
147
155
|
}
|
|
148
156
|
return { created, added, skipped, warnings, ...(cut ? { cut } : {}) };
|
|
149
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Build eval cases from real runs — the CLI equivalent of ROC's "Add from
|
|
160
|
+
* executions". For each context it reads the run's event stream from the vault
|
|
161
|
+
* (`vaultEvents`; no orchestrator, no `--as-pubkey`) and WRITES the resulting
|
|
162
|
+
* cases:
|
|
163
|
+
* - input = the run's `workflow.start` event payload (an inline `graph` dropped);
|
|
164
|
+
* - output = the `workflow.completed` event whose `runId` matches this run
|
|
165
|
+
* (older runners that stamp no `runId` are not matched), turned into
|
|
166
|
+
* the expectation to be edited afterwards.
|
|
167
|
+
* `--workflow-version` is stamped on each case's `source` but does NOT change the
|
|
168
|
+
* read scope. Unlike ROC's `captureRun`, which reads the runner's first task,
|
|
169
|
+
* this reads the `workflow.start` event.
|
|
170
|
+
*
|
|
171
|
+
* IDEMPOTENT: a context already recorded as a case's `source.context` is skipped,
|
|
172
|
+
* so re-running the same contexts writes nothing; the same context named twice in
|
|
173
|
+
* one call yields one case (the first occurrence).
|
|
174
|
+
*
|
|
175
|
+
* PLAN THEN WRITE, like ROC's `planCases`: every case is built and its id
|
|
176
|
+
* resolved before anything is saved, and `--id` is validated up front (format,
|
|
177
|
+
* single context, not already taken) before any event is read, so a refusal never
|
|
178
|
+
* leaves half the runs written. A run with no `workflow.start` cannot become a
|
|
179
|
+
* case — it is skipped with a note, not fatal. `dryRun` stops after planning.
|
|
180
|
+
* Auto ids come from the context (`slugCaseId`) and are made unique against the
|
|
181
|
+
* dataset's live, archived and deleted ids and the batch so far; the write itself
|
|
182
|
+
* uses `addCase`, which refuses an id that already exists.
|
|
183
|
+
*/
|
|
184
|
+
export async function evalCapture(opts) {
|
|
185
|
+
if (opts.id !== undefined && opts.contexts.length !== 1) {
|
|
186
|
+
throw new Error("[cef eval] --id names one case; pass a single context");
|
|
187
|
+
}
|
|
188
|
+
if (!(await getHeader(opts.store, opts.workflowId, opts.dataset))) {
|
|
189
|
+
throw new Error(`[cef eval] workflow "${opts.workflowId}" has no dataset "${opts.dataset}"`);
|
|
190
|
+
}
|
|
191
|
+
// Every id a new case may not take: live, archived and deleted alike; plus the
|
|
192
|
+
// working set, to skip contexts already captured as a case's source.
|
|
193
|
+
const [live, deleted, working] = await Promise.all([
|
|
194
|
+
listCaseIds(opts.store, opts.workflowId, opts.dataset, { includeArchived: true }),
|
|
195
|
+
listDeletedCaseIds(opts.store, opts.workflowId, opts.dataset),
|
|
196
|
+
loadWorkingSet(opts.store, opts.workflowId, opts.dataset),
|
|
197
|
+
]);
|
|
198
|
+
const taken = new Set([...live, ...deleted]);
|
|
199
|
+
// `--id` up front, before any event is read: format, and not already taken.
|
|
200
|
+
if (opts.id !== undefined) {
|
|
201
|
+
if (!isCaseId(opts.id))
|
|
202
|
+
throw new Error(`[cef eval] "${opts.id}" is not a case id`);
|
|
203
|
+
if (taken.has(opts.id))
|
|
204
|
+
throw new Error(`[cef eval] "${opts.id}" already exists in ${opts.dataset}`);
|
|
205
|
+
}
|
|
206
|
+
const captured = new Set(working.cases.map(({ case: c }) => c.source?.context).filter((c) => !!c));
|
|
207
|
+
const planned = [];
|
|
208
|
+
const skipped = [];
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (const context of opts.contexts) {
|
|
211
|
+
if (seen.has(context))
|
|
212
|
+
continue; // the same context twice in one call → one case
|
|
213
|
+
seen.add(context);
|
|
214
|
+
if (captured.has(context)) {
|
|
215
|
+
skipped.push(`${context}: already captured as a case — nothing to add`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const run = capturedFromEvents(await opts.vaultEvents(context), context, opts.workflowVersion);
|
|
219
|
+
if (run.problem) {
|
|
220
|
+
skipped.push(`${context}: ${run.problem}`);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const id = opts.id ?? uniqueCaseId(slugCaseId(context), taken);
|
|
224
|
+
taken.add(id);
|
|
225
|
+
planned.push(caseFromRun({
|
|
226
|
+
input: run.input,
|
|
227
|
+
context,
|
|
228
|
+
...(run.output != null ? { output: run.output } : {}),
|
|
229
|
+
...(run.workflowVersion ? { workflowVersion: run.workflowVersion } : {}),
|
|
230
|
+
id,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
// `addCase` refuses an id that already exists, so a derived or `--id` id that
|
|
234
|
+
// collides errors instead of appending a revision to someone else's case.
|
|
235
|
+
if (!opts.dryRun)
|
|
236
|
+
for (const c of planned)
|
|
237
|
+
await addCase(opts.store, opts.workflowId, opts.dataset, c);
|
|
238
|
+
return { dataset: opts.dataset, added: planned, skipped };
|
|
239
|
+
}
|
|
150
240
|
/** The pulled state in the repo dir, when it is this dataset's. */
|
|
151
241
|
async function readState(dir, dataset) {
|
|
152
242
|
const raw = (await readJsonFile(path.join(datasetDir(dir, dataset), VERSIONS_FILE)));
|
|
@@ -466,6 +556,73 @@ export async function evalCompare(opts) {
|
|
|
466
556
|
};
|
|
467
557
|
return compareExperiments(await load(opts.base), await load(opts.candidate));
|
|
468
558
|
}
|
|
559
|
+
/** One experiment with its per-case run records. Found by id alone (no workflow needed), like `compare`. */
|
|
560
|
+
export async function evalShow(opts) {
|
|
561
|
+
const experiment = await findExperiment(opts.store, opts.id);
|
|
562
|
+
if (!experiment)
|
|
563
|
+
throw new Error(`[cef eval] experiment "${opts.id}" not found`);
|
|
564
|
+
return { experiment, runs: await listRunRecords(opts.store, experiment.workflowId, experiment.id) };
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Mark a stuck/running experiment cancelled — the CLI equivalent of ROC's
|
|
568
|
+
* "Mark cancelled". Found by id alone (no workflow needed), like `show`. A vault
|
|
569
|
+
* write: any workflow-version pin the experiment holds is left to expire on its
|
|
570
|
+
* own, so no pin is released here. Returns both the experiment as it was and as
|
|
571
|
+
* it is now, so the caller can tell "we cancelled it" from "it was already
|
|
572
|
+
* terminal" or "it turned out complete and was recorded done".
|
|
573
|
+
*/
|
|
574
|
+
export async function evalCancel(opts) {
|
|
575
|
+
const before = await findExperiment(opts.store, opts.experiment);
|
|
576
|
+
if (!before)
|
|
577
|
+
throw new Error(`[cef eval] experiment "${opts.experiment}" not found`);
|
|
578
|
+
// The default no-op releasePin: the CLI does not hold or release pins.
|
|
579
|
+
const after = await markCancelled(opts.store, before.workflowId, opts.experiment, undefined);
|
|
580
|
+
return { before, after };
|
|
581
|
+
}
|
|
582
|
+
/** Make `experiment` the dataset's baseline, or clear it with `reset`. */
|
|
583
|
+
export async function evalBaseline(opts) {
|
|
584
|
+
if (opts.experiment && opts.reset) {
|
|
585
|
+
throw new Error("[cef eval] pass an experiment or --reset, not both");
|
|
586
|
+
}
|
|
587
|
+
if (!opts.reset && !opts.experiment) {
|
|
588
|
+
throw new Error("[cef eval] baseline <experiment> is required (or --reset to clear the baseline)");
|
|
589
|
+
}
|
|
590
|
+
if (!opts.reset) {
|
|
591
|
+
// A baseline is this dataset's own experiment — a typo or another dataset's id is refused.
|
|
592
|
+
const exp = await findExperiment(opts.store, opts.experiment);
|
|
593
|
+
if (!exp)
|
|
594
|
+
throw new Error(`[cef eval] experiment "${opts.experiment}" not found`);
|
|
595
|
+
if (exp.workflowId !== opts.workflowId || exp.dataset !== opts.dataset) {
|
|
596
|
+
throw new Error(`[cef eval] experiment "${opts.experiment}" is not an experiment of ${opts.workflowId}/${opts.dataset}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// `null` clears it; `setBaseline` also checks the id's format.
|
|
600
|
+
return setBaseline(opts.store, opts.workflowId, opts.dataset, opts.reset ? null : opts.experiment);
|
|
601
|
+
}
|
|
602
|
+
/** The dataset's versions, newest first, each with what changed and how many experiments ran it. */
|
|
603
|
+
export async function evalVersions(opts) {
|
|
604
|
+
// A missing dataset has no versions either way; say so, rather than "No versions." as if it existed.
|
|
605
|
+
if (!(await getHeader(opts.store, opts.workflowId, opts.dataset))) {
|
|
606
|
+
throw new Error(`[cef eval] workflow "${opts.workflowId}" has no dataset "${opts.dataset}"`);
|
|
607
|
+
}
|
|
608
|
+
return versionHistory(opts.store, opts.workflowId, opts.dataset);
|
|
609
|
+
}
|
|
610
|
+
/** Archive a case (hide it from new versions) or, with `undo`, unarchive it. */
|
|
611
|
+
export async function evalArchive(opts) {
|
|
612
|
+
// The case must exist in the dataset — live, archived or deleted — so --undo on a
|
|
613
|
+
// typo is refused rather than printing "✓ Unarchived" for a case that never was.
|
|
614
|
+
const [ids, deleted] = await Promise.all([
|
|
615
|
+
listCaseIds(opts.store, opts.workflowId, opts.dataset, { includeArchived: true }),
|
|
616
|
+
listDeletedCaseIds(opts.store, opts.workflowId, opts.dataset),
|
|
617
|
+
]);
|
|
618
|
+
if (!ids.includes(opts.caseId) && !deleted.includes(opts.caseId)) {
|
|
619
|
+
throw new Error(`[cef eval] dataset "${opts.dataset}" has no case "${opts.caseId}"`);
|
|
620
|
+
}
|
|
621
|
+
if (opts.undo)
|
|
622
|
+
await unarchiveCase(opts.store, opts.workflowId, opts.dataset, opts.caseId);
|
|
623
|
+
else
|
|
624
|
+
await archiveCase(opts.store, opts.workflowId, opts.dataset, opts.caseId);
|
|
625
|
+
}
|
|
469
626
|
/**
|
|
470
627
|
* The Result a workflow version declares, read off its graph:
|
|
471
628
|
* - the live deployment row's `params.graph` when the version is the live
|
|
@@ -547,6 +704,23 @@ export function formatExperiment(e) {
|
|
|
547
704
|
: []),
|
|
548
705
|
].join("\n");
|
|
549
706
|
}
|
|
707
|
+
/** An experiment's per-case runs, one row each (case, repeat, status, verdict, cost). */
|
|
708
|
+
export function formatRunRecords(runs) {
|
|
709
|
+
if (!runs.length)
|
|
710
|
+
return "No runs.";
|
|
711
|
+
const verdict = (r) => (r.pass === null ? "–" : r.pass ? "pass" : "FAIL");
|
|
712
|
+
return table(["CASE", "REPEAT", "STATUS", "VERDICT", "OUTCOME", "DURATION", "TOKENS"], [...runs]
|
|
713
|
+
.sort((a, b) => a.caseId.localeCompare(b.caseId) || a.repeat - b.repeat)
|
|
714
|
+
.map((r) => [r.caseId, r.repeat, r.status, verdict(r), r.outcome, ms(r.metrics.durationMs), r.metrics.totalTokens]));
|
|
715
|
+
}
|
|
716
|
+
/** A dataset's version history, newest first — the columns of ROC's History table. */
|
|
717
|
+
export function formatVersionHistory(list) {
|
|
718
|
+
if (!list.length)
|
|
719
|
+
return "No versions.";
|
|
720
|
+
// Compact change counts against the previous version: added / edited / removed.
|
|
721
|
+
const changes = (v) => `+${v.diff.added.length} ~${v.diff.edited.length} -${v.diff.removed.length}`;
|
|
722
|
+
return table(["VERSION", "CREATED", "CASES", "CHANGES", "EXPERIMENTS", "NOTE"], list.map((v) => [`v${v.version}`, v.createdAt.replace("T", " ").slice(0, 16), v.cases, changes(v), v.experiments, v.note]));
|
|
723
|
+
}
|
|
550
724
|
/** "Dataset changed between these experiments: …" — or nothing when it did not. */
|
|
551
725
|
export function formatDatasetDiff(c) {
|
|
552
726
|
const d = c.datasetDiff;
|
|
@@ -766,6 +940,40 @@ export function addEvalCommands(program, opts) {
|
|
|
766
940
|
opts.onError(e);
|
|
767
941
|
}
|
|
768
942
|
});
|
|
943
|
+
withWorkflow(ev.command("capture <dataset> <context...>")
|
|
944
|
+
.description("Build cases from real runs' input and output, and add them to the dataset (ROC's \"Add from executions\")"))
|
|
945
|
+
.option("--id <caseId>", "Name the case (only with a single context); otherwise derived from the context")
|
|
946
|
+
.option("--workflow-version <v>", "Stamp this workflow version on each case's source")
|
|
947
|
+
.option("--dry-run", "Build and print what would be added, write nothing")
|
|
948
|
+
.action(async (dataset, contexts, o) => {
|
|
949
|
+
try {
|
|
950
|
+
const workflowId = await workflowOf(o);
|
|
951
|
+
// Reads the run's events from the vault and writes cases: no wallet, no orchestrator, no --as-pubkey.
|
|
952
|
+
const vault = await openVault(o, undefined, opts.fetchImpl);
|
|
953
|
+
const scope = await scopeOf(o, workflowId);
|
|
954
|
+
const store = vaultEvalStore(vault, scope);
|
|
955
|
+
const browse = new Browse(vault.http);
|
|
956
|
+
const reader = {
|
|
957
|
+
events: (context, cursor) => browse.listStreamEvents(vault.vaultId, scope, context, { limit: 200, ...(cursor ? { cursor } : {}) }),
|
|
958
|
+
};
|
|
959
|
+
const r = await evalCapture({
|
|
960
|
+
store, vaultEvents: (context) => allEvents(reader, context),
|
|
961
|
+
workflowId, dataset, contexts,
|
|
962
|
+
...(o.id !== undefined ? { id: o.id } : {}),
|
|
963
|
+
...(o.workflowVersion ? { workflowVersion: o.workflowVersion } : {}),
|
|
964
|
+
dryRun: !!o.dryRun,
|
|
965
|
+
});
|
|
966
|
+
print(o.json, r, () => [
|
|
967
|
+
o.dryRun ? "• Dry run — nothing written" : null,
|
|
968
|
+
`${o.dryRun ? "Would add" : "✓ Added"} ${plural(r.added.length, "case")}`
|
|
969
|
+
+ (r.added.length ? `: ${r.added.map((c) => c.id).join(", ")}` : ""),
|
|
970
|
+
...(r.skipped.length ? [" skipped:", ...r.skipped.map((s) => ` ${s}`)] : []),
|
|
971
|
+
].filter((l) => l !== null).join("\n"));
|
|
972
|
+
}
|
|
973
|
+
catch (e) {
|
|
974
|
+
opts.onError(e);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
769
977
|
withDir(withWorkflow(ev.command("pull <dataset>").description("Write a dataset's live cases to the repo dir")))
|
|
770
978
|
.option("--force", "Overwrite case files edited locally since the last pull/push")
|
|
771
979
|
.action(async (dataset, o) => {
|
|
@@ -919,5 +1127,124 @@ export function addEvalCommands(program, opts) {
|
|
|
919
1127
|
opts.onError(e);
|
|
920
1128
|
}
|
|
921
1129
|
});
|
|
1130
|
+
// An experiment id is global, so the workflow is optional here (as in `compare`).
|
|
1131
|
+
withWorkflow(ev.command("show <experiment>").description("One experiment's results: summary, per-field accuracy and per-case runs"))
|
|
1132
|
+
.action(async (experiment, o) => {
|
|
1133
|
+
try {
|
|
1134
|
+
const workflowId = await workflowOf(o).catch(() => undefined);
|
|
1135
|
+
const { experiment: exp, runs } = await evalShow({ store: await openStore(o, workflowId), id: experiment });
|
|
1136
|
+
print(o.json, { experiment: exp, runs }, () => [formatExperiment(exp), "", formatRunRecords(runs)].join("\n"));
|
|
1137
|
+
}
|
|
1138
|
+
catch (e) {
|
|
1139
|
+
opts.onError(e);
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
// An experiment id is global, so the workflow is optional here (as in `show`).
|
|
1143
|
+
withWorkflow(ev.command("cancel <experiment>")
|
|
1144
|
+
.description("Mark a stuck/running experiment cancelled (the CLI's \"Mark cancelled\")"))
|
|
1145
|
+
.option("--yes", "Confirm: write the experiment record cancelled now")
|
|
1146
|
+
.addHelpText("after", "\ncancel writes the experiment record now. Only do this if nothing is still running it\n"
|
|
1147
|
+
+ "(a ROC tab or another \"cef eval run --resume\") — that would flip it back to done/error.\n"
|
|
1148
|
+
+ "Pass --yes to confirm.\n"
|
|
1149
|
+
+ "\nWrites only the experiment record. A workflow-version pin it may hold is not released here;\n"
|
|
1150
|
+
+ "it expires on its own (~6h).")
|
|
1151
|
+
.action(async (experiment, o) => {
|
|
1152
|
+
try {
|
|
1153
|
+
// Refuse until confirmed: cancelling a record that is still being run flips it back.
|
|
1154
|
+
if (!o.yes) {
|
|
1155
|
+
throw new Error("[cef eval] cancel writes the experiment record now. Only do this if nothing is still running it "
|
|
1156
|
+
+ "(a ROC tab or another \"cef eval run --resume\") — that would flip it back to done/error. "
|
|
1157
|
+
+ "Pass --yes to confirm.");
|
|
1158
|
+
}
|
|
1159
|
+
const workflowId = await workflowOf(o).catch(() => undefined);
|
|
1160
|
+
const store = await openStore(o, workflowId);
|
|
1161
|
+
const { before, after } = await evalCancel({ store, experiment });
|
|
1162
|
+
// The message turns on what it was before: --json carries the resulting record.
|
|
1163
|
+
print(o.json, after, () => before.status !== "running"
|
|
1164
|
+
? `• Experiment ${experiment} is ${before.status} — nothing to cancel`
|
|
1165
|
+
: after.status === "done"
|
|
1166
|
+
? `✓ Experiment ${experiment} was already complete — recorded as done`
|
|
1167
|
+
: `✓ Experiment ${experiment} cancelled`);
|
|
1168
|
+
}
|
|
1169
|
+
catch (e) {
|
|
1170
|
+
opts.onError(e);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
// `[experiment]` is optional so `--reset` needs no placeholder; it is required otherwise.
|
|
1174
|
+
withWorkflow(ev.command("baseline <dataset> [experiment]").description("Set the dataset's baseline experiment (--reset to clear it)"))
|
|
1175
|
+
.option("--reset", "Clear the dataset's baseline")
|
|
1176
|
+
.action(async (dataset, experiment, o) => {
|
|
1177
|
+
try {
|
|
1178
|
+
const workflowId = await workflowOf(o);
|
|
1179
|
+
const store = await openStore(o, workflowId);
|
|
1180
|
+
const header = await evalBaseline({ store, workflowId, dataset, ...(experiment ? { experiment } : {}), reset: !!o.reset });
|
|
1181
|
+
print(o.json, header, () => o.reset ? `✓ Baseline for ${dataset} reset` : `✓ Baseline for ${dataset} set to ${experiment}`);
|
|
1182
|
+
}
|
|
1183
|
+
catch (e) {
|
|
1184
|
+
opts.onError(e);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
withWorkflow(ev.command("versions <dataset>").description("The dataset's version history"))
|
|
1188
|
+
.action(async (dataset, o) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const workflowId = await workflowOf(o);
|
|
1191
|
+
const store = await openStore(o, workflowId);
|
|
1192
|
+
const list = await evalVersions({ store, workflowId, dataset });
|
|
1193
|
+
print(o.json, list, () => formatVersionHistory(list));
|
|
1194
|
+
}
|
|
1195
|
+
catch (e) {
|
|
1196
|
+
opts.onError(e);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
withWorkflow(ev.command("archive <dataset> <caseId>")
|
|
1200
|
+
.description("Archive a case: hide it from new versions (cef eval push --restore also unarchives from the repo dir)"))
|
|
1201
|
+
.option("--undo", "Unarchive the case instead")
|
|
1202
|
+
.action(async (dataset, caseId, o) => {
|
|
1203
|
+
try {
|
|
1204
|
+
const workflowId = await workflowOf(o);
|
|
1205
|
+
const store = await openStore(o, workflowId);
|
|
1206
|
+
await evalArchive({ store, workflowId, dataset, caseId, undo: !!o.undo });
|
|
1207
|
+
print(o.json, { dataset, caseId, archived: !o.undo }, () => (o.undo ? `✓ Unarchived ${caseId}` : `✓ Archived ${caseId}`));
|
|
1208
|
+
}
|
|
1209
|
+
catch (e) {
|
|
1210
|
+
opts.onError(e);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
withWorkflow(ev.command("check <dataset>").description("Check the dataset's live cases against the workflow version's declared Result"))
|
|
1214
|
+
.option("--workflow-version <v>", "Workflow version (default: the live one)")
|
|
1215
|
+
.option("--as-pubkey <hex>", "Agent service pubkey the workflow belongs to ($CEF_AS_PUBKEY); needed only to resolve the live version (not with --workflow-version)")
|
|
1216
|
+
.option("--orchestrator <url>", "Orchestrator base URL ($CEF_ENDPOINT; default from --env)")
|
|
1217
|
+
.action(async (dataset, o) => {
|
|
1218
|
+
try {
|
|
1219
|
+
const workflowId = await workflowOf(o);
|
|
1220
|
+
// No wallet needed to read: the vault takes an access token.
|
|
1221
|
+
const vault = await openVault(o, undefined, opts.fetchImpl);
|
|
1222
|
+
const scope = await scopeOf(o, workflowId);
|
|
1223
|
+
const store = vaultEvalStore(vault, scope);
|
|
1224
|
+
const header = await getHeader(store, workflowId, dataset);
|
|
1225
|
+
if (!header)
|
|
1226
|
+
throw new Error(`[cef eval] workflow "${workflowId}" has no dataset "${dataset}"`);
|
|
1227
|
+
let workflowVersion;
|
|
1228
|
+
let result;
|
|
1229
|
+
if (o.workflowVersion) {
|
|
1230
|
+
// A named version resolves from the LOCAL graph (cef.config.ts or dist/) — no
|
|
1231
|
+
// orchestrator, so no --as-pubkey.
|
|
1232
|
+
workflowVersion = o.workflowVersion;
|
|
1233
|
+
result = await resultFieldsFor({ workflowId, workflowVersion, local: local() });
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
// The live version: through the orchestrator (needs --as-pubkey), as `run` does.
|
|
1237
|
+
const { orchestrator, agentId } = openTarget(o, vault, scope, workflowId, opts.fetchImpl);
|
|
1238
|
+
const liveRow = liveOf((await getDeploymentSet(orchestrator, agentId))?.deployments).row;
|
|
1239
|
+
workflowVersion = liveRow?.version ?? "latest";
|
|
1240
|
+
result = await resultFieldsFor({ workflowId, workflowVersion, ...(liveRow ? { liveRow } : {}), local: local() });
|
|
1241
|
+
}
|
|
1242
|
+
const check = await evalResultCheck({ store, workflowId, dataset, workflowVersion, result });
|
|
1243
|
+
print(o.json, check, () => formatResultCheck(check).join("\n"));
|
|
1244
|
+
}
|
|
1245
|
+
catch (e) {
|
|
1246
|
+
opts.onError(e);
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
922
1249
|
}
|
|
923
1250
|
//# sourceMappingURL=eval.js.map
|