@crewhaus/eval-runner 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate.d.ts +9 -0
- package/dist/aggregate.js +9 -0
- package/dist/index.js +19 -1
- package/dist/run-sample.js +11 -1
- package/dist/types.d.ts +48 -0
- package/package.json +30 -30
package/dist/aggregate.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { SampleResult } from "./types";
|
|
2
2
|
export declare function quantile(sorted: ReadonlyArray<number>, q: number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Retry honesty (RunEvalOptions.retryErrors): a retried sample appears in
|
|
5
|
+
* `samples` exactly ONCE — the retry REPLACED the errored first attempt —
|
|
6
|
+
* so passRate's denominator still counts each dataset sample once. A retry
|
|
7
|
+
* that passes counts as a normal pass; a retry that errors again lands in
|
|
8
|
+
* `errorCount` (and, like every errored sample, drags passRate down while
|
|
9
|
+
* staying out of meanScore/latency/token aggregates). The discarded first
|
|
10
|
+
* attempt is counted nowhere, by design: it was infra noise, not a result.
|
|
11
|
+
*/
|
|
3
12
|
export declare function aggregate(samples: ReadonlyArray<SampleResult>): {
|
|
4
13
|
passRate: number;
|
|
5
14
|
meanScore: number;
|
package/dist/aggregate.js
CHANGED
|
@@ -11,6 +11,15 @@ export function quantile(sorted, q) {
|
|
|
11
11
|
const fract = idx - lo;
|
|
12
12
|
return (sorted[lo] ?? 0) * (1 - fract) + (sorted[hi] ?? 0) * fract;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Retry honesty (RunEvalOptions.retryErrors): a retried sample appears in
|
|
16
|
+
* `samples` exactly ONCE — the retry REPLACED the errored first attempt —
|
|
17
|
+
* so passRate's denominator still counts each dataset sample once. A retry
|
|
18
|
+
* that passes counts as a normal pass; a retry that errors again lands in
|
|
19
|
+
* `errorCount` (and, like every errored sample, drags passRate down while
|
|
20
|
+
* staying out of meanScore/latency/token aggregates). The discarded first
|
|
21
|
+
* attempt is counted nowhere, by design: it was infra noise, not a result.
|
|
22
|
+
*/
|
|
14
23
|
export function aggregate(samples) {
|
|
15
24
|
const ok = samples.filter((s) => s.error === undefined);
|
|
16
25
|
const total = samples.length;
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,8 @@ export async function runEval(args) {
|
|
|
86
86
|
startedAt,
|
|
87
87
|
specHash,
|
|
88
88
|
datasetName: dataset.name,
|
|
89
|
+
...(opts.datasetHash !== undefined ? { datasetHash: opts.datasetHash } : {}),
|
|
90
|
+
...(opts.gradersHash !== undefined ? { gradersHash: opts.gradersHash } : {}),
|
|
89
91
|
graderNames: graders.map((g) => g.name),
|
|
90
92
|
model: ir.agent.model,
|
|
91
93
|
...(opts.judgeModel !== undefined ? { judgeModel: opts.judgeModel } : {}),
|
|
@@ -117,7 +119,7 @@ export async function runEval(args) {
|
|
|
117
119
|
throw new RunnerError(`run interrupted before sample "${sample.id}"`);
|
|
118
120
|
}
|
|
119
121
|
try {
|
|
120
|
-
|
|
122
|
+
const runOnce = () => runSample({
|
|
121
123
|
sample,
|
|
122
124
|
invoker,
|
|
123
125
|
graders,
|
|
@@ -125,6 +127,20 @@ export async function runEval(args) {
|
|
|
125
127
|
model: ir.agent.model,
|
|
126
128
|
...(opts.seed !== undefined ? { seed: opts.seed } : {}),
|
|
127
129
|
});
|
|
130
|
+
const first = await runOnce();
|
|
131
|
+
// Noise auto-retry (failure-arbiter item 7): an errored SampleResult
|
|
132
|
+
// means the INVOKER failed (provider timeout, 429, sandbox blip),
|
|
133
|
+
// and a graderError means a GRADER threw (judge infra blip) — both
|
|
134
|
+
// are infra noise, not graded failures. Retry exactly once within
|
|
135
|
+
// the run; the retried outcome replaces the errored one wholesale
|
|
136
|
+
// (the second runSample rewrites the same per-sample artifact dir)
|
|
137
|
+
// and is tagged `retried: true` so reports and triage can tell.
|
|
138
|
+
// Skipped on SIGINT or when the caller opted out (`--no-retry`).
|
|
139
|
+
const infraNoise = first.error !== undefined || first.graderError !== undefined;
|
|
140
|
+
if (!infraNoise || opts.retryErrors === false || interrupted) {
|
|
141
|
+
return first;
|
|
142
|
+
}
|
|
143
|
+
return { ...(await runOnce()), retried: true };
|
|
128
144
|
}
|
|
129
145
|
finally {
|
|
130
146
|
release();
|
|
@@ -163,6 +179,8 @@ export async function runEval(args) {
|
|
|
163
179
|
config: {
|
|
164
180
|
specHash,
|
|
165
181
|
datasetName: dataset.name,
|
|
182
|
+
...(opts.datasetHash !== undefined ? { datasetHash: opts.datasetHash } : {}),
|
|
183
|
+
...(opts.gradersHash !== undefined ? { gradersHash: opts.gradersHash } : {}),
|
|
166
184
|
graderNames: graders.map((g) => g.name),
|
|
167
185
|
model: ir.agent.model,
|
|
168
186
|
...(opts.judgeModel !== undefined ? { judgeModel: opts.judgeModel } : {}),
|
package/dist/run-sample.js
CHANGED
|
@@ -79,20 +79,28 @@ export async function runSample(args) {
|
|
|
79
79
|
latencyMs,
|
|
80
80
|
};
|
|
81
81
|
const perGrader = [];
|
|
82
|
+
// A grader throwing is grader INFRA noise (judge 429/timeout), not a graded
|
|
83
|
+
// verdict on the agent's output. It still lands as a failed perGrader entry
|
|
84
|
+
// (so `overall` honestly fails), but the structured `graderError` below
|
|
85
|
+
// lets the run loop retry the sample and triage classify it as noise.
|
|
86
|
+
const graderErrors = [];
|
|
82
87
|
for (const g of graders) {
|
|
83
88
|
let result;
|
|
84
89
|
try {
|
|
85
90
|
result = await g.grader(sample, runResult);
|
|
86
91
|
}
|
|
87
92
|
catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
graderErrors.push(`${g.name}: ${msg}`);
|
|
88
95
|
result = {
|
|
89
96
|
passed: false,
|
|
90
97
|
score: 0,
|
|
91
|
-
rationale: `grader threw: ${
|
|
98
|
+
rationale: `grader threw: ${msg}`,
|
|
92
99
|
};
|
|
93
100
|
}
|
|
94
101
|
perGrader.push({ name: g.name, ...result });
|
|
95
102
|
}
|
|
103
|
+
const graderError = graderErrors.length > 0 ? `grader threw: ${graderErrors.join("; ")}` : undefined;
|
|
96
104
|
// Overall = AND of all graders, score = mean.
|
|
97
105
|
const overall = {
|
|
98
106
|
passed: error === undefined && perGrader.every((g) => g.passed),
|
|
@@ -113,6 +121,7 @@ export async function runSample(args) {
|
|
|
113
121
|
agentOutput,
|
|
114
122
|
grades: { overall, perGrader },
|
|
115
123
|
...(error !== undefined ? { error } : {}),
|
|
124
|
+
...(graderError !== undefined ? { graderError } : {}),
|
|
116
125
|
};
|
|
117
126
|
await Bun.write(join(sampleDir, "grades.json"), JSON.stringify(sampleResult.grades, null, 2));
|
|
118
127
|
await Bun.write(join(sampleDir, "meta.json"), JSON.stringify({
|
|
@@ -125,6 +134,7 @@ export async function runSample(args) {
|
|
|
125
134
|
tokens,
|
|
126
135
|
model,
|
|
127
136
|
...(error !== undefined ? { error } : {}),
|
|
137
|
+
...(graderError !== undefined ? { graderError } : {}),
|
|
128
138
|
...(args.seed !== undefined ? { seed: args.seed } : {}),
|
|
129
139
|
}, null, 2));
|
|
130
140
|
return sampleResult;
|
package/dist/types.d.ts
CHANGED
|
@@ -52,6 +52,24 @@ export type SampleResult = {
|
|
|
52
52
|
} & GradeResult>;
|
|
53
53
|
};
|
|
54
54
|
readonly error?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Set when one or more GRADERS threw while grading this sample (judge
|
|
57
|
+
* provider 429/timeout, rubric fetch failure, …) — grader infrastructure
|
|
58
|
+
* noise, distinct from `error` (the INVOKER failed) and from an honest
|
|
59
|
+
* graded failure. The thrown grader still contributes a failed
|
|
60
|
+
* `perGrader` entry (rationale `grader threw: …`), so `grades.overall`
|
|
61
|
+
* fails; this field preserves the structured evidence so the retry loop
|
|
62
|
+
* can retry the sample and triage can classify the failure as noise.
|
|
63
|
+
*/
|
|
64
|
+
readonly graderError?: string;
|
|
65
|
+
/**
|
|
66
|
+
* True when this result replaced an ERRORED first attempt via the runner's
|
|
67
|
+
* bounded noise retry (see {@link RunEvalOptions.retryErrors}) — invoker
|
|
68
|
+
* errors and grader throws (`graderError`) alike. Set on the retried
|
|
69
|
+
* outcome regardless of whether the retry passed or errored again; absent
|
|
70
|
+
* on samples that succeeded (or failed grading) on attempt one.
|
|
71
|
+
*/
|
|
72
|
+
readonly retried?: boolean;
|
|
55
73
|
};
|
|
56
74
|
export type EvalRunSummary = {
|
|
57
75
|
readonly runId: string;
|
|
@@ -74,6 +92,10 @@ export type EvalRunSummary = {
|
|
|
74
92
|
readonly config: {
|
|
75
93
|
readonly specHash: string;
|
|
76
94
|
readonly datasetName: string;
|
|
95
|
+
/** sha256 of the dataset file bytes, when the caller supplied one. */
|
|
96
|
+
readonly datasetHash?: string;
|
|
97
|
+
/** sha256 of the parsed GradersConfig, when the caller supplied one. */
|
|
98
|
+
readonly gradersHash?: string;
|
|
77
99
|
readonly graderNames: ReadonlyArray<string>;
|
|
78
100
|
readonly model: string;
|
|
79
101
|
readonly judgeModel?: string;
|
|
@@ -90,6 +112,32 @@ export type RunEvalOptions = {
|
|
|
90
112
|
readonly judgeModel?: string;
|
|
91
113
|
readonly invoker?: AgentInvoker;
|
|
92
114
|
readonly cwd?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Retry a sample ONCE, within the run, when its result is an ERROR
|
|
117
|
+
* (`SampleResult.error` — the INVOKER failed: provider timeout, 429,
|
|
118
|
+
* sandbox blip) or a GRADER threw (`SampleResult.graderError` — judge
|
|
119
|
+
* infra noise; the agent may have answered fine). Infra noise, not a
|
|
120
|
+
* graded failure. The retried outcome replaces the errored one wholesale
|
|
121
|
+
* (per-sample artifacts included) and is tagged `retried: true`.
|
|
122
|
+
* Default: true. `crewhaus eval --no-retry` opts out. Interrupted runs
|
|
123
|
+
* (SIGINT) never retry.
|
|
124
|
+
*/
|
|
125
|
+
readonly retryErrors?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Content hash (sha256 hex) of the dataset file the samples came from.
|
|
128
|
+
* Purely informational — persisted into `run.json` / `results.json` so
|
|
129
|
+
* downstream consumers (run-history index, dataset-drift detection) can
|
|
130
|
+
* tell whether two runs saw byte-identical data.
|
|
131
|
+
*/
|
|
132
|
+
readonly datasetHash?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Content hash (sha256 hex) of the parsed GradersConfig the graders came
|
|
135
|
+
* from. Mirrors {@link datasetHash}: purely informational, persisted into
|
|
136
|
+
* `run.json` / `results.json` so downstream consumers (notably the item-30
|
|
137
|
+
* sentinel) can tell whether two runs graded with byte-identical config —
|
|
138
|
+
* a changed rubric/threshold must not be misattributed to provider drift.
|
|
139
|
+
*/
|
|
140
|
+
readonly gradersHash?: string;
|
|
93
141
|
};
|
|
94
142
|
export type GraderEntry = {
|
|
95
143
|
readonly name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/eval-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Eval runner — per-sample isolation, concurrency, grading, persistence",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,35 +15,35 @@
|
|
|
15
15
|
"test": "bun test src"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@crewhaus/agent-context-isolation": "0.
|
|
19
|
-
"@crewhaus/compiler": "0.
|
|
20
|
-
"@crewhaus/errors": "0.
|
|
21
|
-
"@crewhaus/eval-dataset": "0.
|
|
22
|
-
"@crewhaus/eval-grader": "0.
|
|
23
|
-
"@crewhaus/eval-judge": "0.
|
|
24
|
-
"@crewhaus/event-log": "0.
|
|
25
|
-
"@crewhaus/hooks-engine": "0.
|
|
26
|
-
"@crewhaus/ir": "0.
|
|
27
|
-
"@crewhaus/logging": "0.
|
|
28
|
-
"@crewhaus/mcp-host": "0.
|
|
29
|
-
"@crewhaus/permission-engine": "0.
|
|
30
|
-
"@crewhaus/run-context": "0.
|
|
31
|
-
"@crewhaus/runtime-core": "0.
|
|
32
|
-
"@crewhaus/skills-registry": "0.
|
|
33
|
-
"@crewhaus/slash-commands": "0.
|
|
34
|
-
"@crewhaus/spec": "0.
|
|
35
|
-
"@crewhaus/sub-agent-spawner": "0.
|
|
36
|
-
"@crewhaus/tenancy": "0.
|
|
37
|
-
"@crewhaus/tool-bash": "0.
|
|
38
|
-
"@crewhaus/tool-catalog": "0.
|
|
39
|
-
"@crewhaus/tool-fetch": "0.
|
|
40
|
-
"@crewhaus/tool-fs": "0.
|
|
41
|
-
"@crewhaus/tool-image": "0.
|
|
42
|
-
"@crewhaus/tool-mcp": "0.
|
|
43
|
-
"@crewhaus/tool-task": "0.
|
|
44
|
-
"@crewhaus/tool-todo": "0.
|
|
45
|
-
"@crewhaus/tool-web": "0.
|
|
46
|
-
"@crewhaus/trace-event-bus": "0.
|
|
18
|
+
"@crewhaus/agent-context-isolation": "0.2.0",
|
|
19
|
+
"@crewhaus/compiler": "0.2.0",
|
|
20
|
+
"@crewhaus/errors": "0.2.0",
|
|
21
|
+
"@crewhaus/eval-dataset": "0.2.0",
|
|
22
|
+
"@crewhaus/eval-grader": "0.2.0",
|
|
23
|
+
"@crewhaus/eval-judge": "0.2.0",
|
|
24
|
+
"@crewhaus/event-log": "0.2.0",
|
|
25
|
+
"@crewhaus/hooks-engine": "0.2.0",
|
|
26
|
+
"@crewhaus/ir": "0.2.0",
|
|
27
|
+
"@crewhaus/logging": "0.2.0",
|
|
28
|
+
"@crewhaus/mcp-host": "0.2.0",
|
|
29
|
+
"@crewhaus/permission-engine": "0.2.0",
|
|
30
|
+
"@crewhaus/run-context": "0.2.0",
|
|
31
|
+
"@crewhaus/runtime-core": "0.2.0",
|
|
32
|
+
"@crewhaus/skills-registry": "0.2.0",
|
|
33
|
+
"@crewhaus/slash-commands": "0.2.0",
|
|
34
|
+
"@crewhaus/spec": "0.2.0",
|
|
35
|
+
"@crewhaus/sub-agent-spawner": "0.2.0",
|
|
36
|
+
"@crewhaus/tenancy": "0.2.0",
|
|
37
|
+
"@crewhaus/tool-bash": "0.2.0",
|
|
38
|
+
"@crewhaus/tool-catalog": "0.2.0",
|
|
39
|
+
"@crewhaus/tool-fetch": "0.2.0",
|
|
40
|
+
"@crewhaus/tool-fs": "0.2.0",
|
|
41
|
+
"@crewhaus/tool-image": "0.2.0",
|
|
42
|
+
"@crewhaus/tool-mcp": "0.2.0",
|
|
43
|
+
"@crewhaus/tool-task": "0.2.0",
|
|
44
|
+
"@crewhaus/tool-todo": "0.2.0",
|
|
45
|
+
"@crewhaus/tool-web": "0.2.0",
|
|
46
|
+
"@crewhaus/trace-event-bus": "0.2.0",
|
|
47
47
|
"zod": "^3.23.8"
|
|
48
48
|
},
|
|
49
49
|
"license": "Apache-2.0",
|