@eigenpal/cli 0.4.8 → 0.4.10
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 +34 -0
- package/dist/cli.js +439 -85
- package/dist/skill/SKILL.md +76 -6
- package/dist/skill/reference/cli/workflow.md +43 -17
- package/dist/skill/reference/evaluators.md +96 -28
- package/dist/skill/reference/step-types.md +0 -1
- package/dist/skill/reference/workflow-yaml.md +1 -0
- package/dist/templates/pdf-extraction/evaluators.yaml +1 -0
- package/dist/templates/text-classification/evaluators.yaml +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -141,6 +141,16 @@
|
|
|
141
141
|
|
|
142
142
|
Non-overlapping examples are listed in dedicated `only in A` / `only in B` blocks before the table so |Δ| sort stays meaningful.
|
|
143
143
|
|
|
144
|
+
- 9dc84b9: CLI: agent-friendly experiment polling, watching, and comparison.
|
|
145
|
+
|
|
146
|
+
Four small additions so agents (and humans writing monitoring scripts) don't have to roll their own bash pollers, JSON parsers, or aggregators on top of `experiment status`.
|
|
147
|
+
- **`experiment status --json` now includes a top-level `summary` rollup** — `{ total, terminal, complete, completedCount, failedCount, cancelledCount, rejectedCount, runningCount, pendingCount }`. Polling scripts can read `.summary.complete` / `.summary.failedCount` directly instead of folding `executions[].status` themselves. `executions[]` stays as-is for backwards compat.
|
|
148
|
+
- **`experiment status --short`** — single-line, awk-friendly stdout summary (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Mutually exclusive with `--watch`. Drops the failure detail block + closing tip so monitoring loops stay clean.
|
|
149
|
+
- **New `experiment watch <workflow-id> <batchId>`** — polls until terminal, then auto-pulls eval results to `./results-<batchId>.<format>` in one command. Replaces the old "monitor + status + pull + score" recipe. Flags: `--interval`, `--max-wait`, `--format csv|json`, `--pull-on-complete <path>` to override destination, `--no-pull` to opt out. Exit codes match `experiment status --watch` (0 clean, 1 any failure, 2 deadline).
|
|
150
|
+
- **`experiment compare` adds a per-evaluator aggregate** — a "Per-evaluator deltas" table renders above the existing per-row table with rows, mean Δ, regressions, and improvements per evaluator (sorted by |meanΔ| desc). `--json` exposes it at `summary.byEvaluator[]`.
|
|
151
|
+
|
|
152
|
+
Updated SKILL.md so the canonical "run an experiment + collect results" recipe uses `experiment watch` and adds a "monitoring scripts" section that calls out `--short` and `summary.complete`.
|
|
153
|
+
|
|
144
154
|
- 3da0612: CLI: pretty default rendering for `list`, `get`, and mutating commands; `--json` toggle for piping.
|
|
145
155
|
- `workflow definition/dataset/experiment/version/step-type list` and `workflow execution list` render aligned ASCII tables. `--json` restores the legacy raw payload.
|
|
146
156
|
- `workflow execution get` (no other flags) prints the vertical step list via `renderFrame` instead of dumping 1000+ lines of JSON. `--json` for the full payload; `--output-path` / `--step` / `--include` continue to print filtered JSON.
|
|
@@ -201,6 +211,25 @@
|
|
|
201
211
|
|
|
202
212
|
`@` path syntax (`--code @file.js`, `--inputs items=@arr.json`) matches `curl -d @file` / `gh --body-file -`. Bare paths and `-` (stdin) work too.
|
|
203
213
|
|
|
214
|
+
- 4137eb8: custom-script evaluator: the YAML now carries the entire
|
|
215
|
+
`function scoreScript(expected, actual) { ... }` declaration in a single
|
|
216
|
+
`function` field. Previous formats (`code`, then `body`) wrapped the
|
|
217
|
+
text implicitly; the new shape stores the function declaration verbatim
|
|
218
|
+
so the YAML matches the dashboard editor 1-for-1.
|
|
219
|
+
|
|
220
|
+
`workflow validate` and `workflow evaluators validate` flag YAML where
|
|
221
|
+
the function fails to parse, the signature does not match
|
|
222
|
+
`function scoreScript(expected, actual)` exactly, or the body contains
|
|
223
|
+
neither `return` nor `throw` — same checks the dashboard runs at form
|
|
224
|
+
save. The function must return a number in [0, 1]; throws are caught
|
|
225
|
+
and scored as 0.
|
|
226
|
+
|
|
227
|
+
Existing evaluator YAML must be ported by hand: rename `body` (or
|
|
228
|
+
`code`) to `function`, and add the `function scoreScript(expected,
|
|
229
|
+
actual) { ... }` declaration around your statements. Parameter order is
|
|
230
|
+
load-bearing — swapping `expected` and `actual` would silently invert
|
|
231
|
+
every score.
|
|
232
|
+
|
|
204
233
|
- 3da0612: All list commands now support `--limit` / `--offset` pagination consistently.
|
|
205
234
|
- CLI: `workflow execution list`, `workflow version list`, and `workflow step-type list` previously had no `--offset` (or no pagination at all). All six list commands now accept `--limit`/`--offset` with defaults of 50/0.
|
|
206
235
|
- Server: `GET /api/workflows/:id/versions` now reads `?limit=` and `?offset=` query params (defaults 50/0; max limit 100). The `listVersions` repo method gained an optional `{ limit, offset }` argument.
|
|
@@ -273,6 +302,11 @@
|
|
|
273
302
|
|
|
274
303
|
Interactive profiles are unchanged: when you log in via `eigenpal auth login`, the profile stores both apiKey and baseUrl, and every subsequent command derives baseUrl from that profile. Set `EIGENPAL_PROFILE=<name>` to switch profiles per-shell without modifying state.
|
|
275
304
|
|
|
305
|
+
- 542fe0e: CLI: surface evaluator entry-level fields (`name`, `description`, `weight`) so agents and humans can discover the optional `description` field on every evaluator.
|
|
306
|
+
- `workflow evaluator-type get <type>` now emits `entrySchema` alongside `configSchema`. Inspect with `eigenpal workflow evaluator-type get llm-judge | jq '.entrySchema'`.
|
|
307
|
+
- The skill reference now renders a "Common entry fields" table per type and a "Writing descriptions for stakeholders" section explaining the convention: each evaluator's `description` should be a one-sentence, plain-language summary aimed at non-technical reviewers in the dashboard.
|
|
308
|
+
- `init` templates `pdf-extraction` and `text-classification` now ship a `description:` line on their evaluator entries.
|
|
309
|
+
|
|
276
310
|
- d41ee8b: CLI: fix `eigenpal status` error UX + harden `formatCliError` for real-world connection failures.
|
|
277
311
|
- `eigenpal status` against an unreachable base URL no longer mis-diagnoses the failure as an auth problem. Previously, `EIGENPAL_BASE_URL=http://localhost:9999 eigenpal status` printed "Authentication check failed; try `eigenpal auth login`" — the wrong rabbit hole. The status command now rethrows non-auth errors (connection refused, DNS NXDOMAIN, timeout, HTML responses, 5xx) so `formatCliError` upstream can surface the right hint: `Could not connect to the server at <url> ... Set EIGENPAL_BASE_URL or pass --base-url <url>.` Only real 401/403 responses are treated as auth failures.
|
|
278
312
|
- `formatCliError` now detects connection failures wrapped in any Error shape — bare `Error('ENOTFOUND ...')`, `Error` with `code: 'ECONNREFUSED'`, and `TypeError('fetch failed')` whose `.cause` carries the underlying error. Previously only `TypeError` whose message contained `fetch`/`URL` was recognized, so `getaddrinfo`-style errors fell through to the generic raw-message branch and lost the URL context.
|
package/dist/cli.js
CHANGED
|
@@ -21310,7 +21310,6 @@ var init_extract = __esm(() => {
|
|
|
21310
21310
|
prompt: exports_external.string().optional().describe("Custom prompt template for extraction"),
|
|
21311
21311
|
provider: exports_external.string().optional().describe('Provider ID to use (e.g., "openai-gpt4o", "anthropic-claude")'),
|
|
21312
21312
|
model: exports_external.string().optional().describe("Model to use (overrides provider default)"),
|
|
21313
|
-
temperature: exports_external.number().min(0).max(2).default(0).describe("Model temperature (0 = deterministic)"),
|
|
21314
21313
|
maxInputTokens: exports_external.number().int().optional().describe("Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit.")
|
|
21315
21314
|
});
|
|
21316
21315
|
});
|
|
@@ -21844,6 +21843,9 @@ var init_execution_row = __esm(() => {
|
|
|
21844
21843
|
];
|
|
21845
21844
|
});
|
|
21846
21845
|
|
|
21846
|
+
// ../types/src/llm-determinism.ts
|
|
21847
|
+
var DETERMINISTIC_TEMPERATURE = 0;
|
|
21848
|
+
|
|
21847
21849
|
// ../types/src/workflow/steps.ts
|
|
21848
21850
|
function parseStepType(type) {
|
|
21849
21851
|
const [category, operation] = type.split(".");
|
|
@@ -22110,7 +22112,6 @@ var init_step_configs = __esm(() => {
|
|
|
22110
22112
|
prompt: exports_external.string().optional().describe("Custom prompt template for extraction"),
|
|
22111
22113
|
provider: exports_external.string().optional().describe('Provider ID (e.g., "openai-gpt4o")'),
|
|
22112
22114
|
model: exports_external.string().optional().describe("Model override"),
|
|
22113
|
-
temperature: exports_external.number().min(0).max(2).default(0).optional().describe("Model temperature (0 = deterministic)"),
|
|
22114
22115
|
maxInputTokens: exports_external.number().int().optional().describe("Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit.")
|
|
22115
22116
|
});
|
|
22116
22117
|
AiExtractOutputSchema = exports_external.record(exports_external.string(), exports_external.unknown()).describe("Extracted structured data matching the provided schema");
|
|
@@ -22695,7 +22696,8 @@ var init_workflow = __esm(() => {
|
|
|
22695
22696
|
inputs: exports_external.array(WorkflowInputDefSchema).optional(),
|
|
22696
22697
|
steps: exports_external.array(StepSchema),
|
|
22697
22698
|
output: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
22698
|
-
settings: WorkflowSettingsSchema.optional()
|
|
22699
|
+
settings: WorkflowSettingsSchema.optional(),
|
|
22700
|
+
defaultModel: exports_external.string().optional()
|
|
22699
22701
|
});
|
|
22700
22702
|
StoredWorkflowDefinitionSchema = exports_external.object({
|
|
22701
22703
|
id: exports_external.string(),
|
|
@@ -23325,6 +23327,141 @@ var init_dataset_archive = __esm(() => {
|
|
|
23325
23327
|
};
|
|
23326
23328
|
});
|
|
23327
23329
|
|
|
23330
|
+
// ../types/src/eval/score-function.ts
|
|
23331
|
+
function stripLeadingTrivia(text) {
|
|
23332
|
+
let i = 0;
|
|
23333
|
+
while (i < text.length) {
|
|
23334
|
+
const ch = text[i];
|
|
23335
|
+
if (ch === " " || ch === "\t" || ch === `
|
|
23336
|
+
` || ch === "\r" || ch === "\v" || ch === "\f") {
|
|
23337
|
+
i++;
|
|
23338
|
+
continue;
|
|
23339
|
+
}
|
|
23340
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
23341
|
+
const nl = text.indexOf(`
|
|
23342
|
+
`, i + 2);
|
|
23343
|
+
if (nl === -1)
|
|
23344
|
+
return "";
|
|
23345
|
+
i = nl + 1;
|
|
23346
|
+
continue;
|
|
23347
|
+
}
|
|
23348
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
23349
|
+
const end = text.indexOf("*/", i + 2);
|
|
23350
|
+
if (end === -1)
|
|
23351
|
+
break;
|
|
23352
|
+
i = end + 2;
|
|
23353
|
+
continue;
|
|
23354
|
+
}
|
|
23355
|
+
break;
|
|
23356
|
+
}
|
|
23357
|
+
return text.slice(i);
|
|
23358
|
+
}
|
|
23359
|
+
function hasValidScoreSignature(fn) {
|
|
23360
|
+
return STRICT_HEADER_REGEX.test(stripLeadingTrivia(fn));
|
|
23361
|
+
}
|
|
23362
|
+
function isParseableScoreFunction(fn) {
|
|
23363
|
+
try {
|
|
23364
|
+
new Function(fn);
|
|
23365
|
+
return true;
|
|
23366
|
+
} catch {
|
|
23367
|
+
return false;
|
|
23368
|
+
}
|
|
23369
|
+
}
|
|
23370
|
+
function getScoreFunctionRuntimeIssue(fn) {
|
|
23371
|
+
if (/\basync\s+function\s+scoreScript\b/.test(fn)) {
|
|
23372
|
+
return "scoreScript must be synchronous (drop the `async` keyword — the sandbox calls it sync, so a Promise return scores 0)";
|
|
23373
|
+
}
|
|
23374
|
+
if (/\bimport\s*\(/.test(fn)) {
|
|
23375
|
+
return "dynamic `import(...)` is not supported in the score sandbox";
|
|
23376
|
+
}
|
|
23377
|
+
if (/\brequire\s*\(/.test(fn)) {
|
|
23378
|
+
return "`require(...)` is not available in the score sandbox (no Node module system)";
|
|
23379
|
+
}
|
|
23380
|
+
return null;
|
|
23381
|
+
}
|
|
23382
|
+
function extractScoreFunctionBody(fn) {
|
|
23383
|
+
const headerMatch = fn.match(ANY_HEADER_REGEX);
|
|
23384
|
+
if (!headerMatch || headerMatch.index === undefined) {
|
|
23385
|
+
return { body: fn, wrapperOk: false };
|
|
23386
|
+
}
|
|
23387
|
+
const start = headerMatch.index + headerMatch[0].length;
|
|
23388
|
+
let depth = 1;
|
|
23389
|
+
let i = start;
|
|
23390
|
+
let inLine = false;
|
|
23391
|
+
let inBlock = false;
|
|
23392
|
+
let strQuote = null;
|
|
23393
|
+
while (i < fn.length) {
|
|
23394
|
+
const ch = fn[i];
|
|
23395
|
+
const next = fn[i + 1];
|
|
23396
|
+
if (inLine) {
|
|
23397
|
+
if (ch === `
|
|
23398
|
+
`)
|
|
23399
|
+
inLine = false;
|
|
23400
|
+
} else if (inBlock) {
|
|
23401
|
+
if (ch === "*" && next === "/") {
|
|
23402
|
+
inBlock = false;
|
|
23403
|
+
i++;
|
|
23404
|
+
}
|
|
23405
|
+
} else if (strQuote) {
|
|
23406
|
+
if (ch === "\\")
|
|
23407
|
+
i++;
|
|
23408
|
+
else if (ch === strQuote)
|
|
23409
|
+
strQuote = null;
|
|
23410
|
+
} else if (ch === "/" && next === "/") {
|
|
23411
|
+
inLine = true;
|
|
23412
|
+
i++;
|
|
23413
|
+
} else if (ch === "/" && next === "*") {
|
|
23414
|
+
inBlock = true;
|
|
23415
|
+
i++;
|
|
23416
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
23417
|
+
strQuote = ch;
|
|
23418
|
+
} else if (ch === "{") {
|
|
23419
|
+
depth++;
|
|
23420
|
+
} else if (ch === "}") {
|
|
23421
|
+
depth--;
|
|
23422
|
+
if (depth === 0) {
|
|
23423
|
+
const inner = fn.slice(start, i).replace(/^\n+|\n+$/g, "");
|
|
23424
|
+
return { body: inner, wrapperOk: true };
|
|
23425
|
+
}
|
|
23426
|
+
}
|
|
23427
|
+
i++;
|
|
23428
|
+
}
|
|
23429
|
+
return { body: fn.slice(start), wrapperOk: false };
|
|
23430
|
+
}
|
|
23431
|
+
function defaultScoreFunction() {
|
|
23432
|
+
return `/**
|
|
23433
|
+
* Score the workflow output against the example's expected output.
|
|
23434
|
+
*
|
|
23435
|
+
* @param {Expected} expected the example's expected output
|
|
23436
|
+
* @param {Actual} actual the workflow's actual output
|
|
23437
|
+
* @returns {number} a score in [0, 1]; throws are caught and scored as 0
|
|
23438
|
+
*/
|
|
23439
|
+
function scoreScript(expected, actual) {
|
|
23440
|
+
// Return a number in [0, 1]; throws are caught and scored as 0.
|
|
23441
|
+
return JSON.stringify(actual) === JSON.stringify(expected) ? 1 : 0;
|
|
23442
|
+
}
|
|
23443
|
+
`;
|
|
23444
|
+
}
|
|
23445
|
+
function wrapBodyAsScoreFunction(body) {
|
|
23446
|
+
const trimmed = body.replace(/^\n+|\n+$/g, "");
|
|
23447
|
+
return `/**
|
|
23448
|
+
* Score the workflow output against the example's expected output.
|
|
23449
|
+
*
|
|
23450
|
+
* @param {Expected} expected the example's expected output
|
|
23451
|
+
* @param {Actual} actual the workflow's actual output
|
|
23452
|
+
* @returns {number} a score in [0, 1]; throws are caught and scored as 0
|
|
23453
|
+
*/
|
|
23454
|
+
function scoreScript(expected, actual) {
|
|
23455
|
+
${trimmed}
|
|
23456
|
+
}
|
|
23457
|
+
`;
|
|
23458
|
+
}
|
|
23459
|
+
var STRICT_HEADER_REGEX, ANY_HEADER_REGEX;
|
|
23460
|
+
var init_score_function = __esm(() => {
|
|
23461
|
+
STRICT_HEADER_REGEX = /^(?:async\s+)?function\s+scoreScript\s*\(\s*expected\s*,\s*actual\s*\)\s*\{/;
|
|
23462
|
+
ANY_HEADER_REGEX = /function\s+scoreScript\s*\([^)]*\)\s*\{/;
|
|
23463
|
+
});
|
|
23464
|
+
|
|
23328
23465
|
// ../types/src/eval/evaluator-config.ts
|
|
23329
23466
|
function combineEvalScores(results) {
|
|
23330
23467
|
let numerator = 0;
|
|
@@ -23339,9 +23476,10 @@ function combineEvalScores(results) {
|
|
|
23339
23476
|
return null;
|
|
23340
23477
|
return numerator / denominator;
|
|
23341
23478
|
}
|
|
23342
|
-
var EvaluatorTypeSchema, ScoreInUnitInterval, LLM_JUDGE_PROMPT_MIN_LENGTH = 10, ExactDiffConfigSchema, JudgeModeSchema, LabelsRecord, LlmJudgeConfigSchema, CustomScriptConfigSchema, EvaluatorBaseEntrySchema, EvaluatorConfigSchema, EvalConfigYamlSchema, EvaluatorResultSchema;
|
|
23479
|
+
var EvaluatorTypeSchema, ScoreInUnitInterval, LLM_JUDGE_PROMPT_MIN_LENGTH = 10, ExactDiffConfigSchema, JudgeModeSchema, LabelsRecord, LlmJudgeConfigSchema, CUSTOM_SCRIPT_CONTRACT, CUSTOM_SCRIPT_MAX_BYTES = 1e4, CUSTOM_SCRIPT_MAX_TIMEOUT_MS = 30000, CUSTOM_SCRIPT_MAX_MEMORY_MB = 50, CUSTOM_SCRIPT_FUNCTION_DESCRIPTION, CustomScriptConfigSchema, EvaluatorBaseEntrySchema, EvaluatorConfigSchema, EvalConfigYamlSchema, EvaluatorResultSchema;
|
|
23343
23480
|
var init_evaluator_config = __esm(() => {
|
|
23344
23481
|
init_zod();
|
|
23482
|
+
init_score_function();
|
|
23345
23483
|
EvaluatorTypeSchema = exports_external.enum(["exact-diff", "llm-judge", "custom-script"]);
|
|
23346
23484
|
ScoreInUnitInterval = exports_external.number().min(0, { message: "score must be >= 0" }).max(1, { message: "score must be <= 1" });
|
|
23347
23485
|
ExactDiffConfigSchema = exports_external.object({
|
|
@@ -23370,14 +23508,41 @@ var init_evaluator_config = __esm(() => {
|
|
|
23370
23508
|
}
|
|
23371
23509
|
}
|
|
23372
23510
|
});
|
|
23511
|
+
CUSTOM_SCRIPT_CONTRACT = {
|
|
23512
|
+
expected: "the example's expected output",
|
|
23513
|
+
actual: "the workflow's actual output",
|
|
23514
|
+
returns: "a number in [0, 1]",
|
|
23515
|
+
throws: "caught and scored as 0"
|
|
23516
|
+
};
|
|
23517
|
+
CUSTOM_SCRIPT_FUNCTION_DESCRIPTION = `Full JavaScript declaration of \`function scoreScript(expected, actual) { ... }\`. ` + `Receives \`expected\` (${CUSTOM_SCRIPT_CONTRACT.expected}) ` + `and \`actual\` (${CUSTOM_SCRIPT_CONTRACT.actual}), returns ${CUSTOM_SCRIPT_CONTRACT.returns}. ` + `Throws are ${CUSTOM_SCRIPT_CONTRACT.throws}.`;
|
|
23373
23518
|
CustomScriptConfigSchema = exports_external.object({
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23519
|
+
function: exports_external.string().min(1, { message: "required" }).max(CUSTOM_SCRIPT_MAX_BYTES, {
|
|
23520
|
+
message: `must be ≤ ${CUSTOM_SCRIPT_MAX_BYTES} bytes`
|
|
23521
|
+
}).describe(CUSTOM_SCRIPT_FUNCTION_DESCRIPTION).refine(isParseableScoreFunction, {
|
|
23522
|
+
message: "must parse as JavaScript"
|
|
23523
|
+
}).refine(hasValidScoreSignature, {
|
|
23524
|
+
message: "must declare `function scoreScript(expected, actual) { ... }` (parameter names + order matter)"
|
|
23525
|
+
}).refine((fn) => {
|
|
23526
|
+
const { body, wrapperOk } = extractScoreFunctionBody(fn);
|
|
23527
|
+
return wrapperOk && /\b(return|throw)\b/.test(body);
|
|
23528
|
+
}, {
|
|
23529
|
+
message: "function body must `return` a number in [0, 1] (or `throw` to score 0)"
|
|
23530
|
+
}).superRefine((fn, ctx) => {
|
|
23531
|
+
const issue2 = getScoreFunctionRuntimeIssue(fn);
|
|
23532
|
+
if (issue2)
|
|
23533
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: issue2 });
|
|
23534
|
+
}),
|
|
23535
|
+
timeoutMs: exports_external.number().int().positive().max(CUSTOM_SCRIPT_MAX_TIMEOUT_MS, {
|
|
23536
|
+
message: `must be ≤ ${CUSTOM_SCRIPT_MAX_TIMEOUT_MS} ms`
|
|
23537
|
+
}).default(5000).describe("Maximum wall-clock time the script can run before it's killed and the run is marked failed."),
|
|
23538
|
+
memoryLimitMb: exports_external.number().int().positive().max(CUSTOM_SCRIPT_MAX_MEMORY_MB, {
|
|
23539
|
+
message: `must be ≤ ${CUSTOM_SCRIPT_MAX_MEMORY_MB} MB`
|
|
23540
|
+
}).default(10).describe("Maximum memory the sandbox may allocate. Increase if the script processes large arrays or strings."),
|
|
23377
23541
|
passThreshold: ScoreInUnitInterval.default(1).describe("Minimum script score this evaluator must reach for a run to pass.")
|
|
23378
23542
|
});
|
|
23379
23543
|
EvaluatorBaseEntrySchema = exports_external.object({
|
|
23380
23544
|
name: exports_external.string().min(1).max(64).regex(/^[a-z0-9-]+$/, "name must be lowercase kebab-case").describe("Identifier used in eval results and the weighted-mean formula. Use a short kebab-case slug like header-exact or covenants-recall."),
|
|
23545
|
+
description: exports_external.string().trim().max(500).optional().describe("One-sentence explanation, in business language, of what this evaluator measures and why a stakeholder should care. Shown in the dashboard to non-technical reviewers (legal, ops, finance) who never see the YAML or judge prompt. Skip implementation detail (model names, thresholds, dot-paths); those live in `config`."),
|
|
23381
23546
|
weight: exports_external.number().min(0).default(1).describe("Relative importance in the weighted mean. The overall score is Σ(weightᵢ × scoreᵢ) / Σweight. Set to 0 to score-only without affecting overall.")
|
|
23382
23547
|
});
|
|
23383
23548
|
EvaluatorConfigSchema = exports_external.discriminatedUnion("type", [
|
|
@@ -23463,8 +23628,7 @@ async function judgeScoreJson(params) {
|
|
|
23463
23628
|
const response = await client.extract(userContent, {
|
|
23464
23629
|
schema: SCORE_SCHEMA,
|
|
23465
23630
|
prompt: systemPrompt,
|
|
23466
|
-
model
|
|
23467
|
-
temperature: 0
|
|
23631
|
+
model
|
|
23468
23632
|
});
|
|
23469
23633
|
const rawScore = response.data.score;
|
|
23470
23634
|
const score = typeof rawScore === "number" ? rawScore : typeof rawScore === "string" ? parseFloat(rawScore) : NaN;
|
|
@@ -23484,8 +23648,7 @@ async function judgeLabelJson(params) {
|
|
|
23484
23648
|
const response = await client.extract(userContent, {
|
|
23485
23649
|
schema: buildLabelSchema(labels),
|
|
23486
23650
|
prompt: systemPrompt,
|
|
23487
|
-
model
|
|
23488
|
-
temperature: 0
|
|
23651
|
+
model
|
|
23489
23652
|
});
|
|
23490
23653
|
const label = typeof response.data.label === "string" ? response.data.label.trim() : "";
|
|
23491
23654
|
if (!labels.includes(label)) {
|
|
@@ -23569,6 +23732,7 @@ var init_eval = __esm(() => {
|
|
|
23569
23732
|
init_dataset_archive();
|
|
23570
23733
|
init_evaluator_config();
|
|
23571
23734
|
init_judge();
|
|
23735
|
+
init_score_function();
|
|
23572
23736
|
init_workflow_tool_metadata();
|
|
23573
23737
|
});
|
|
23574
23738
|
|
|
@@ -23637,6 +23801,7 @@ var init_paths = __esm(() => {
|
|
|
23637
23801
|
// ../types/src/index.ts
|
|
23638
23802
|
var exports_src = {};
|
|
23639
23803
|
__export(exports_src, {
|
|
23804
|
+
wrapBodyAsScoreFunction: () => wrapBodyAsScoreFunction,
|
|
23640
23805
|
workflowTriggerConfigSchema: () => workflowTriggerConfigSchema,
|
|
23641
23806
|
valuesMatch: () => valuesMatch,
|
|
23642
23807
|
validateStepConfig: () => validateStepConfig,
|
|
@@ -23667,10 +23832,12 @@ __export(exports_src, {
|
|
|
23667
23832
|
isValidJudgeModel: () => isValidJudgeModel,
|
|
23668
23833
|
isTerminalExecutionStatus: () => isTerminalExecutionStatus,
|
|
23669
23834
|
isStepCategory: () => isStepCategory,
|
|
23835
|
+
isParseableScoreFunction: () => isParseableScoreFunction,
|
|
23670
23836
|
isOutputSchemaUnknown: () => isOutputSchemaUnknown,
|
|
23671
23837
|
isNonSuccessTerminalExecutionStatus: () => isNonSuccessTerminalExecutionStatus,
|
|
23672
23838
|
isFilePathDescriptor: () => isFilePathDescriptor,
|
|
23673
23839
|
isFileOutput: () => isFileOutput,
|
|
23840
|
+
hasValidScoreSignature: () => hasValidScoreSignature,
|
|
23674
23841
|
hasTriggerMethod: () => hasTriggerMethod,
|
|
23675
23842
|
goldenFileSatisfiedInResult: () => goldenFileSatisfiedInResult,
|
|
23676
23843
|
getTriggerMethods: () => getTriggerMethods,
|
|
@@ -23678,6 +23845,7 @@ __export(exports_src, {
|
|
|
23678
23845
|
getStepSchema: () => getStepSchema,
|
|
23679
23846
|
getStepOutputSchema: () => getStepOutputSchema,
|
|
23680
23847
|
getStepConfigSchema: () => getStepConfigSchema,
|
|
23848
|
+
getScoreFunctionRuntimeIssue: () => getScoreFunctionRuntimeIssue,
|
|
23681
23849
|
getRequiredOutputKeys: () => getRequiredOutputKeys,
|
|
23682
23850
|
getProcessorSchemas: () => getProcessorSchemas,
|
|
23683
23851
|
getParserCategory: () => getParserCategory,
|
|
@@ -23693,11 +23861,13 @@ __export(exports_src, {
|
|
|
23693
23861
|
formatDiffValue: () => formatDiffValue,
|
|
23694
23862
|
fileFieldNamesFromJsonSchema: () => fileFieldNamesFromJsonSchema,
|
|
23695
23863
|
fieldStemFromGoldenName: () => fieldStemFromGoldenName,
|
|
23864
|
+
extractScoreFunctionBody: () => extractScoreFunctionBody,
|
|
23696
23865
|
extractFieldMetadata: () => extractFieldMetadata,
|
|
23697
23866
|
executionWallClockMs: () => executionWallClockMs,
|
|
23698
23867
|
evalInputKey: () => evalInputKey,
|
|
23699
23868
|
evalExpectedKey: () => evalExpectedKey,
|
|
23700
23869
|
errorHintFor: () => errorHintFor,
|
|
23870
|
+
defaultScoreFunction: () => defaultScoreFunction,
|
|
23701
23871
|
compareOutput: () => compareOutput,
|
|
23702
23872
|
compareExpectedValues: () => compareExpectedValues,
|
|
23703
23873
|
combineEvalScores: () => combineEvalScores,
|
|
@@ -23870,6 +24040,7 @@ __export(exports_src, {
|
|
|
23870
24040
|
DatasetImportRequestSchema: () => DatasetImportRequestSchema,
|
|
23871
24041
|
DatasetImportModeSchema: () => DatasetImportModeSchema,
|
|
23872
24042
|
DURABLE_EXECUTION_TYPES: () => DURABLE_EXECUTION_TYPES,
|
|
24043
|
+
DETERMINISTIC_TEMPERATURE: () => DETERMINISTIC_TEMPERATURE,
|
|
23873
24044
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
23874
24045
|
DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY,
|
|
23875
24046
|
DATASET_NAME_PATTERN: () => DATASET_NAME_PATTERN,
|
|
@@ -23891,6 +24062,10 @@ __export(exports_src, {
|
|
|
23891
24062
|
ControlApprovalOutputSchema: () => ControlApprovalOutputSchema,
|
|
23892
24063
|
ControlApprovalConfigSchema: () => ControlApprovalConfigSchema,
|
|
23893
24064
|
ConnectorSchema: () => ConnectorSchema,
|
|
24065
|
+
CUSTOM_SCRIPT_MAX_TIMEOUT_MS: () => CUSTOM_SCRIPT_MAX_TIMEOUT_MS,
|
|
24066
|
+
CUSTOM_SCRIPT_MAX_MEMORY_MB: () => CUSTOM_SCRIPT_MAX_MEMORY_MB,
|
|
24067
|
+
CUSTOM_SCRIPT_MAX_BYTES: () => CUSTOM_SCRIPT_MAX_BYTES,
|
|
24068
|
+
CUSTOM_SCRIPT_CONTRACT: () => CUSTOM_SCRIPT_CONTRACT,
|
|
23894
24069
|
BoundingRegionSchema: () => BoundingRegionSchema,
|
|
23895
24070
|
BlockStepSchema: () => BlockStepSchema,
|
|
23896
24071
|
BaseEntitySchema: () => BaseEntitySchema,
|
|
@@ -143405,7 +143580,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
143405
143580
|
// package.json
|
|
143406
143581
|
var package_default = {
|
|
143407
143582
|
name: "@eigenpal/cli",
|
|
143408
|
-
version: "0.4.
|
|
143583
|
+
version: "0.4.10",
|
|
143409
143584
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
143410
143585
|
keywords: [
|
|
143411
143586
|
"eigenpal",
|
|
@@ -146148,26 +146323,23 @@ function getExampleNames(dir, exampleNamesFilter) {
|
|
|
146148
146323
|
function readInput(exampleDir) {
|
|
146149
146324
|
const argsPath = join6(exampleDir, "input", "arguments.json");
|
|
146150
146325
|
const inputDir = join6(exampleDir, "input");
|
|
146151
|
-
const
|
|
146326
|
+
const scalars = JSON.parse(readFileSync5(argsPath, "utf-8"));
|
|
146327
|
+
const files = [];
|
|
146152
146328
|
for (const entry of readdirSync3(inputDir)) {
|
|
146153
146329
|
const full = join6(inputDir, entry);
|
|
146154
146330
|
if (!statSync2(full).isDirectory())
|
|
146155
146331
|
continue;
|
|
146156
|
-
const
|
|
146157
|
-
|
|
146158
|
-
|
|
146159
|
-
|
|
146160
|
-
const buf = readFileSync5(join6(full, filename));
|
|
146161
|
-
const mime = guessMimeType(filename);
|
|
146162
|
-
return {
|
|
146163
|
-
_inline: buf.toString("base64"),
|
|
146332
|
+
const filenames = readdirSync3(full).filter((f) => statSync2(join6(full, f)).isFile()).sort();
|
|
146333
|
+
for (const filename of filenames) {
|
|
146334
|
+
files.push({
|
|
146335
|
+
argument: entry,
|
|
146164
146336
|
filename,
|
|
146165
|
-
|
|
146166
|
-
|
|
146167
|
-
|
|
146168
|
-
|
|
146337
|
+
content: readFileSync5(join6(full, filename)),
|
|
146338
|
+
mimeType: guessMimeType(filename)
|
|
146339
|
+
});
|
|
146340
|
+
}
|
|
146169
146341
|
}
|
|
146170
|
-
return
|
|
146342
|
+
return { scalars, files };
|
|
146171
146343
|
}
|
|
146172
146344
|
function readOverrides(exampleDir) {
|
|
146173
146345
|
const metaPath = join6(exampleDir, "meta.json");
|
|
@@ -146181,8 +146353,10 @@ function readOverrides(exampleDir) {
|
|
|
146181
146353
|
return null;
|
|
146182
146354
|
}
|
|
146183
146355
|
function buildExamplePayload(exampleDir) {
|
|
146356
|
+
const { scalars, files } = readInput(exampleDir);
|
|
146184
146357
|
return {
|
|
146185
|
-
|
|
146358
|
+
scalars,
|
|
146359
|
+
files,
|
|
146186
146360
|
overrides: readOverrides(exampleDir)
|
|
146187
146361
|
};
|
|
146188
146362
|
}
|
|
@@ -146664,11 +146838,19 @@ async function runExec(client, dir, workflowIdOrSlug, exampleNames, options8 = {
|
|
|
146664
146838
|
progress.setRunning(index);
|
|
146665
146839
|
else
|
|
146666
146840
|
console.log(`${ui.dim("→")} ${name}`);
|
|
146667
|
-
const
|
|
146668
|
-
|
|
146669
|
-
|
|
146670
|
-
|
|
146671
|
-
|
|
146841
|
+
const form = new FormData;
|
|
146842
|
+
const sidecar = { trigger: "cli" };
|
|
146843
|
+
if (Object.keys(example.scalars).length > 0)
|
|
146844
|
+
sidecar.input = example.scalars;
|
|
146845
|
+
if (example.overrides)
|
|
146846
|
+
sidecar.overrides = example.overrides;
|
|
146847
|
+
form.append("_json", JSON.stringify(sidecar));
|
|
146848
|
+
for (const file2 of example.files) {
|
|
146849
|
+
form.append(file2.argument, new Blob([file2.content], {
|
|
146850
|
+
type: file2.mimeType ?? "application/octet-stream"
|
|
146851
|
+
}), file2.filename);
|
|
146852
|
+
}
|
|
146853
|
+
const res = await client.postFormData(`/api/v1/workflows/${workflowId}/run`, form);
|
|
146672
146854
|
if (typeof res?.executionId !== "string") {
|
|
146673
146855
|
throw new Error("Run API did not return executionId");
|
|
146674
146856
|
}
|
|
@@ -147463,7 +147645,7 @@ var EVALUATOR_REGISTRY = {
|
|
|
147463
147645
|
"custom-script": {
|
|
147464
147646
|
type: "custom-script",
|
|
147465
147647
|
name: "Custom Script",
|
|
147466
|
-
description: "
|
|
147648
|
+
description: "Score the workflow output with sandboxed JavaScript. Authors a `function scoreScript(expected, actual) { ... }` declaration that returns a number in [0, 1].",
|
|
147467
147649
|
configSchema: CustomScriptConfigSchema
|
|
147468
147650
|
}
|
|
147469
147651
|
};
|
|
@@ -147505,6 +147687,7 @@ function registerEvaluatorTypeCommands(parent) {
|
|
|
147505
147687
|
type: def.type,
|
|
147506
147688
|
name: def.name,
|
|
147507
147689
|
description: def.description,
|
|
147690
|
+
entrySchema: z12.toJSONSchema(EvaluatorBaseEntrySchema, { target: "draft-7" }),
|
|
147508
147691
|
configSchema: z12.toJSONSchema(def.configSchema, { target: "draft-7" })
|
|
147509
147692
|
};
|
|
147510
147693
|
const text = JSON.stringify(result, null, 2);
|
|
@@ -147595,10 +147778,47 @@ function buildBatchDiff(args) {
|
|
|
147595
147778
|
sharedExamples: sharedExampleSet.size,
|
|
147596
147779
|
onlyInA,
|
|
147597
147780
|
onlyInB,
|
|
147598
|
-
regressionThreshold
|
|
147781
|
+
regressionThreshold,
|
|
147782
|
+
byEvaluator: aggregateByEvaluator(rows)
|
|
147599
147783
|
}
|
|
147600
147784
|
};
|
|
147601
147785
|
}
|
|
147786
|
+
function aggregateByEvaluator(rows) {
|
|
147787
|
+
const buckets = new Map;
|
|
147788
|
+
for (const r5 of rows) {
|
|
147789
|
+
const list = buckets.get(r5.evaluator);
|
|
147790
|
+
if (list)
|
|
147791
|
+
list.push(r5);
|
|
147792
|
+
else
|
|
147793
|
+
buckets.set(r5.evaluator, [r5]);
|
|
147794
|
+
}
|
|
147795
|
+
const aggregates = [];
|
|
147796
|
+
for (const [evaluator, group2] of buckets) {
|
|
147797
|
+
const comparable = group2.filter((r5) => r5.delta != null);
|
|
147798
|
+
const meanDelta = comparable.length === 0 ? null : comparable.reduce((sum, r5) => sum + r5.delta, 0) / comparable.length;
|
|
147799
|
+
aggregates.push({
|
|
147800
|
+
evaluator,
|
|
147801
|
+
comparable: comparable.length,
|
|
147802
|
+
meanDelta,
|
|
147803
|
+
regressions: group2.filter((r5) => r5.status === "regression").length,
|
|
147804
|
+
improvements: group2.filter((r5) => r5.status === "improvement").length,
|
|
147805
|
+
unchanged: group2.filter((r5) => r5.status === "unchanged").length
|
|
147806
|
+
});
|
|
147807
|
+
}
|
|
147808
|
+
aggregates.sort((a5, b8) => {
|
|
147809
|
+
if (a5.meanDelta == null && b8.meanDelta == null)
|
|
147810
|
+
return a5.evaluator.localeCompare(b8.evaluator);
|
|
147811
|
+
if (a5.meanDelta == null)
|
|
147812
|
+
return 1;
|
|
147813
|
+
if (b8.meanDelta == null)
|
|
147814
|
+
return -1;
|
|
147815
|
+
const diff = Math.abs(b8.meanDelta) - Math.abs(a5.meanDelta);
|
|
147816
|
+
if (diff !== 0)
|
|
147817
|
+
return diff;
|
|
147818
|
+
return a5.evaluator.localeCompare(b8.evaluator);
|
|
147819
|
+
});
|
|
147820
|
+
return aggregates;
|
|
147821
|
+
}
|
|
147602
147822
|
function sortDiffRows(rows, sort) {
|
|
147603
147823
|
const tiebreak = (a5, b8) => {
|
|
147604
147824
|
const exCmp = a5.example.localeCompare(b8.example);
|
|
@@ -147684,10 +147904,30 @@ function renderBatchDiffHuman(diff) {
|
|
|
147684
147904
|
}
|
|
147685
147905
|
process.stderr.write(`
|
|
147686
147906
|
`);
|
|
147907
|
+
if (diff.summary.byEvaluator.length > 0) {
|
|
147908
|
+
process.stderr.write(`${ui.bold("Per-evaluator deltas")} ${ui.dim("(B − A):")}
|
|
147909
|
+
`);
|
|
147910
|
+
console.log(table(diff.summary.byEvaluator, [
|
|
147911
|
+
{ key: "evaluator", header: "evaluator" },
|
|
147912
|
+
{ key: "comparable", header: "rows", align: "right" },
|
|
147913
|
+
{
|
|
147914
|
+
key: "meanDelta",
|
|
147915
|
+
header: "mean Δ",
|
|
147916
|
+
align: "right",
|
|
147917
|
+
format: (v11) => formatDelta(v11)
|
|
147918
|
+
},
|
|
147919
|
+
{ key: "regressions", header: "regressions", align: "right" },
|
|
147920
|
+
{ key: "improvements", header: "improvements", align: "right" }
|
|
147921
|
+
]));
|
|
147922
|
+
process.stderr.write(`
|
|
147923
|
+
`);
|
|
147924
|
+
}
|
|
147687
147925
|
if (diff.rows.length === 0) {
|
|
147688
147926
|
process.stderr.write(`${ui.dim("No shared (example, evaluator) pairs to compare.")}
|
|
147689
147927
|
`);
|
|
147690
147928
|
} else {
|
|
147929
|
+
process.stderr.write(`${ui.bold("Per-row deltas:")}
|
|
147930
|
+
`);
|
|
147691
147931
|
console.log(table(diff.rows, [
|
|
147692
147932
|
{ key: "example", header: "example" },
|
|
147693
147933
|
{ key: "evaluator", header: "evaluator" },
|
|
@@ -147999,7 +148239,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
147999
148239
|
error51(`Cannot update workflow ${ui.bold(workflowId)} — pass --bump <patch|minor|major> or --set-version <X.Y.Z>, or add a top-level \`version: X.Y.Z\` to ${filePath}.`);
|
|
148000
148240
|
process.exit(1);
|
|
148001
148241
|
}
|
|
148002
|
-
const yamlToSend = cliWantsRewrite ?
|
|
148242
|
+
const yamlToSend = cliWantsRewrite ? spliceWorkflowVersion2(yamlOnDisk, nextVersion) : yamlOnDisk;
|
|
148003
148243
|
const result = await client.post(`/api/v1/workflows/${workflowId}/versions`, {
|
|
148004
148244
|
yaml: yamlToSend,
|
|
148005
148245
|
version: nextVersion
|
|
@@ -148013,7 +148253,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
148013
148253
|
if (opts.bump) {
|
|
148014
148254
|
throw new Error("--bump requires --workflow-id (need a current version to bump from). For a new workflow, use --set-version <X.Y.Z> or omit both.");
|
|
148015
148255
|
}
|
|
148016
|
-
const yamlToSend = opts.setVersion && !extractWorkflowVersion(yamlOnDisk) ?
|
|
148256
|
+
const yamlToSend = opts.setVersion && !extractWorkflowVersion(yamlOnDisk) ? spliceWorkflowVersion2(yamlOnDisk, opts.setVersion) : yamlOnDisk;
|
|
148017
148257
|
const result = await client.post("/api/v1/workflows", { yaml: yamlToSend });
|
|
148018
148258
|
if (opts.json) {
|
|
148019
148259
|
printJson(result);
|
|
@@ -148040,7 +148280,7 @@ function bumpSemver(current, level) {
|
|
|
148040
148280
|
return `${major}.${minor + 1}.0`;
|
|
148041
148281
|
return `${major}.${minor}.${patch + 1}`;
|
|
148042
148282
|
}
|
|
148043
|
-
function
|
|
148283
|
+
function spliceWorkflowVersion2(yaml, next) {
|
|
148044
148284
|
if (/^version:\s*['"]?(\d+\.\d+\.\d+)['"]?\s*$/m.test(yaml)) {
|
|
148045
148285
|
return yaml.replace(/^version:.*$/m, `version: ${next}`);
|
|
148046
148286
|
}
|
|
@@ -148444,7 +148684,7 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148444
148684
|
return;
|
|
148445
148685
|
}
|
|
148446
148686
|
success2(`Started experiment ${ui.bold(start.batchId)} ${ui.dim(`(${start.total} execution${start.total === 1 ? "" : "s"} queued)`)}`);
|
|
148447
|
-
process.stderr.write(` ${ui.dim(`Watch: eigenpal workflow experiment
|
|
148687
|
+
process.stderr.write(` ${ui.dim(`Watch + auto-pull: eigenpal workflow experiment watch ${workflowId} ${start.batchId}`)}
|
|
148448
148688
|
`);
|
|
148449
148689
|
return;
|
|
148450
148690
|
}
|
|
@@ -148461,20 +148701,32 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148461
148701
|
await new Promise((r5) => setTimeout(r5, intervalMs));
|
|
148462
148702
|
}
|
|
148463
148703
|
}));
|
|
148464
|
-
const statusCmd = experiment.command("status <workflow-id> <batchId>").description(descriptionFor("workflow_get_experiment_status")).option("--watch", "Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit", false).option("--interval <seconds>", "Poll interval in seconds when --watch is set (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling for --watch in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--include <kinds>", "Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB)", "");
|
|
148704
|
+
const statusCmd = experiment.command("status <workflow-id> <batchId>").description(descriptionFor("workflow_get_experiment_status")).option("--watch", "Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit", false).option("--short", "Single-line plain-text summary on stdout (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Pipe-friendly for monitoring loops; mutually exclusive with --watch.", false).option("--interval <seconds>", "Poll interval in seconds when --watch is set (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling for --watch in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--include <kinds>", "Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB)", "");
|
|
148465
148705
|
addJsonFlag(withBaseUrl(statusCmd)).action(action(async (workflow2, batchId, opts) => {
|
|
148706
|
+
if (opts.short && opts.watch) {
|
|
148707
|
+
error51("--short and --watch are mutually exclusive (short is for spot-check polling).");
|
|
148708
|
+
process.exit(2);
|
|
148709
|
+
}
|
|
148466
148710
|
const includes = new Set((opts.include ?? "").split(",").map((s5) => s5.trim()).filter(Boolean));
|
|
148467
148711
|
const includePayload = includes.has("payload");
|
|
148468
148712
|
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
148469
148713
|
const url3 = `/api/v1/workflows/${workflowId}/experiments/${batchId}`;
|
|
148470
148714
|
if (!opts.watch) {
|
|
148471
148715
|
const result = await client.get(url3);
|
|
148716
|
+
const execs = result.executions ?? [];
|
|
148717
|
+
const summary = summarizeExperimentExecutions(execs);
|
|
148472
148718
|
if (opts.json) {
|
|
148473
|
-
printJson(result);
|
|
148719
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148720
|
+
return;
|
|
148721
|
+
}
|
|
148722
|
+
if (opts.short) {
|
|
148723
|
+
console.log(formatShortStatus(summary));
|
|
148724
|
+
const failed2 = summary.counts.failed ?? 0;
|
|
148725
|
+
const cancelled2 = summary.counts.cancelled ?? 0;
|
|
148726
|
+
if (summary.done && failed2 + cancelled2 > 0)
|
|
148727
|
+
process.exit(1);
|
|
148474
148728
|
return;
|
|
148475
148729
|
}
|
|
148476
|
-
const execs = result.executions ?? [];
|
|
148477
|
-
const summary = summarizeExperimentExecutions(execs);
|
|
148478
148730
|
const counts = Object.entries(summary.counts).map(([k9, v11]) => `${k9}=${v11}`).sort().join(" ");
|
|
148479
148731
|
console.log(`${ui.bold(batchId)} ${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`);
|
|
148480
148732
|
const failed = summary.counts.failed ?? 0;
|
|
@@ -148488,56 +148740,25 @@ returns immediately with \`{ batchId, total }\`; poll
|
|
|
148488
148740
|
process.exit(1);
|
|
148489
148741
|
return;
|
|
148490
148742
|
}
|
|
148491
|
-
const
|
|
148492
|
-
|
|
148493
|
-
|
|
148494
|
-
|
|
148495
|
-
|
|
148496
|
-
|
|
148497
|
-
|
|
148498
|
-
const line3 = `${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`;
|
|
148499
|
-
if (line3 !== lastSummary) {
|
|
148500
|
-
if (process.stderr.isTTY) {
|
|
148501
|
-
process.stderr.write(`\r\x1B[K${line3}`);
|
|
148502
|
-
} else {
|
|
148503
|
-
process.stderr.write(`${line3}
|
|
148504
|
-
`);
|
|
148505
|
-
}
|
|
148506
|
-
lastSummary = line3;
|
|
148507
|
-
}
|
|
148508
|
-
const wantPayload = opts.json || includePayload;
|
|
148509
|
-
if (summary.done) {
|
|
148510
|
-
if (process.stderr.isTTY)
|
|
148511
|
-
process.stderr.write(`
|
|
148512
|
-
`);
|
|
148513
|
-
const failed = summary.counts.failed ?? 0;
|
|
148514
|
-
const cancelled = summary.counts.cancelled ?? 0;
|
|
148515
|
-
const rejected = summary.counts.rejected ?? 0;
|
|
148516
|
-
const badCount = failed + cancelled + rejected;
|
|
148517
|
-
if (badCount > 0) {
|
|
148518
|
-
renderExperimentFailures(execs, summary.total);
|
|
148519
|
-
}
|
|
148743
|
+
const wantPayload = opts.json || includePayload;
|
|
148744
|
+
await pollExperimentUntilTerminal({
|
|
148745
|
+
client,
|
|
148746
|
+
url: url3,
|
|
148747
|
+
interval: opts.interval,
|
|
148748
|
+
maxWait: opts.maxWait,
|
|
148749
|
+
onTerminal: (result, summary) => {
|
|
148520
148750
|
if (wantPayload) {
|
|
148521
|
-
printJson(result);
|
|
148751
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148522
148752
|
} else {
|
|
148523
148753
|
process.stderr.write(`${ui.dim(`use --include payload (or --json) for the full per-execution snapshot`)}
|
|
148524
148754
|
`);
|
|
148525
148755
|
}
|
|
148526
|
-
|
|
148527
|
-
|
|
148528
|
-
return;
|
|
148529
|
-
}
|
|
148530
|
-
if (Date.now() >= deadline) {
|
|
148531
|
-
if (process.stderr.isTTY)
|
|
148532
|
-
process.stderr.write(`
|
|
148533
|
-
`);
|
|
148534
|
-
error51(`Timed out after ${opts.maxWait}s with ${summary.total - summary.terminal} non-terminal execution(s). Re-run to keep watching.`);
|
|
148756
|
+
},
|
|
148757
|
+
onDeadline: (result, summary) => {
|
|
148535
148758
|
if (wantPayload)
|
|
148536
|
-
printJson(result);
|
|
148537
|
-
process.exit(2);
|
|
148759
|
+
printJson({ ...result, summary: rollupForJson(summary) });
|
|
148538
148760
|
}
|
|
148539
|
-
|
|
148540
|
-
}
|
|
148761
|
+
});
|
|
148541
148762
|
}));
|
|
148542
148763
|
const cancelCmd = experiment.command("cancel <workflow-id> <batchId>").description("Cancel every execution in a batch. Idempotent.").option("--yes", "Required for non-TTY shells (CI, pipes). Acts immediately, no prompt.").addHelpText("after", `
|
|
148543
148764
|
Examples:
|
|
@@ -148626,6 +148847,62 @@ batches must belong to the same workflow within the caller's tenant.
|
|
|
148626
148847
|
}
|
|
148627
148848
|
renderBatchDiffHuman(diff);
|
|
148628
148849
|
}));
|
|
148850
|
+
const watchCmd = experiment.command("watch <workflow-id> <batchId>").description("Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`.").option("--interval <seconds>", "Poll interval in seconds (default 5)", intArg, 5).option("--max-wait <seconds>", "Hard ceiling in seconds (default 1800 = 30 min)", intArg, 30 * 60).option("--pull-on-complete <path>", "Destination for the results file. Default: ./results-<batchId>.<format>").option("--format <csv|json>", "Results export format (default json)", parseExportFormat, "json").option("--no-pull", "Skip auto-pulling results on terminal (just watch)").addHelpText("after", `
|
|
148851
|
+
Examples:
|
|
148852
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789
|
|
148853
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --format csv
|
|
148854
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --pull-on-complete ./out/r.json
|
|
148855
|
+
$ eigenpal workflow experiment watch wf_abc123 evb_xyz789 --no-pull
|
|
148856
|
+
|
|
148857
|
+
Behavior:
|
|
148858
|
+
- Polls every --interval seconds; redraws a one-line tick on a TTY,
|
|
148859
|
+
appends a new line per tick when piped.
|
|
148860
|
+
- On terminal: renders the failed-execution detail block (if any), then
|
|
148861
|
+
downloads results to ./results-<batchId>.<format> unless --no-pull
|
|
148862
|
+
or --pull-on-complete <path> says otherwise.
|
|
148863
|
+
- 4xx errors (e.g. unknown batch id) abort immediately rather than spin.
|
|
148864
|
+
- Pre-flight check: the destination directory must exist before the
|
|
148865
|
+
poll loop starts, so a typo doesn't waste a 30-min wait.
|
|
148866
|
+
- Exit codes (matching \`experiment status --watch\`):
|
|
148867
|
+
0 every execution succeeded (or pull skipped via --no-pull on a clean batch)
|
|
148868
|
+
1 at least one execution ended in failed/cancelled/rejected,
|
|
148869
|
+
OR the export download failed after a clean batch
|
|
148870
|
+
2 --max-wait deadline reached — re-run to keep watching;
|
|
148871
|
+
OR misuse (e.g. --no-pull combined with --pull-on-complete)
|
|
148872
|
+
`);
|
|
148873
|
+
withBaseUrl(watchCmd).action(action(async (workflow2, batchId, opts) => {
|
|
148874
|
+
if (!opts.pull && opts.pullOnComplete) {
|
|
148875
|
+
error51("--no-pull and --pull-on-complete are mutually exclusive — pass one or the other.");
|
|
148876
|
+
process.exit(2);
|
|
148877
|
+
}
|
|
148878
|
+
const { client, workflowId } = await buildClientForWorkflow(workflow2, opts);
|
|
148879
|
+
const url3 = `/api/v1/workflows/${workflowId}/experiments/${batchId}`;
|
|
148880
|
+
const dest = opts.pull ? opts.pullOnComplete ?? `./results-${batchId}.${opts.format}` : null;
|
|
148881
|
+
if (dest) {
|
|
148882
|
+
const dir = path15.dirname(dest);
|
|
148883
|
+
if (!existsSync9(dir)) {
|
|
148884
|
+
error51(`Destination directory does not exist: ${dir}. Create it first or pass a path under an existing directory.`);
|
|
148885
|
+
process.exit(2);
|
|
148886
|
+
}
|
|
148887
|
+
}
|
|
148888
|
+
await pollExperimentUntilTerminal({
|
|
148889
|
+
client,
|
|
148890
|
+
url: url3,
|
|
148891
|
+
interval: opts.interval,
|
|
148892
|
+
maxWait: opts.maxWait,
|
|
148893
|
+
onTerminal: async () => {
|
|
148894
|
+
if (!dest)
|
|
148895
|
+
return;
|
|
148896
|
+
const params = new URLSearchParams({ format: opts.format, batchId });
|
|
148897
|
+
try {
|
|
148898
|
+
await downloadStreamToFile(client, `/api/v1/workflows/${workflowId}/eval-results/export?${params.toString()}`, dest);
|
|
148899
|
+
} catch (err2) {
|
|
148900
|
+
error51(`Watch finished but pulling results failed: ${err2 instanceof Error ? err2.message : String(err2)}. Re-run \`experiment results <wf> <batch>\` to retry.`);
|
|
148901
|
+
process.exit(1);
|
|
148902
|
+
}
|
|
148903
|
+
}
|
|
148904
|
+
});
|
|
148905
|
+
}));
|
|
148629
148906
|
}
|
|
148630
148907
|
function renderExperimentFailures(execs, totalCount) {
|
|
148631
148908
|
const failedExecs = execs.filter((e8) => e8.status === "failed" || e8.status === "cancelled" || e8.status === "rejected");
|
|
@@ -148664,6 +148941,83 @@ function summarizeExperimentExecutions(execs) {
|
|
|
148664
148941
|
done: execs.length > 0 && terminal === execs.length
|
|
148665
148942
|
};
|
|
148666
148943
|
}
|
|
148944
|
+
function rollupForJson(summary) {
|
|
148945
|
+
return {
|
|
148946
|
+
total: summary.total,
|
|
148947
|
+
terminal: summary.terminal,
|
|
148948
|
+
complete: summary.done,
|
|
148949
|
+
completedCount: summary.counts.completed ?? 0,
|
|
148950
|
+
failedCount: summary.counts.failed ?? 0,
|
|
148951
|
+
cancelledCount: summary.counts.cancelled ?? 0,
|
|
148952
|
+
rejectedCount: summary.counts.rejected ?? 0,
|
|
148953
|
+
runningCount: summary.counts.running ?? 0,
|
|
148954
|
+
pendingCount: summary.counts.pending ?? 0
|
|
148955
|
+
};
|
|
148956
|
+
}
|
|
148957
|
+
function formatShortStatus(summary) {
|
|
148958
|
+
const state = summary.done ? "done" : "in-progress";
|
|
148959
|
+
return [
|
|
148960
|
+
`${summary.terminal}/${summary.total}`,
|
|
148961
|
+
state,
|
|
148962
|
+
`failed=${summary.counts.failed ?? 0}`,
|
|
148963
|
+
`cancelled=${summary.counts.cancelled ?? 0}`,
|
|
148964
|
+
`rejected=${summary.counts.rejected ?? 0}`
|
|
148965
|
+
].join(" ");
|
|
148966
|
+
}
|
|
148967
|
+
function renderTickLine(summary) {
|
|
148968
|
+
const counts = Object.entries(summary.counts).map(([k9, v11]) => `${k9}=${v11}`).sort().join(" ");
|
|
148969
|
+
return `${summary.terminal}/${summary.total} terminal ${ui.dim(`(${counts || "no executions"})`)}`;
|
|
148970
|
+
}
|
|
148971
|
+
async function pollExperimentUntilTerminal({
|
|
148972
|
+
client,
|
|
148973
|
+
url: url3,
|
|
148974
|
+
interval,
|
|
148975
|
+
maxWait,
|
|
148976
|
+
onTerminal,
|
|
148977
|
+
onDeadline
|
|
148978
|
+
}) {
|
|
148979
|
+
const deadline = Date.now() + maxWait * 1000;
|
|
148980
|
+
let lastSummary = "";
|
|
148981
|
+
while (true) {
|
|
148982
|
+
const result = await client.get(url3);
|
|
148983
|
+
const execs = result.executions ?? [];
|
|
148984
|
+
const summary = summarizeExperimentExecutions(execs);
|
|
148985
|
+
const line3 = renderTickLine(summary);
|
|
148986
|
+
if (line3 !== lastSummary) {
|
|
148987
|
+
if (process.stderr.isTTY) {
|
|
148988
|
+
process.stderr.write(`\r\x1B[K${line3}`);
|
|
148989
|
+
} else {
|
|
148990
|
+
process.stderr.write(`${line3}
|
|
148991
|
+
`);
|
|
148992
|
+
}
|
|
148993
|
+
lastSummary = line3;
|
|
148994
|
+
}
|
|
148995
|
+
if (summary.done) {
|
|
148996
|
+
if (process.stderr.isTTY)
|
|
148997
|
+
process.stderr.write(`
|
|
148998
|
+
`);
|
|
148999
|
+
const failed = summary.counts.failed ?? 0;
|
|
149000
|
+
const cancelled = summary.counts.cancelled ?? 0;
|
|
149001
|
+
const rejected = summary.counts.rejected ?? 0;
|
|
149002
|
+
const badCount = failed + cancelled + rejected;
|
|
149003
|
+
if (badCount > 0)
|
|
149004
|
+
renderExperimentFailures(execs, summary.total);
|
|
149005
|
+
await onTerminal(result, summary);
|
|
149006
|
+
if (badCount > 0)
|
|
149007
|
+
process.exit(1);
|
|
149008
|
+
return;
|
|
149009
|
+
}
|
|
149010
|
+
if (Date.now() >= deadline) {
|
|
149011
|
+
if (process.stderr.isTTY)
|
|
149012
|
+
process.stderr.write(`
|
|
149013
|
+
`);
|
|
149014
|
+
error51(`Timed out after ${maxWait}s with ${summary.total - summary.terminal} non-terminal execution(s). Re-run to keep watching.`);
|
|
149015
|
+
onDeadline?.(result, summary);
|
|
149016
|
+
process.exit(2);
|
|
149017
|
+
}
|
|
149018
|
+
await new Promise((r5) => setTimeout(r5, interval * 1000));
|
|
149019
|
+
}
|
|
149020
|
+
}
|
|
148667
149021
|
function registerVersionsCommands(parent) {
|
|
148668
149022
|
const versions2 = parent.command("versions").description("Inspect and restore historical workflow versions.");
|
|
148669
149023
|
const listCmd = versions2.command("list <workflow-id>").description(descriptionFor("workflow_list_versions"));
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -145,6 +145,17 @@ to retry.
|
|
|
145
145
|
eigenpal workflow experiment compare evb_old evb_new --regression-threshold 0.05
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
Output is two stacked tables: a per-evaluator aggregate (rows per evaluator
|
|
149
|
+
with mean Δ, regressions, improvements — sorted biggest mover first) followed
|
|
150
|
+
by a per-(example, evaluator) detail table. No need to write a Python
|
|
151
|
+
aggregator on top.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# JSON exposes both layers — `summary.byEvaluator` for the rollup,
|
|
155
|
+
# `rows` for the full per-example detail.
|
|
156
|
+
eigenpal workflow experiment compare evb_old evb_new --json | jq '.summary.byEvaluator'
|
|
157
|
+
```
|
|
158
|
+
|
|
148
159
|
Unlike its siblings, `compare` takes no `<workflow-id>` — the server resolves
|
|
149
160
|
the owning workflow from each batch id. Both batches must live in the
|
|
150
161
|
same workflow within your tenant. See [`reference/debugging.md`](reference/debugging.md#6-compare-two-experiment-batches)
|
|
@@ -163,6 +174,12 @@ eigenpal workflow evaluators validate ./evaluators.yaml
|
|
|
163
174
|
eigenpal workflow evaluators push <workflow-id> --file evaluators.yaml
|
|
164
175
|
```
|
|
165
176
|
|
|
177
|
+
Always set `description:` on every evaluator entry. It's a short, plain-language
|
|
178
|
+
sentence (≤500 chars; aim for one line) explaining what the evaluator measures
|
|
179
|
+
and why it matters, written for a non-technical reviewer in the dashboard.
|
|
180
|
+
Skip model names, thresholds, and dot-paths; those belong in `config`. See
|
|
181
|
+
[`reference/evaluators.md`](reference/evaluators.md#writing-descriptions-for-stakeholders).
|
|
182
|
+
|
|
166
183
|
### Build + validate + upload a dataset
|
|
167
184
|
|
|
168
185
|
```bash
|
|
@@ -218,12 +235,64 @@ eigenpal workflow experiment run <workflow-id> # → { bat
|
|
|
218
235
|
|
|
219
236
|
# Or restrict to one example:
|
|
220
237
|
eigenpal workflow experiment run <workflow-id> --example-id evx_…
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Recommended:** use `experiment watch` — it polls until terminal AND auto-pulls
|
|
241
|
+
results to disk in one command. Replaces the old "run → status (poll) → results"
|
|
242
|
+
chain so agents don't have to write a custom poller, parse `executions[].status`,
|
|
243
|
+
or chain `monitor + status + pull + score` shell logic.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# One command: watch until terminal, then write ./results-<batchId>.json on completion.
|
|
247
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id>
|
|
248
|
+
|
|
249
|
+
# CSV output:
|
|
250
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --format csv
|
|
251
|
+
|
|
252
|
+
# Custom destination (overrides the default ./results-<batchId>.<format>):
|
|
253
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --pull-on-complete ./out/r.json
|
|
254
|
+
|
|
255
|
+
# Just watch, skip the pull (e.g. you only need the live tick + exit code):
|
|
256
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> --no-pull
|
|
257
|
+
```
|
|
221
258
|
|
|
222
|
-
|
|
223
|
-
|
|
259
|
+
Exit codes match `experiment status --watch`: `0` clean, `1` any
|
|
260
|
+
failed/cancelled/rejected execution, `2` `--max-wait` deadline reached
|
|
261
|
+
(30 min default — re-run to keep watching).
|
|
224
262
|
|
|
225
|
-
|
|
226
|
-
|
|
263
|
+
### Monitoring scripts — use `--short` and the JSON rollup
|
|
264
|
+
|
|
265
|
+
When a script needs to spot-check a batch (no waiting), prefer one of the two
|
|
266
|
+
machine-readable paths instead of folding `executions[].status` yourself.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Single line on stdout, awk-friendly:
|
|
270
|
+
eigenpal workflow experiment status <wf> <batch> --short
|
|
271
|
+
# → 6/6 done failed=0 cancelled=0 rejected=0 (or "in-progress" while pending)
|
|
272
|
+
|
|
273
|
+
# Parse with awk:
|
|
274
|
+
DONE_STATE=$(eigenpal workflow experiment status <wf> <batch> --short | awk '{print $2}')
|
|
275
|
+
[ "$DONE_STATE" = "done" ] || exit 0 # not terminal yet, try again later
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# JSON includes a top-level `summary` rollup so you don't count statuses by hand:
|
|
280
|
+
eigenpal workflow experiment status <wf> <batch> --json | jq '.summary'
|
|
281
|
+
# → { total: 6, terminal: 6, complete: true,
|
|
282
|
+
# completedCount: 5, failedCount: 1,
|
|
283
|
+
# cancelledCount: 0, rejectedCount: 0,
|
|
284
|
+
# runningCount: 0, pendingCount: 0 }
|
|
285
|
+
|
|
286
|
+
# `summary.complete` is the canonical poll-completion flag.
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`--short` and `--json` are mutually exclusive with `--watch` — for live
|
|
290
|
+
streaming use `experiment watch` instead.
|
|
291
|
+
|
|
292
|
+
If you need the raw eval-results export after watching with `--no-pull`, or
|
|
293
|
+
to refetch results outside a watch loop, fall back to:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
227
296
|
eigenpal workflow experiment results <workflow-id> <batch-id> --format csv --out r.csv
|
|
228
297
|
```
|
|
229
298
|
|
|
@@ -433,10 +502,11 @@ eigenpal workflow clear-local # delete local execution artifacts
|
|
|
433
502
|
|
|
434
503
|
# Experiments (batch eval runs)
|
|
435
504
|
eigenpal workflow experiment run <workflow-id> [--example-id <id> ...]
|
|
436
|
-
eigenpal workflow experiment status <workflow-id> <batch-id> [--watch] # exit 1 on terminal w/ failures, 2 on --max-wait
|
|
505
|
+
eigenpal workflow experiment status <workflow-id> <batch-id> [--watch] [--short] [--json] # exit 1 on terminal w/ failures, 2 on --max-wait. --json includes top-level `summary` rollup.
|
|
506
|
+
eigenpal workflow experiment watch <workflow-id> <batch-id> [--format csv|json] [--pull-on-complete <path>] [--no-pull] # poll + auto-pull results in one shot
|
|
437
507
|
eigenpal workflow experiment results <workflow-id> [batch-id] --format {csv|json} --out r.csv
|
|
438
508
|
eigenpal workflow experiment list <workflow-id>
|
|
439
|
-
eigenpal workflow experiment compare <batch-a> <batch-b> [--regression-threshold] [--sort] # no --workflow-id
|
|
509
|
+
eigenpal workflow experiment compare <batch-a> <batch-b> [--regression-threshold] [--sort] # no --workflow-id; prints per-evaluator aggregate + per-row table
|
|
440
510
|
|
|
441
511
|
# Step-type / evaluator-type introspection
|
|
442
512
|
eigenpal workflow step-type list
|
|
@@ -38,6 +38,7 @@ Manage workflows: push, pull, run, evaluate.
|
|
|
38
38
|
- [`eigenpal workflow experiment cancel [options] <workflow-id> <batchId>`](#eigenpal-workflow-experiment-cancel-options-workflow-id-batchid)
|
|
39
39
|
- [`eigenpal workflow experiment results [options] <workflow-id> [batchId]`](#eigenpal-workflow-experiment-results-options-workflow-id-batchid)
|
|
40
40
|
- [`eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>`](#eigenpal-workflow-experiment-compare-options-batchida-batchidb)
|
|
41
|
+
- [`eigenpal workflow experiment watch [options] <workflow-id> <batchId>`](#eigenpal-workflow-experiment-watch-options-workflow-id-batchid)
|
|
41
42
|
- [`eigenpal workflow execution run [options] <workflow-id> [examples...]`](#eigenpal-workflow-execution-run-options-workflow-id-examples)
|
|
42
43
|
- [`eigenpal workflow execution get [options] <executionId>`](#eigenpal-workflow-execution-get-options-executionid)
|
|
43
44
|
- [`eigenpal workflow execution list [options] <workflow-id>`](#eigenpal-workflow-execution-list-options-workflow-id)
|
|
@@ -79,7 +80,8 @@ workflow
|
|
|
79
80
|
│ ├── status <workflow-id> <batchId>
|
|
80
81
|
│ ├── cancel <workflow-id> <batchId>
|
|
81
82
|
│ ├── results <workflow-id> [batchId]
|
|
82
|
-
│
|
|
83
|
+
│ ├── compare <batchIdA> <batchIdB>
|
|
84
|
+
│ └── watch <workflow-id> <batchId>
|
|
83
85
|
├── execution
|
|
84
86
|
│ ├── run <workflow-id> [examples...]
|
|
85
87
|
│ ├── get <executionId>
|
|
@@ -137,14 +139,15 @@ workflow
|
|
|
137
139
|
|
|
138
140
|
### Experiment
|
|
139
141
|
|
|
140
|
-
| Command | Description
|
|
141
|
-
| ------------------------------------------------------------------------ |
|
|
142
|
-
| `eigenpal workflow experiment list [options] <workflow-id>` | List executions for the workflow, newest first.
|
|
143
|
-
| `eigenpal workflow experiment run [options] <workflow-id>` | Start a batch eval against the workflow's dataset.
|
|
144
|
-
| `eigenpal workflow experiment status [options] <workflow-id> <batchId>` | Aggregate progress for a batch by `batchId`.
|
|
145
|
-
| `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>` | Cancel every execution in a batch. Idempotent.
|
|
146
|
-
| `eigenpal workflow experiment results [options] <workflow-id> [batchId]` | Download eval results in CSV or JSON.
|
|
147
|
-
| `eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>` | Diff eval scores between two experiment batches.
|
|
142
|
+
| Command | Description |
|
|
143
|
+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
144
|
+
| `eigenpal workflow experiment list [options] <workflow-id>` | List executions for the workflow, newest first. |
|
|
145
|
+
| `eigenpal workflow experiment run [options] <workflow-id>` | Start a batch eval against the workflow's dataset. |
|
|
146
|
+
| `eigenpal workflow experiment status [options] <workflow-id> <batchId>` | Aggregate progress for a batch by `batchId`. |
|
|
147
|
+
| `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>` | Cancel every execution in a batch. Idempotent. |
|
|
148
|
+
| `eigenpal workflow experiment results [options] <workflow-id> [batchId]` | Download eval results in CSV or JSON. |
|
|
149
|
+
| `eigenpal workflow experiment compare [options] <batchIdA> <batchIdB>` | Diff eval scores between two experiment batches. |
|
|
150
|
+
| `eigenpal workflow experiment watch [options] <workflow-id> <batchId>` | Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`. |
|
|
148
151
|
|
|
149
152
|
### Execution
|
|
150
153
|
|
|
@@ -508,14 +511,15 @@ Aggregate progress for a batch by `batchId`.
|
|
|
508
511
|
|
|
509
512
|
### Options
|
|
510
513
|
|
|
511
|
-
| Flag | Required | Default | Description
|
|
512
|
-
| ---------------------- | -------- | ------- |
|
|
513
|
-
| `--watch` | no | `false` | Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit
|
|
514
|
-
| `--
|
|
515
|
-
| `--
|
|
516
|
-
| `--
|
|
517
|
-
| `--
|
|
518
|
-
| `--
|
|
514
|
+
| Flag | Required | Default | Description |
|
|
515
|
+
| ---------------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
516
|
+
| `--watch` | no | `false` | Poll until every execution reaches a terminal state (completed/failed/cancelled/rejected), then exit |
|
|
517
|
+
| `--short` | no | `false` | Single-line plain-text summary on stdout (e.g. `6/6 done failed=0 cancelled=0 rejected=0`). Pipe-friendly for monitoring loops; mutually exclusive with --watch. |
|
|
518
|
+
| `--interval <seconds>` | no | `5` | Poll interval in seconds when --watch is set (default 5) |
|
|
519
|
+
| `--max-wait <seconds>` | no | `1800` | Hard ceiling for --watch in seconds (default 1800 = 30 min) |
|
|
520
|
+
| `--include <kinds>` | no | `""` | Comma-separated extras to attach when --watch terminates: payload (full per-execution snapshot, can be hundreds of KB) |
|
|
521
|
+
| `--base-url <url>` | no | | Server base URL |
|
|
522
|
+
| `--json` | no | | Output the raw server response as JSON |
|
|
519
523
|
|
|
520
524
|
### `eigenpal workflow experiment cancel [options] <workflow-id> <batchId>`
|
|
521
525
|
|
|
@@ -575,6 +579,28 @@ Diff eval scores between two experiment batches.
|
|
|
575
579
|
| `--base-url <url>` | no | | Server base URL |
|
|
576
580
|
| `--json` | no | | Output the raw server response as JSON |
|
|
577
581
|
|
|
582
|
+
### `eigenpal workflow experiment watch [options] <workflow-id> <batchId>`
|
|
583
|
+
|
|
584
|
+
Poll until terminal, then auto-pull results — replaces `status --watch` + `results --out`.
|
|
585
|
+
|
|
586
|
+
### Arguments
|
|
587
|
+
|
|
588
|
+
| Name | Required | Variadic | Description |
|
|
589
|
+
| ------------- | -------- | -------- | ----------- |
|
|
590
|
+
| `workflow-id` | yes | no | |
|
|
591
|
+
| `batchId` | yes | no | |
|
|
592
|
+
|
|
593
|
+
### Options
|
|
594
|
+
|
|
595
|
+
| Flag | Required | Default | Description |
|
|
596
|
+
| --------------------------- | -------- | -------- | ----------------------------------------------------------------------- |
|
|
597
|
+
| `--interval <seconds>` | no | `5` | Poll interval in seconds (default 5) |
|
|
598
|
+
| `--max-wait <seconds>` | no | `1800` | Hard ceiling in seconds (default 1800 = 30 min) |
|
|
599
|
+
| `--pull-on-complete <path>` | no | | Destination for the results file. Default: ./results-<batchId>.<format> |
|
|
600
|
+
| `--format <csv\|json>` | no | `"json"` | Results export format (default json) |
|
|
601
|
+
| `--no-pull` | no | | Skip auto-pulling results on terminal (just watch) |
|
|
602
|
+
| `--base-url <url>` | no | | Server base URL |
|
|
603
|
+
|
|
578
604
|
### `eigenpal workflow execution run [options] <workflow-id> [examples...]`
|
|
579
605
|
|
|
580
606
|
Run a saved workflow against local dataset examples.
|
|
@@ -11,16 +11,41 @@ this is the agent-facing summary.
|
|
|
11
11
|
```yaml
|
|
12
12
|
evaluators: # array of evaluator definitions, ≥1 entry
|
|
13
13
|
- name: ... # required, unique within this file, [a-z0-9][a-z0-9-_]*
|
|
14
|
+
description: ... # optional, ≤500 chars; one-sentence business explanation (see below)
|
|
14
15
|
type: ... # required, one of: exact-diff | llm-judge | custom-script
|
|
15
16
|
config: # required, shape depends on `type`
|
|
16
17
|
passThreshold: 0.95 # 0..1, optional (default 1.0)
|
|
17
18
|
...
|
|
18
19
|
|
|
19
|
-
# OPTIONAL
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
# OPTIONAL workflow-level pass threshold. A run passes when its weighted-mean
|
|
21
|
+
# score across evaluators clears this number. Defaults to 0.7.
|
|
22
|
+
passThreshold: 0.7
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Writing descriptions for stakeholders
|
|
26
|
+
|
|
27
|
+
Every evaluator entry takes an optional `description`. Always set it. The
|
|
28
|
+
dashboard surfaces this string to non-technical reviewers (legal, ops,
|
|
29
|
+
finance) who never see the YAML or the judge prompt. Write it for them:
|
|
30
|
+
|
|
31
|
+
- One short sentence, plain language.
|
|
32
|
+
- Name what the evaluator measures and why a business reader should care.
|
|
33
|
+
- Skip implementation detail (model names, thresholds, dot-paths). Those
|
|
34
|
+
belong in `config`.
|
|
35
|
+
- Keep it under ~120 characters when you can; the schema caps it at 500.
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
# Good. A non-technical reviewer immediately grasps the intent.
|
|
39
|
+
- name: covenants-recall
|
|
40
|
+
description: Checks every covenant from the contract appears in the extraction.
|
|
41
|
+
type: llm-judge
|
|
42
|
+
config: ...
|
|
43
|
+
|
|
44
|
+
# Bad. Restates the config; teaches a stakeholder nothing.
|
|
45
|
+
- name: covenants-recall
|
|
46
|
+
description: llm-judge with passThreshold 0.85 over the covenants array.
|
|
47
|
+
type: llm-judge
|
|
48
|
+
config: ...
|
|
24
49
|
```
|
|
25
50
|
|
|
26
51
|
## Evaluator types
|
|
@@ -47,23 +72,34 @@ judge: # optional shared model defaults for llm-judge evaluators
|
|
|
47
72
|
- name: covenants-recall
|
|
48
73
|
type: llm-judge
|
|
49
74
|
config:
|
|
50
|
-
model: gpt-4o #
|
|
51
|
-
|
|
75
|
+
model: gpt-4o # OPTIONAL — falls back to the workspace default LLM provider
|
|
76
|
+
mode: continuous # continuous | discrete (default: continuous)
|
|
52
77
|
passThreshold: 0.85
|
|
53
|
-
|
|
78
|
+
docLabel: extraction # what the judge calls the workflow output (default: "output")
|
|
79
|
+
promptExtension: |
|
|
54
80
|
Score how well the actual output captures every covenant in the
|
|
55
|
-
expected output
|
|
56
|
-
|
|
57
|
-
```
|
|
81
|
+
expected output. Penalise missing covenants; do not penalise
|
|
82
|
+
over-extraction. Be strict on covenants that change loan economics.
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
# discrete mode — the judge picks a label and the score table maps to a number
|
|
85
|
+
- name: tone-check
|
|
86
|
+
type: llm-judge
|
|
87
|
+
config:
|
|
88
|
+
mode: discrete
|
|
89
|
+
passThreshold: 0.7
|
|
90
|
+
labels:
|
|
91
|
+
excellent: 1.0
|
|
92
|
+
acceptable: 0.7
|
|
93
|
+
off-tone: 0.0
|
|
94
|
+
promptExtension: |
|
|
95
|
+
Pick the label that best matches the tone of the response.
|
|
96
|
+
```
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
`promptExtension` is the only required field — it's appended to the harness
|
|
99
|
+
prompt and tells the judge what "good" looks like for this workflow. The
|
|
100
|
+
harness handles JSON parsing, retries, and feeds the judge `actual`,
|
|
101
|
+
`expected`, and example metadata. In `discrete` mode the judge MUST return
|
|
102
|
+
one of the keys in `labels`; the harness then looks up the score.
|
|
67
103
|
|
|
68
104
|
### `custom-script` — JavaScript in the sandbox
|
|
69
105
|
|
|
@@ -72,16 +108,38 @@ It expects the model to return JSON with at least `score` (0..1) and
|
|
|
72
108
|
type: custom-script
|
|
73
109
|
config:
|
|
74
110
|
passThreshold: 1.0
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
function: |
|
|
112
|
+
/**
|
|
113
|
+
* @param {Expected} expected the example's expected output
|
|
114
|
+
* @param {Actual} actual the workflow's actual output
|
|
115
|
+
* @returns {number} a score in [0, 1]; throws are caught and scored as 0
|
|
116
|
+
*/
|
|
117
|
+
function scoreScript(expected, actual) {
|
|
118
|
+
const a = actual.totalAmount ?? 0;
|
|
119
|
+
const e = expected.totalAmount ?? 0;
|
|
120
|
+
return Math.abs(a - e) <= 0.01 ? 1 : 0;
|
|
121
|
+
}
|
|
80
122
|
```
|
|
81
123
|
|
|
82
124
|
Same WASM sandbox as `transform.script` (5s wall clock, 10 MB heap, no
|
|
83
|
-
network).
|
|
84
|
-
|
|
125
|
+
network). The YAML stores the **whole** `function scoreScript(expected,
|
|
126
|
+
actual) { ... }` declaration verbatim — same text the dashboard editor
|
|
127
|
+
shows. The JSDoc preamble is optional but recommended: it pulls the
|
|
128
|
+
workflow's output schema into IntelliSense for `expected.…` /
|
|
129
|
+
`actual.…` in the dashboard.
|
|
130
|
+
|
|
131
|
+
The signature shape is **locked**:
|
|
132
|
+
|
|
133
|
+
- Function name MUST be `scoreScript`.
|
|
134
|
+
- Parameters MUST be `(expected, actual)` in that order. Swapping them
|
|
135
|
+
would silently invert every score.
|
|
136
|
+
- Body MUST `return` a number in `[0, 1]`, or `throw` (which is caught
|
|
137
|
+
and scored as 0).
|
|
138
|
+
|
|
139
|
+
`workflow validate` and `workflow evaluators validate` flag YAML where
|
|
140
|
+
the function fails to parse, the signature doesn't match, or the body
|
|
141
|
+
contains neither `return` nor `throw` — so a hand-edited config errors
|
|
142
|
+
before it reaches the worker.
|
|
85
143
|
|
|
86
144
|
## Pass / fail vs score
|
|
87
145
|
|
|
@@ -160,9 +218,11 @@ process — `--out` writes the file directly from the server's storage.
|
|
|
160
218
|
LLM-judge scores have variance. To reduce flake:
|
|
161
219
|
|
|
162
220
|
- Pin a specific model id (`gpt-4o-2024-08-06` rather than `gpt-4o`).
|
|
163
|
-
-
|
|
164
|
-
- Keep the prompt narrow — score one dimension per evaluator. Use
|
|
221
|
+
- Keep `promptExtension` narrow — score one dimension per evaluator. Use
|
|
165
222
|
multiple judges instead of one omnibus prompt.
|
|
223
|
+
- Prefer `mode: discrete` with a small label set when the judgement is
|
|
224
|
+
categorical (good / acceptable / bad) — it eliminates the model's
|
|
225
|
+
free-form numeric drift.
|
|
166
226
|
- For high-stakes thresholds, run the experiment ≥3 times and average.
|
|
167
227
|
|
|
168
228
|
<!-- GENERATED:EVALUATOR_CATALOG START -->
|
|
@@ -177,6 +237,14 @@ _Generated from `EvalConfigYamlSchema` in `@eigenpal/types/src/eval/evaluator-co
|
|
|
177
237
|
| `evaluators` | array<object> | no | `[]` | |
|
|
178
238
|
| `passThreshold` | number | no | `0.7` | |
|
|
179
239
|
|
|
240
|
+
### Common entry fields (every evaluator type)
|
|
241
|
+
|
|
242
|
+
| Field | Type | Required | Default | Description |
|
|
243
|
+
| --- | --- | --- | --- | --- |
|
|
244
|
+
| `name` | string | yes | | Identifier used in eval results and the weighted-mean formula. Use a short kebab-case slug like header-exact or covenants-recall. |
|
|
245
|
+
| `description` | string | no | | One-sentence explanation, in business language, of what this evaluator measures and why a stakeholder should care. Shown in the dashboard to non-technical reviewers (legal, ops, finance) who never see the YAML or judge prompt. Skip implementation detail (model names, thresholds, dot-paths); those live in `config`. |
|
|
246
|
+
| `weight` | number | no | `1` | Relative importance in the weighted mean. The overall score is Σ(weightᵢ × scoreᵢ) / Σweight. Set to 0 to score-only without affecting overall. |
|
|
247
|
+
|
|
180
248
|
### `exact-diff` — JSON deep-diff against expected output
|
|
181
249
|
|
|
182
250
|
| Field | Type | Required | Default | Description |
|
|
@@ -203,7 +271,7 @@ _Generated from `EvalConfigYamlSchema` in `@eigenpal/types/src/eval/evaluator-co
|
|
|
203
271
|
|
|
204
272
|
| Field | Type | Required | Default | Description |
|
|
205
273
|
| --- | --- | --- | --- | --- |
|
|
206
|
-
| `
|
|
274
|
+
| `function` | string | yes | | Full JavaScript declaration of `function scoreScript(expected, actual) { ... }`. Receives `expected` (the example's expected output) and `actual` (the workflow's actual output), returns a number in [0, 1]. Throws are caught and scored as 0. |
|
|
207
275
|
| `timeoutMs` | integer | no | `5000` | Maximum wall-clock time the script can run before it's killed and the run is marked failed. |
|
|
208
276
|
| `memoryLimitMb` | integer | no | `10` | Maximum memory the sandbox may allocate. Increase if the script processes large arrays or strings. |
|
|
209
277
|
| `passThreshold` | number | no | `1` | Minimum script score this evaluator must reach for a run to pass. |
|
|
@@ -153,7 +153,6 @@ Extract structured data from text using AI with a JSON schema
|
|
|
153
153
|
| `prompt` | string | no | | Custom prompt template for extraction |
|
|
154
154
|
| `provider` | string | no | | Provider ID (e.g., "openai-gpt4o") |
|
|
155
155
|
| `model` | string | no | | Model override |
|
|
156
|
-
| `temperature` | number | no | `0` | Model temperature (0 = deterministic) |
|
|
157
156
|
| `maxInputTokens` | integer | no | | Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit. |
|
|
158
157
|
|
|
159
158
|
**Output:** `record<string, unknown>`
|
|
@@ -387,6 +387,7 @@ _Generated from `WorkflowDefinitionSchema` in `@eigenpal/types/src/workflow/work
|
|
|
387
387
|
| `steps` | array<unknown> | yes | | |
|
|
388
388
|
| `output` | record<string, string> | no | | |
|
|
389
389
|
| `settings` | object | no | | |
|
|
390
|
+
| `defaultModel` | string | no | | |
|
|
390
391
|
|
|
391
392
|
|
|
392
393
|
## Per-input fields
|