@prismatic-io/lux 0.0.1 → 0.0.2-preview.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/lib/answerers/persona/index.d.ts +2 -1
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +19 -5
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/answerers/terminal/index.js +1 -1
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/authoring.d.ts +14 -0
- package/lib/assertions/authoring.d.ts.map +1 -0
- package/lib/assertions/authoring.js +3 -0
- package/lib/assertions/authoring.js.map +1 -0
- package/lib/assertions/core/helpers.d.ts +2 -7
- package/lib/assertions/core/helpers.d.ts.map +1 -1
- package/lib/assertions/core/helpers.js +2 -6
- package/lib/assertions/core/helpers.js.map +1 -1
- package/lib/assertions/core/index.d.ts.map +1 -1
- package/lib/assertions/core/index.js +2 -1
- package/lib/assertions/core/index.js.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts +19 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +32 -0
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/authoring.d.ts +1 -0
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +1 -0
- package/lib/authoring.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +12 -67
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +16 -3
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +1 -0
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js.map +1 -1
- package/lib/cli/view.d.ts +13 -0
- package/lib/cli/view.d.ts.map +1 -0
- package/lib/cli/view.js +126 -0
- package/lib/cli/view.js.map +1 -0
- package/lib/core/action-events.d.ts +29 -0
- package/lib/core/action-events.d.ts.map +1 -0
- package/lib/core/action-events.js +51 -0
- package/lib/core/action-events.js.map +1 -0
- package/lib/core/agent-activity.d.ts +49 -0
- package/lib/core/agent-activity.d.ts.map +1 -0
- package/lib/core/agent-activity.js +206 -0
- package/lib/core/agent-activity.js.map +1 -0
- package/lib/core/agent-events.d.ts +26 -0
- package/lib/core/agent-events.d.ts.map +1 -0
- package/lib/core/agent-events.js +13 -0
- package/lib/core/agent-events.js.map +1 -0
- package/lib/core/artifact-evidence.d.ts +8 -0
- package/lib/core/artifact-evidence.d.ts.map +1 -1
- package/lib/core/artifact-evidence.js +36 -4
- package/lib/core/artifact-evidence.js.map +1 -1
- package/lib/core/assertion.d.ts +19 -9
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +26 -0
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case-authoring-state.d.ts +12 -0
- package/lib/core/case-authoring-state.d.ts.map +1 -0
- package/lib/core/case-authoring-state.js +43 -0
- package/lib/core/case-authoring-state.js.map +1 -0
- package/lib/core/case.d.ts +29 -1
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +14 -6
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver-metrics.d.ts +98 -0
- package/lib/core/driver-metrics.d.ts.map +1 -0
- package/lib/core/driver-metrics.js +36 -0
- package/lib/core/driver-metrics.js.map +1 -0
- package/lib/core/driver.d.ts +3 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js.map +1 -1
- package/lib/core/events.d.ts +36 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +12 -2
- package/lib/core/events.js.map +1 -1
- package/lib/core/experiment.d.ts +19 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +2 -0
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +4 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/lifecycle-fixtures.d.ts +3 -1
- package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
- package/lib/core/lifecycle-fixtures.js +4 -2
- package/lib/core/lifecycle-fixtures.js.map +1 -1
- package/lib/core/lifecycle-hooks.d.ts +9 -0
- package/lib/core/lifecycle-hooks.d.ts.map +1 -1
- package/lib/core/lifecycle-hooks.js +8 -2
- package/lib/core/lifecycle-hooks.js.map +1 -1
- package/lib/core/run.d.ts +150 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +9 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/wire.d.ts +12 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/core/wire.js +5 -1
- package/lib/core/wire.js.map +1 -1
- package/lib/drivers/claude-code/parse-events.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.js +73 -8
- package/lib/drivers/claude-code/parse-events.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts +9 -2
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +93 -12
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +10 -14
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +6 -0
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/index.d.ts +10 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +10 -6
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/annotation-store.d.ts.map +1 -1
- package/lib/orchestrator/annotation-store.js +6 -2
- package/lib/orchestrator/annotation-store.js.map +1 -1
- package/lib/orchestrator/case-extensions.d.ts +6 -0
- package/lib/orchestrator/case-extensions.d.ts.map +1 -0
- package/lib/orchestrator/case-extensions.js +28 -0
- package/lib/orchestrator/case-extensions.js.map +1 -0
- package/lib/orchestrator/compare.d.ts +13 -1
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +12 -0
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +0 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +5 -3
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +19 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +17 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +19 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +38 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +12 -0
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grading.d.ts +2 -1
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +16 -4
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/index.d.ts +1 -1
- package/lib/orchestrator/index.d.ts.map +1 -1
- package/lib/orchestrator/index.js +1 -1
- package/lib/orchestrator/index.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +7 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +50 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/loader.d.ts.map +1 -1
- package/lib/orchestrator/loader.js +2 -0
- package/lib/orchestrator/loader.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +3 -0
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +42 -14
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/owned-lock.d.ts.map +1 -1
- package/lib/orchestrator/owned-lock.js +52 -38
- package/lib/orchestrator/owned-lock.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +6 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/regrade.js +8 -4
- package/lib/orchestrator/regrade.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +2 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +20 -5
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +34 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +68 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +45 -7
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/artifacts.d.ts +25 -0
- package/lib/previewer/artifacts.d.ts.map +1 -0
- package/lib/previewer/artifacts.js +180 -0
- package/lib/previewer/artifacts.js.map +1 -0
- package/lib/previewer/server.d.ts +105 -0
- package/lib/previewer/server.d.ts.map +1 -0
- package/lib/previewer/server.js +542 -0
- package/lib/previewer/server.js.map +1 -0
- package/package.json +2 -2
- package/skills/lux/SKILL.md +7 -3
- package/skills/lux/references/cli.md +25 -2
- package/skills/lux/references/eval-authoring.md +158 -4
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/persona/index.ts +20 -4
- package/src/answerers/terminal/index.ts +1 -1
- package/src/assertions/authoring.ts +81 -0
- package/src/assertions/core/helpers.ts +3 -6
- package/src/assertions/core/index.ts +2 -0
- package/src/assertions/core/resource-checks.ts +35 -0
- package/src/authoring.ts +1 -0
- package/src/cli/program.ts +14 -93
- package/src/cli/render/reporter.ts +18 -3
- package/src/cli/run-options.ts +1 -0
- package/src/cli/view.ts +168 -0
- package/src/core/action-events.ts +59 -0
- package/src/core/agent-activity.ts +236 -0
- package/src/core/agent-events.ts +17 -0
- package/src/core/artifact-evidence.ts +47 -4
- package/src/core/assertion.ts +41 -22
- package/src/core/case-authoring-state.ts +65 -0
- package/src/core/case.ts +57 -6
- package/src/core/driver-metrics.ts +51 -0
- package/src/core/driver.ts +3 -0
- package/src/core/events.ts +14 -2
- package/src/core/experiment.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/lifecycle-fixtures.ts +7 -2
- package/src/core/lifecycle-hooks.ts +16 -2
- package/src/core/run.ts +9 -0
- package/src/core/wire.ts +5 -1
- package/src/drivers/claude-code/parse-events.ts +98 -12
- package/src/drivers/codex/app-events.ts +116 -16
- package/src/drivers/codex/index.ts +16 -14
- package/src/drivers/subprocess/index.ts +6 -0
- package/src/index.ts +50 -6
- package/src/orchestrator/annotation-store.ts +5 -1
- package/src/orchestrator/case-extensions.ts +44 -0
- package/src/orchestrator/compare.ts +18 -0
- package/src/orchestrator/config.ts +0 -2
- package/src/orchestrator/doctor.ts +5 -3
- package/src/orchestrator/experiment-evaluation.ts +23 -0
- package/src/orchestrator/grading.ts +21 -2
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/list-runs.ts +66 -5
- package/src/orchestrator/loader.ts +2 -0
- package/src/orchestrator/orchestrator.ts +86 -40
- package/src/orchestrator/owned-lock.ts +54 -34
- package/src/orchestrator/regrade.ts +8 -7
- package/src/orchestrator/run-execution.ts +24 -3
- package/src/orchestrator/suite-summary.ts +101 -8
- package/src/previewer/artifacts.ts +221 -0
- package/src/previewer/server.ts +765 -0
- package/viewer/app.css +2075 -0
- package/viewer/app.js +606 -0
- package/viewer/artifact-view.js +48 -0
- package/viewer/charts.js +86 -0
- package/viewer/detail.js +1214 -0
- package/viewer/format.js +129 -0
- package/viewer/index.html +151 -0
- package/lib/answerers/scripted/index.d.ts +0 -8
- package/lib/answerers/scripted/index.d.ts.map +0 -1
- package/lib/answerers/scripted/index.js +0 -163
- package/lib/answerers/scripted/index.js.map +0 -1
- package/src/answerers/scripted/index.ts +0 -186
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
readRunArtifactBytes,
|
|
34
34
|
valueHash,
|
|
35
35
|
} from "../core/index.js";
|
|
36
|
+
import { registryForEvalCase } from "./case-extensions.js";
|
|
36
37
|
import { evaluatorImplementationHash } from "./evaluator-identity.js";
|
|
37
38
|
import { parsePersistedJson, readBoundedHandle, safeReadJson } from "./fs-read.js";
|
|
38
39
|
import {
|
|
@@ -526,18 +527,18 @@ export const regradeRun = async (options: {
|
|
|
526
527
|
now?: Date;
|
|
527
528
|
}): Promise<{ path: string; snapshot: GradingSnapshot }> => {
|
|
528
529
|
const run = await loadFrozenRegradableRun(options.runDir, options.case);
|
|
530
|
+
const registry = options.case
|
|
531
|
+
? registryForEvalCase(options.registry, options.case)
|
|
532
|
+
: options.registry;
|
|
529
533
|
try {
|
|
530
534
|
const evidenceHash = await gradingEvidenceHash(run);
|
|
531
535
|
const validatedAssertionConfigs = await validateAssertionPlan(
|
|
532
536
|
run.case.assertions ?? [],
|
|
533
|
-
|
|
537
|
+
registry,
|
|
534
538
|
);
|
|
535
539
|
const validatedAssertionsHash = valueHash(validatedAssertionConfigs);
|
|
536
|
-
const assertionEvaluators = assertionEvaluatorIdentities(
|
|
537
|
-
|
|
538
|
-
options.registry,
|
|
539
|
-
);
|
|
540
|
-
const assertionPlugins = assertionPluginIdentities(run.case.assertions ?? [], options.registry);
|
|
540
|
+
const assertionEvaluators = assertionEvaluatorIdentities(run.case.assertions ?? [], registry);
|
|
541
|
+
const assertionPlugins = assertionPluginIdentities(run.case.assertions ?? [], registry);
|
|
541
542
|
const graderHash = graderConfigHash({
|
|
542
543
|
luxVersion: LUX_VERSION,
|
|
543
544
|
implementationHash: await currentEvaluatorImplementationHash(),
|
|
@@ -547,7 +548,7 @@ export const regradeRun = async (options: {
|
|
|
547
548
|
});
|
|
548
549
|
const report: GradingReport = await gradeRun(
|
|
549
550
|
run,
|
|
550
|
-
|
|
551
|
+
registry,
|
|
551
552
|
undefined,
|
|
552
553
|
options.abortSignal,
|
|
553
554
|
undefined,
|
|
@@ -10,13 +10,16 @@ import {
|
|
|
10
10
|
AnswererModelCallAccountingSchema,
|
|
11
11
|
type AnswerHistoryEntry,
|
|
12
12
|
type Artifact,
|
|
13
|
+
actionTiming,
|
|
13
14
|
type EvalCase,
|
|
14
15
|
emptyUsage,
|
|
15
16
|
type FixturesRef,
|
|
17
|
+
type FixtureValueBinding,
|
|
16
18
|
type GradingReport,
|
|
17
19
|
guardRunObserver,
|
|
18
20
|
inspectRunArtifact,
|
|
19
21
|
type LifecycleStage,
|
|
22
|
+
normalizeDriverMetrics,
|
|
20
23
|
type OperationalFailure,
|
|
21
24
|
type PhaseDurations,
|
|
22
25
|
type ReadyState,
|
|
@@ -27,6 +30,7 @@ import {
|
|
|
27
30
|
type RunMetadata,
|
|
28
31
|
type RunProvenance,
|
|
29
32
|
type RunUsage,
|
|
33
|
+
summarizeAgentActivity,
|
|
30
34
|
summarizeToolOutcomes,
|
|
31
35
|
sumUsage,
|
|
32
36
|
type TokenUsage,
|
|
@@ -70,6 +74,7 @@ export type RunWithPluginsArgs = {
|
|
|
70
74
|
validatedAssertionsHash: string;
|
|
71
75
|
graderConfigHash: string;
|
|
72
76
|
evaluatorConfigHash: string;
|
|
77
|
+
fixtureValues: readonly FixtureValueBinding[];
|
|
73
78
|
luxVersion: string;
|
|
74
79
|
registry: Registry;
|
|
75
80
|
driver: AgentDriver;
|
|
@@ -181,6 +186,11 @@ const modelAccountingFromSummary = (
|
|
|
181
186
|
): { modelCalls: number; unpricedModelCalls?: number | undefined } | null =>
|
|
182
187
|
SummaryModelAccountingSchema.safeParse(summary).data ?? null;
|
|
183
188
|
|
|
189
|
+
const metricsFromSummary = (summary: unknown) => {
|
|
190
|
+
if (!summary || typeof summary !== "object") return {};
|
|
191
|
+
return normalizeDriverMetrics((summary as { metrics?: unknown }).metrics);
|
|
192
|
+
};
|
|
193
|
+
|
|
184
194
|
const artifactMeasurements = async (
|
|
185
195
|
artifacts: readonly Artifact[],
|
|
186
196
|
runDir: string,
|
|
@@ -360,6 +370,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
360
370
|
caseId: this.args.caseId,
|
|
361
371
|
prompt: evalCase.prompt,
|
|
362
372
|
fixtures: staged?.fixtures,
|
|
373
|
+
...(this.args.fixtureValues.length > 0 ? { fixtureValues: this.args.fixtureValues } : {}),
|
|
363
374
|
runsRoot: resolve(request.runsRoot),
|
|
364
375
|
runDir: this.runDir.path,
|
|
365
376
|
artifactsDir: this.runDir.artifactsDir,
|
|
@@ -387,6 +398,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
387
398
|
metadata.exitReason = "error";
|
|
388
399
|
metadata.operationalFailure = { phase: operationalPhase, reason };
|
|
389
400
|
metadata.phaseDurationMs = phaseDurations(this.runDir.recorded);
|
|
401
|
+
metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
390
402
|
await this.runDir.writeJson("run.json", metadata);
|
|
391
403
|
await this.args.request.recordPreGradingAccounting?.(metadata);
|
|
392
404
|
await this.args.request.recordModelAttemptsComplete?.(this.subjectAttemptIds);
|
|
@@ -404,6 +416,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
404
416
|
this.lifecycle("collect-end");
|
|
405
417
|
await this.runDir.events.drain();
|
|
406
418
|
const completed = await this.completedAgentRun();
|
|
419
|
+
completed.metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
407
420
|
this.metadata = completed.metadata;
|
|
408
421
|
this.usage = completed.usage;
|
|
409
422
|
this.run = completed.run;
|
|
@@ -427,6 +440,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
427
440
|
grading.assertions.flatMap((assertion) => (assertion.usage ? [assertion.usage] : [])),
|
|
428
441
|
);
|
|
429
442
|
metadata.phaseDurationMs = phaseDurations(this.runDir.recorded);
|
|
443
|
+
metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
430
444
|
await this.runDir.events.drain();
|
|
431
445
|
await this.runDir.writeJson("run.json", metadata);
|
|
432
446
|
await this.runDir.writeJson("artifacts.json", this.collection.artifacts);
|
|
@@ -503,6 +517,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
503
517
|
private fallbackFailureMetadata(): RunMetadata {
|
|
504
518
|
const failedAt = new Date();
|
|
505
519
|
const answererAccounting = readAnswererModelAccounting(this.args.answerer);
|
|
520
|
+
const ready = this.ready ?? { agentId: "start-failed" };
|
|
506
521
|
return {
|
|
507
522
|
schemaVersion: 1,
|
|
508
523
|
luxVersion: this.args.luxVersion,
|
|
@@ -511,7 +526,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
511
526
|
agentEndedAt: failedAt.toISOString(),
|
|
512
527
|
endedAt: failedAt.toISOString(),
|
|
513
528
|
exitReason: "error",
|
|
514
|
-
driver: { name: this.args.driverName, ...
|
|
529
|
+
driver: { name: this.args.driverName, ...ready },
|
|
515
530
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
516
531
|
usage: {
|
|
517
532
|
agent: emptyUsage(),
|
|
@@ -527,6 +542,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
527
542
|
interruptCounts: this.interruptCounts,
|
|
528
543
|
toolCallCount: this.toolCallCount,
|
|
529
544
|
toolOutcomes: summarizeToolOutcomes(this.runDir.recorded),
|
|
545
|
+
agentActivity: summarizeAgentActivity(this.runDir.recorded, ready),
|
|
530
546
|
artifacts: { count: this.collection.artifacts.length },
|
|
531
547
|
provenance: this.runProvenance(),
|
|
532
548
|
};
|
|
@@ -557,9 +573,10 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
557
573
|
const outcome = this.require(this.loopOutcome, "driver outcome");
|
|
558
574
|
const agentEndedAt = new Date();
|
|
559
575
|
const exitReason = agentExitReason(outcome, this.collection.error);
|
|
560
|
-
const summary = outcome.kind === "done" ? outcome.done.summary :
|
|
576
|
+
const summary = outcome.kind === "done" ? outcome.done.summary : outcome.error.summary;
|
|
561
577
|
const driverCost = costFromSummary(summary);
|
|
562
578
|
const reportedModelAccounting = modelAccountingFromSummary(summary);
|
|
579
|
+
const driverMetrics = metricsFromSummary(summary);
|
|
563
580
|
const answererAccounting = readAnswererModelAccounting(this.args.answerer);
|
|
564
581
|
const agentCalls = this.ready ? (reportedModelAccounting?.modelCalls ?? 1) : 0;
|
|
565
582
|
const operationalFailure = loopFailure(outcome, this.collection.error);
|
|
@@ -568,6 +585,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
568
585
|
answerer: readAnswererUsage(this.args.answerer),
|
|
569
586
|
grading: emptyUsage(),
|
|
570
587
|
};
|
|
588
|
+
const ready = this.ready ?? { agentId: "not-started" };
|
|
571
589
|
const metadata: RunMetadata = {
|
|
572
590
|
schemaVersion: 1,
|
|
573
591
|
luxVersion: this.args.luxVersion,
|
|
@@ -576,7 +594,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
576
594
|
agentEndedAt: agentEndedAt.toISOString(),
|
|
577
595
|
endedAt: agentEndedAt.toISOString(),
|
|
578
596
|
exitReason,
|
|
579
|
-
driver: { name: this.args.driverName, ...
|
|
597
|
+
driver: { name: this.args.driverName, ...ready },
|
|
580
598
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
581
599
|
usage,
|
|
582
600
|
modelAccounting: {
|
|
@@ -590,10 +608,12 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
590
608
|
interruptCounts: this.interruptCounts,
|
|
591
609
|
toolCallCount: this.toolCallCount,
|
|
592
610
|
toolOutcomes: summarizeToolOutcomes(this.runDir.recorded),
|
|
611
|
+
agentActivity: summarizeAgentActivity(this.runDir.recorded, ready),
|
|
593
612
|
artifacts: await artifactMeasurements(this.collection.artifacts, this.runDir.path),
|
|
594
613
|
...(operationalFailure ? { operationalFailure } : {}),
|
|
595
614
|
provenance: this.runProvenance(),
|
|
596
615
|
...(driverCost !== undefined ? { cost: { usd: driverCost } } : {}),
|
|
616
|
+
...(Object.keys(driverMetrics).length > 0 ? { driverMetrics } : {}),
|
|
597
617
|
};
|
|
598
618
|
return {
|
|
599
619
|
metadata,
|
|
@@ -654,6 +674,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
654
674
|
request.recordGradingAssertion,
|
|
655
675
|
modelAttempts,
|
|
656
676
|
this.args.validatedAssertionConfigs,
|
|
677
|
+
request.skipJudge ?? false,
|
|
657
678
|
);
|
|
658
679
|
}
|
|
659
680
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type DriverMetric,
|
|
6
|
+
DriverMetricUnitSchema,
|
|
7
|
+
harnessUsage,
|
|
8
|
+
isUsageEmpty,
|
|
9
|
+
type RunMetadata,
|
|
10
|
+
} from "../core/index.js";
|
|
5
11
|
import { runComparisonIdentity } from "./comparison-identity.js";
|
|
6
12
|
import { safeReadJson } from "./fs-read.js";
|
|
7
13
|
import type { SuiteCaseResult } from "./orchestrator.js";
|
|
@@ -16,6 +22,9 @@ export const MetricSummarySchema = z.object({
|
|
|
16
22
|
mean: z.number(),
|
|
17
23
|
median: z.number(),
|
|
18
24
|
p90: z.number(),
|
|
25
|
+
unit: z.exactOptional(DriverMetricUnitSchema),
|
|
26
|
+
label: z.exactOptional(z.string()),
|
|
27
|
+
better: z.exactOptional(z.enum(["lower", "higher", "neutral"])),
|
|
19
28
|
});
|
|
20
29
|
export type MetricSummary = z.infer<typeof MetricSummarySchema>;
|
|
21
30
|
|
|
@@ -59,13 +68,22 @@ export const SuiteSummarySchema = z.object({
|
|
|
59
68
|
});
|
|
60
69
|
export type SuiteSummary = z.infer<typeof SuiteSummarySchema>;
|
|
61
70
|
|
|
62
|
-
type MetricSamples = Map<
|
|
71
|
+
type MetricSamples = Map<
|
|
72
|
+
string,
|
|
73
|
+
{ values: number[]; descriptor?: Omit<DriverMetric, "value"> | undefined }
|
|
74
|
+
>;
|
|
63
75
|
|
|
64
|
-
const add = (
|
|
76
|
+
const add = (
|
|
77
|
+
samples: MetricSamples,
|
|
78
|
+
name: string,
|
|
79
|
+
value: number | undefined,
|
|
80
|
+
descriptor?: Omit<DriverMetric, "value">,
|
|
81
|
+
): void => {
|
|
65
82
|
if (value === undefined || !Number.isFinite(value)) return;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
const sample = samples.get(name) ?? { values: [], descriptor };
|
|
84
|
+
if (!sample.descriptor && descriptor) sample.descriptor = descriptor;
|
|
85
|
+
sample.values.push(value);
|
|
86
|
+
samples.set(name, sample);
|
|
69
87
|
};
|
|
70
88
|
|
|
71
89
|
const cacheHitRate = (usage: {
|
|
@@ -137,6 +155,78 @@ const collectMetadata = (samples: MetricSamples, metadata: RunMetadata): void =>
|
|
|
137
155
|
add(samples, "phase.agentMs", metadata.phaseDurationMs?.agent);
|
|
138
156
|
add(samples, "phase.collectMs", metadata.phaseDurationMs?.collect);
|
|
139
157
|
add(samples, "phase.gradingMs", metadata.phaseDurationMs?.grading);
|
|
158
|
+
add(samples, "timeToFirstActionMs", metadata.actionTiming?.timeToFirstActionMs);
|
|
159
|
+
add(samples, "timeToFirstMutationMs", metadata.actionTiming?.timeToFirstMutationMs);
|
|
160
|
+
add(samples, "timeToFirstDurableMutationMs", metadata.actionTiming?.timeToFirstDurableMutationMs);
|
|
161
|
+
const subagents = (metadata.agentActivity ?? []).filter(
|
|
162
|
+
(activity) => activity.agent.role === "subagent",
|
|
163
|
+
);
|
|
164
|
+
add(samples, "subagents.count", subagents.length, {
|
|
165
|
+
unit: "count",
|
|
166
|
+
label: "Subagents spawned",
|
|
167
|
+
better: "neutral",
|
|
168
|
+
});
|
|
169
|
+
add(
|
|
170
|
+
samples,
|
|
171
|
+
"subagents.events",
|
|
172
|
+
subagents.reduce((sum, activity) => sum + activity.eventCount, 0),
|
|
173
|
+
{ unit: "count", label: "Subagent events", better: "neutral" },
|
|
174
|
+
);
|
|
175
|
+
add(
|
|
176
|
+
samples,
|
|
177
|
+
"subagents.toolCalls",
|
|
178
|
+
subagents.reduce((sum, activity) => sum + activity.toolCalls.total, 0),
|
|
179
|
+
{ unit: "count", label: "Subagent tool calls", better: "neutral" },
|
|
180
|
+
);
|
|
181
|
+
add(
|
|
182
|
+
samples,
|
|
183
|
+
"subagents.toolCalls.failed",
|
|
184
|
+
subagents.reduce((sum, activity) => sum + activity.toolCalls.failed, 0),
|
|
185
|
+
{ unit: "count", label: "Failed subagent tool calls", better: "lower" },
|
|
186
|
+
);
|
|
187
|
+
add(
|
|
188
|
+
samples,
|
|
189
|
+
"subagents.tokens",
|
|
190
|
+
subagents.reduce((sum, activity) => sum + activity.usage.total, 0),
|
|
191
|
+
{ unit: "tokens", label: "Subagent tokens", better: "lower" },
|
|
192
|
+
);
|
|
193
|
+
add(
|
|
194
|
+
samples,
|
|
195
|
+
"subagents.actions",
|
|
196
|
+
subagents.reduce((sum, activity) => sum + activity.actions.total, 0),
|
|
197
|
+
{ unit: "count", label: "Subagent actions", better: "neutral" },
|
|
198
|
+
);
|
|
199
|
+
add(
|
|
200
|
+
samples,
|
|
201
|
+
"subagents.mutations",
|
|
202
|
+
subagents.reduce((sum, activity) => sum + activity.actions.mutations, 0),
|
|
203
|
+
{ unit: "count", label: "Subagent mutations", better: "neutral" },
|
|
204
|
+
);
|
|
205
|
+
add(
|
|
206
|
+
samples,
|
|
207
|
+
"subagents.totalDurationMs",
|
|
208
|
+
subagents.reduce((sum, activity) => sum + activity.durationMs, 0),
|
|
209
|
+
{ unit: "milliseconds", label: "Total subagent time", better: "neutral" },
|
|
210
|
+
);
|
|
211
|
+
add(
|
|
212
|
+
samples,
|
|
213
|
+
"subagents.maxDurationMs",
|
|
214
|
+
subagents.length > 0 ? Math.max(...subagents.map((activity) => activity.durationMs)) : 0,
|
|
215
|
+
{ unit: "milliseconds", label: "Longest subagent", better: "lower" },
|
|
216
|
+
);
|
|
217
|
+
const firstSubagentAction = subagents
|
|
218
|
+
.map((activity) => activity.actions.timeToFirstActionMs)
|
|
219
|
+
.filter((value): value is number => value !== undefined)
|
|
220
|
+
.sort((a, b) => a - b)[0];
|
|
221
|
+
add(samples, "subagents.timeToFirstActionMs", firstSubagentAction, {
|
|
222
|
+
unit: "milliseconds",
|
|
223
|
+
label: "First subagent action",
|
|
224
|
+
better: "lower",
|
|
225
|
+
});
|
|
226
|
+
for (const [name, metric] of Object.entries(metadata.driverMetrics ?? {})) {
|
|
227
|
+
const { value, ...descriptor } = metric;
|
|
228
|
+
add(samples, `driver.${name}`, value, descriptor);
|
|
229
|
+
}
|
|
140
230
|
};
|
|
141
231
|
|
|
142
232
|
const percentile = (sorted: readonly number[], fraction: number): number => {
|
|
@@ -165,7 +255,10 @@ const summarizeMetrics = (samples: MetricSamples): Record<string, MetricSummary>
|
|
|
165
255
|
Object.fromEntries(
|
|
166
256
|
[...samples.entries()]
|
|
167
257
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
168
|
-
.map(([name, values]) => [
|
|
258
|
+
.map(([name, { values, descriptor }]) => [
|
|
259
|
+
name,
|
|
260
|
+
MetricSummarySchema.parse({ ...summarizeNumbers(values), ...descriptor }),
|
|
261
|
+
]),
|
|
169
262
|
);
|
|
170
263
|
|
|
171
264
|
const summarizeOutcomes = (
|
|
@@ -258,7 +351,7 @@ export const buildSuiteSummary = (
|
|
|
258
351
|
? { score: result.grading.caseScore }
|
|
259
352
|
: {}),
|
|
260
353
|
metrics: Object.fromEntries(
|
|
261
|
-
[...samples.entries()].map(([name,
|
|
354
|
+
[...samples.entries()].map(([name, sample]) => [name, sample.values[0] ?? 0]),
|
|
262
355
|
),
|
|
263
356
|
};
|
|
264
357
|
}),
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { Artifact } from "../core/index.js";
|
|
2
|
+
import { inspectRunArtifact, readRunArtifactBytes } from "../core/index.js";
|
|
3
|
+
|
|
4
|
+
const MAX_PREVIEW_BYTES = 512_000;
|
|
5
|
+
const MAX_DIFF_LINES = 2_000;
|
|
6
|
+
const MAX_DIFF_CELLS = 1_000_000;
|
|
7
|
+
|
|
8
|
+
export type ArtifactPreview = {
|
|
9
|
+
path: string;
|
|
10
|
+
exists: boolean;
|
|
11
|
+
viewable: boolean;
|
|
12
|
+
size: number | null;
|
|
13
|
+
contentType: string | null;
|
|
14
|
+
text: string | null;
|
|
15
|
+
reason: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ArtifactDiffOperation = {
|
|
19
|
+
kind: "same" | "added" | "removed";
|
|
20
|
+
lines: string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ArtifactComparison = {
|
|
24
|
+
path: string;
|
|
25
|
+
before: ArtifactPreview;
|
|
26
|
+
after: ArtifactPreview;
|
|
27
|
+
operations: ArtifactDiffOperation[] | null;
|
|
28
|
+
reason: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const evidenceArtifacts = (
|
|
32
|
+
indexed: readonly Artifact[],
|
|
33
|
+
knownPaths: readonly string[],
|
|
34
|
+
): Artifact[] => {
|
|
35
|
+
const out = [...indexed];
|
|
36
|
+
const indexedPaths = new Set(indexed.map((artifact) => artifact.path));
|
|
37
|
+
for (const path of knownPaths) {
|
|
38
|
+
if (!indexedPaths.has(path)) out.push({ path });
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const loadArtifactPreview = async (
|
|
44
|
+
runDir: string,
|
|
45
|
+
indexed: readonly Artifact[],
|
|
46
|
+
knownPaths: readonly string[],
|
|
47
|
+
path: string,
|
|
48
|
+
): Promise<ArtifactPreview> => {
|
|
49
|
+
if (!knownPaths.includes(path) && !indexed.some((artifact) => artifact.path === path)) {
|
|
50
|
+
return {
|
|
51
|
+
path,
|
|
52
|
+
exists: false,
|
|
53
|
+
viewable: false,
|
|
54
|
+
size: null,
|
|
55
|
+
contentType: null,
|
|
56
|
+
text: null,
|
|
57
|
+
reason: "artifact was not captured by this run",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const artifacts = evidenceArtifacts(indexed, knownPaths);
|
|
61
|
+
const run = { runDir, artifacts };
|
|
62
|
+
const inspected = await inspectRunArtifact(run, path);
|
|
63
|
+
const contentType = inspected.artifact?.contentType ?? null;
|
|
64
|
+
if (!inspected.stats?.isFile()) {
|
|
65
|
+
return {
|
|
66
|
+
path,
|
|
67
|
+
exists: false,
|
|
68
|
+
viewable: false,
|
|
69
|
+
size: inspected.stats?.size ?? null,
|
|
70
|
+
contentType,
|
|
71
|
+
text: null,
|
|
72
|
+
reason: inspected.issue ?? "artifact is not a regular file",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (inspected.stats.size > MAX_PREVIEW_BYTES) {
|
|
76
|
+
return {
|
|
77
|
+
path,
|
|
78
|
+
exists: true,
|
|
79
|
+
viewable: false,
|
|
80
|
+
size: inspected.stats.size,
|
|
81
|
+
contentType,
|
|
82
|
+
text: null,
|
|
83
|
+
reason: `artifact exceeds the ${MAX_PREVIEW_BYTES}-byte preview limit`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const bytes = await readRunArtifactBytes(run, path, { maxBytes: MAX_PREVIEW_BYTES });
|
|
87
|
+
let text: string;
|
|
88
|
+
try {
|
|
89
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
90
|
+
} catch {
|
|
91
|
+
return {
|
|
92
|
+
path,
|
|
93
|
+
exists: true,
|
|
94
|
+
viewable: false,
|
|
95
|
+
size: bytes.length,
|
|
96
|
+
contentType,
|
|
97
|
+
text: null,
|
|
98
|
+
reason: "artifact is not valid UTF-8 text",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (text.includes("\0")) {
|
|
102
|
+
return {
|
|
103
|
+
path,
|
|
104
|
+
exists: true,
|
|
105
|
+
viewable: false,
|
|
106
|
+
size: bytes.length,
|
|
107
|
+
contentType,
|
|
108
|
+
text: null,
|
|
109
|
+
reason: "artifact appears to be binary",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
path,
|
|
114
|
+
exists: true,
|
|
115
|
+
viewable: true,
|
|
116
|
+
size: bytes.length,
|
|
117
|
+
contentType,
|
|
118
|
+
text,
|
|
119
|
+
reason: null,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const appendOperation = (
|
|
124
|
+
operations: ArtifactDiffOperation[],
|
|
125
|
+
kind: ArtifactDiffOperation["kind"],
|
|
126
|
+
line: string,
|
|
127
|
+
): void => {
|
|
128
|
+
const current = operations.at(-1);
|
|
129
|
+
if (current?.kind === kind) current.lines.push(line);
|
|
130
|
+
else operations.push({ kind, lines: [line] });
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const diffLines = (beforeText: string, afterText: string): ArtifactDiffOperation[] | null => {
|
|
134
|
+
const before = beforeText.split("\n");
|
|
135
|
+
const after = afterText.split("\n");
|
|
136
|
+
if (
|
|
137
|
+
before.length > MAX_DIFF_LINES ||
|
|
138
|
+
after.length > MAX_DIFF_LINES ||
|
|
139
|
+
before.length * after.length > MAX_DIFF_CELLS
|
|
140
|
+
) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const width = after.length + 1;
|
|
144
|
+
const table = new Uint32Array((before.length + 1) * width);
|
|
145
|
+
for (let left = before.length - 1; left >= 0; left--) {
|
|
146
|
+
for (let right = after.length - 1; right >= 0; right--) {
|
|
147
|
+
const cell = left * width + right;
|
|
148
|
+
table[cell] =
|
|
149
|
+
before[left] === after[right]
|
|
150
|
+
? (table[(left + 1) * width + right + 1] ?? 0) + 1
|
|
151
|
+
: Math.max(table[(left + 1) * width + right] ?? 0, table[left * width + right + 1] ?? 0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const operations: ArtifactDiffOperation[] = [];
|
|
155
|
+
let left = 0;
|
|
156
|
+
let right = 0;
|
|
157
|
+
while (left < before.length && right < after.length) {
|
|
158
|
+
if (before[left] === after[right]) {
|
|
159
|
+
appendOperation(operations, "same", before[left] ?? "");
|
|
160
|
+
left++;
|
|
161
|
+
right++;
|
|
162
|
+
} else if ((table[(left + 1) * width + right] ?? 0) >= (table[left * width + right + 1] ?? 0)) {
|
|
163
|
+
appendOperation(operations, "removed", before[left] ?? "");
|
|
164
|
+
left++;
|
|
165
|
+
} else {
|
|
166
|
+
appendOperation(operations, "added", after[right] ?? "");
|
|
167
|
+
right++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
while (left < before.length) {
|
|
171
|
+
appendOperation(operations, "removed", before[left] ?? "");
|
|
172
|
+
left++;
|
|
173
|
+
}
|
|
174
|
+
while (right < after.length) {
|
|
175
|
+
appendOperation(operations, "added", after[right] ?? "");
|
|
176
|
+
right++;
|
|
177
|
+
}
|
|
178
|
+
return operations;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const compareArtifactPreviews = (
|
|
182
|
+
path: string,
|
|
183
|
+
before: ArtifactPreview,
|
|
184
|
+
after: ArtifactPreview,
|
|
185
|
+
): ArtifactComparison => {
|
|
186
|
+
if (before.viewable && before.text !== null && !after.exists) {
|
|
187
|
+
return {
|
|
188
|
+
path,
|
|
189
|
+
before,
|
|
190
|
+
after,
|
|
191
|
+
operations: [{ kind: "removed", lines: before.text.split("\n") }],
|
|
192
|
+
reason: null,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (!before.exists && after.viewable && after.text !== null) {
|
|
196
|
+
return {
|
|
197
|
+
path,
|
|
198
|
+
before,
|
|
199
|
+
after,
|
|
200
|
+
operations: [{ kind: "added", lines: after.text.split("\n") }],
|
|
201
|
+
reason: null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (!before.viewable || !after.viewable || before.text === null || after.text === null) {
|
|
205
|
+
return {
|
|
206
|
+
path,
|
|
207
|
+
before,
|
|
208
|
+
after,
|
|
209
|
+
operations: null,
|
|
210
|
+
reason: before.reason ?? after.reason ?? "artifact text is unavailable",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const operations = diffLines(before.text, after.text);
|
|
214
|
+
return {
|
|
215
|
+
path,
|
|
216
|
+
before,
|
|
217
|
+
after,
|
|
218
|
+
operations,
|
|
219
|
+
reason: operations ? null : "artifact is too line-dense for an inline diff",
|
|
220
|
+
};
|
|
221
|
+
};
|