stageflow 0.44.0 → 0.46.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/agents/evalGate.js +1 -0
- package/dist/cli/evalCommand.d.ts +1 -1
- package/dist/cli/evalCommand.js +39 -2
- package/dist/cli/hostClient.d.ts +6 -0
- package/dist/cli/hostClient.js +4 -0
- package/dist/config/loadEval.d.ts +3 -1
- package/dist/config/loadEval.js +75 -7
- package/dist/config/validateEval.js +17 -1
- package/dist/evals/calibration.d.ts +21 -0
- package/dist/evals/calibration.js +69 -0
- package/dist/evals/calibrationSuggest.d.ts +29 -0
- package/dist/evals/calibrationSuggest.js +72 -0
- package/dist/evals/calibrator.d.ts +46 -0
- package/dist/evals/calibrator.js +148 -0
- package/dist/evals/compare.d.ts +1 -1
- package/dist/evals/compare.js +3 -0
- package/dist/evals/constants.d.ts +4 -2
- package/dist/evals/constants.js +4 -0
- package/dist/evals/engine/FakeEngine.d.ts +1 -0
- package/dist/evals/engine/FakeEngine.js +7 -2
- package/dist/evals/engine/promptfoo/PromptfooEngine.js +2 -1
- package/dist/evals/engine/promptfoo/generate.d.ts +2 -0
- package/dist/evals/engine/promptfoo/generate.js +33 -16
- package/dist/evals/engine/promptfoo/judgeProviders.js +2 -1
- package/dist/evals/engine/promptfoo/normalize.d.ts +4 -1
- package/dist/evals/engine/promptfoo/normalize.js +16 -3
- package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +2 -0
- package/dist/evals/engine/promptfoo/rubricPrompt.js +10 -2
- package/dist/evals/errors.d.ts +7 -0
- package/dist/evals/errors.js +10 -0
- package/dist/evals/judgeProviders.d.ts +6 -0
- package/dist/evals/judgeProviders.js +14 -0
- package/dist/evals/judged.d.ts +7 -0
- package/dist/evals/judged.js +7 -0
- package/dist/evals/manager.d.ts +14 -8
- package/dist/evals/manager.js +63 -10
- package/dist/evals/plan.d.ts +2 -0
- package/dist/evals/plan.js +5 -4
- package/dist/evals/planInternalAgent.d.ts +1 -1
- package/dist/evals/planInternalAgent.js +2 -6
- package/dist/evals/read/api.d.ts +6 -0
- package/dist/evals/read/api.js +5 -1
- package/dist/evals/read/labelQueue.d.ts +45 -0
- package/dist/evals/read/labelQueue.js +83 -0
- package/dist/evals/stats.d.ts +40 -0
- package/dist/evals/stats.js +69 -0
- package/dist/evals/storeTypes.d.ts +42 -0
- package/dist/evals/types.d.ts +13 -0
- package/dist/evals/votes.d.ts +10 -0
- package/dist/evals/votes.js +60 -0
- package/dist/mcp/evalTools.js +39 -0
- package/dist/operatorAgent/profiles/guideConfirm.js +1 -1
- package/dist/operatorAgent/profiles/stageflowGuide.d.ts +1 -1
- package/dist/operatorAgent/profiles/stageflowGuide.js +8 -7
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/port.d.ts +13 -1
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +10 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +25 -0
- package/dist/runstore/sqlite/calibrationStore.d.ts +10 -0
- package/dist/runstore/sqlite/calibrationStore.js +106 -0
- package/dist/runstore/sqlite/evalStore.d.ts +5 -0
- package/dist/runstore/sqlite/evalStore.js +7 -0
- package/dist/runstore/sqlite/migrations/018-eval-grader-labels.d.ts +11 -0
- package/dist/runstore/sqlite/migrations/018-eval-grader-labels.js +43 -0
- package/dist/runstore/sqlite/migrations/index.js +2 -0
- package/dist/server/evalRoutes.js +213 -3
- package/dist/toolkit/meta.js +2 -0
- package/dist/ui/assets/{MarkdownText-S5C_vemc.js → MarkdownText-BS2BBbCx.js} +1 -1
- package/dist/ui/assets/adapters-DPD-cAsy.js +17 -0
- package/dist/ui/assets/chat-BD1-VTT6.js +2 -0
- package/dist/ui/assets/chat-DXnQv9st.css +2 -0
- package/dist/ui/assets/main-AX5XNLtX.js +278 -0
- package/dist/ui/assets/{main-Del1GeLf.css → main-CFX0_n8J.css} +1 -1
- package/dist/ui/chat.html +3 -3
- package/dist/ui/index.html +3 -3
- package/package.json +1 -1
- package/skills/stageflow/references/control-surface.md +1 -1
- package/skills/stageflow/references/evals.md +1 -0
- package/dist/ui/assets/adapters-C0l4ruiR.js +0 -17
- package/dist/ui/assets/chat-CjnsmvqR.js +0 -2
- package/dist/ui/assets/chat-DFDd4UjS.css +0 -2
- package/dist/ui/assets/main-_yXVqcW9.js +0 -277
package/dist/agents/evalGate.js
CHANGED
|
@@ -9,6 +9,7 @@ const ALWAYS_BLOCKED = new Set([
|
|
|
9
9
|
"stageflow.agent_chat",
|
|
10
10
|
"stageflow.fire_trigger",
|
|
11
11
|
"stageflow.sort_eval_failures",
|
|
12
|
+
"stageflow.calibrate_eval_grader",
|
|
12
13
|
"stageflow.cancel_eval_run",
|
|
13
14
|
]);
|
|
14
15
|
const BLOCKED_UNLESS_REAL = new Set(["stageflow.probe_project_mcp"]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RunNpm } from "../evals/engine/promptfoo/install.js";
|
|
2
2
|
import { type EnsureGlobalServiceResult } from "../server/ensureGlobalService.js";
|
|
3
|
-
export declare const EVAL_USAGE = "Usage:\n sf eval list [--json]\n sf eval show <id|path> [--json]\n sf eval validate [<id|path>] [--json] [--strict]\n sf eval run <id|path> [--repeats <n>] [--judge <model>] [--model <id>] [--user-model <model>] [--allow-real-tools] [--max-cost <usd>] [--case <id> ...] [--tag <tag> ...] [--label <text>] [--no-wait] [--json]\n sf eval runs [<evalId>] [--json]\n sf eval runs show <evalRunId> [--json]\n sf eval runs cancel <evalRunId>\n sf eval runs delete <evalRunId>\n sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]\n sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]\n sf eval compare <evalRunIdA> <evalRunIdB> [--json]\n sf eval datasets [--json]\n sf eval engine status [--json]\n sf eval engine install\n\n Agent evals: --model overrides the agent's model, --user-model the model that plays the user. An eval whose\n conversation.tools is real also needs --allow-real-tools.\n\n Every subcommand also accepts --project-root <id-or-path>.";
|
|
3
|
+
export declare const EVAL_USAGE = "Usage:\n sf eval list [--json]\n sf eval show <id|path> [--json]\n sf eval validate [<id|path>] [--json] [--strict]\n sf eval run <id|path> [--repeats <n>] [--judge <model>] [--model <id>] [--user-model <model>] [--allow-real-tools] [--max-cost <usd>] [--case <id> ...] [--tag <tag> ...] [--label <text>] [--no-wait] [--json]\n sf eval runs [<evalId>] [--json]\n sf eval runs show <evalRunId> [--json]\n sf eval runs cancel <evalRunId>\n sf eval runs delete <evalRunId>\n sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]\n sf eval calibrate <evalId> <graderId> [--split all|dev|held_out] [--max-cost <usd>] [--stability] [--json]\n sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]\n sf eval compare <evalRunIdA> <evalRunIdB> [--json]\n sf eval datasets [--json]\n sf eval engine status [--json]\n sf eval engine install\n\n Agent evals: --model overrides the agent's model, --user-model the model that plays the user. An eval whose\n conversation.tools is real also needs --allow-real-tools.\n\n Every subcommand also accepts --project-root <id-or-path>.";
|
|
4
4
|
export type EvalCommandIo = {
|
|
5
5
|
log: (line: string) => void;
|
|
6
6
|
error: (line: string) => void;
|
package/dist/cli/evalCommand.js
CHANGED
|
@@ -9,7 +9,7 @@ import { installPromptfoo } from "../evals/engine/promptfoo/install.js";
|
|
|
9
9
|
import { engineStatus } from "../evals/engine/promptfoo/status.js";
|
|
10
10
|
import { globalStageflowHome } from "../project/globalHome.js";
|
|
11
11
|
import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
|
|
12
|
-
import { httpCancelEvalRun, httpCompareEvalRuns, httpDeleteEvalRun, httpEnsureProject, httpGetEval, httpGetEvalRun, httpListDatasets, httpListEvalRuns, httpListEvalSuggestions, httpSortEvalFailures, httpListEvals, httpStartEvalRun, } from "./hostClient.js";
|
|
12
|
+
import { httpCancelEvalRun, httpCompareEvalRuns, httpDeleteEvalRun, httpEnsureProject, httpGetEval, httpGetEvalRun, httpListDatasets, httpListEvalRuns, httpListEvalSuggestions, httpCalibrateEvalGrader, httpSortEvalFailures, httpListEvals, httpStartEvalRun, } from "./hostClient.js";
|
|
13
13
|
import { resolveCliProjectRootLocal } from "./resolveCliProjectRoot.js";
|
|
14
14
|
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
15
15
|
export const EVAL_USAGE = `Usage:
|
|
@@ -22,6 +22,7 @@ export const EVAL_USAGE = `Usage:
|
|
|
22
22
|
sf eval runs cancel <evalRunId>
|
|
23
23
|
sf eval runs delete <evalRunId>
|
|
24
24
|
sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]
|
|
25
|
+
sf eval calibrate <evalId> <graderId> [--split all|dev|held_out] [--max-cost <usd>] [--stability] [--json]
|
|
25
26
|
sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]
|
|
26
27
|
sf eval compare <evalRunIdA> <evalRunIdB> [--json]
|
|
27
28
|
sf eval datasets [--json]
|
|
@@ -61,6 +62,7 @@ const FLAGS = {
|
|
|
61
62
|
runs: {},
|
|
62
63
|
sort: { "--classifier": { value: true }, "--limit": { value: true }, "--resort": {} },
|
|
63
64
|
suggestions: { "--status": { value: true } },
|
|
65
|
+
calibrate: { "--split": { value: true }, "--max-cost": { value: true }, "--stability": {} },
|
|
64
66
|
compare: {},
|
|
65
67
|
datasets: {},
|
|
66
68
|
engine: {},
|
|
@@ -76,6 +78,7 @@ function parseArgs(command, rest) {
|
|
|
76
78
|
noWait: false,
|
|
77
79
|
allowRealTools: false,
|
|
78
80
|
resort: false,
|
|
81
|
+
stability: false,
|
|
79
82
|
cases: [],
|
|
80
83
|
tags: [],
|
|
81
84
|
positionals: [],
|
|
@@ -150,6 +153,15 @@ function parseArgs(command, rest) {
|
|
|
150
153
|
case "--resort":
|
|
151
154
|
parsed.resort = true;
|
|
152
155
|
break;
|
|
156
|
+
case "--stability":
|
|
157
|
+
parsed.stability = true;
|
|
158
|
+
break;
|
|
159
|
+
case "--split":
|
|
160
|
+
if (value !== "all" && value !== "dev" && value !== "held_out") {
|
|
161
|
+
throw new UsageError("--split must be all, dev or held_out");
|
|
162
|
+
}
|
|
163
|
+
parsed.split = value;
|
|
164
|
+
break;
|
|
153
165
|
case "--status":
|
|
154
166
|
if (value !== "pending" && value !== "accepted" && value !== "dismissed") {
|
|
155
167
|
throw new UsageError("--status must be pending, accepted or dismissed");
|
|
@@ -404,7 +416,9 @@ export async function runEvalCommand(args, options = {}) {
|
|
|
404
416
|
? expectArgs(2, 2, "<evalRunIdA> <evalRunIdB>")
|
|
405
417
|
: command === "sort" || command === "suggestions"
|
|
406
418
|
? expectArgs(1, 1, "<evalRunId>")
|
|
407
|
-
:
|
|
419
|
+
: command === "calibrate"
|
|
420
|
+
? expectArgs(2, 2, "<evalId> <graderId>")
|
|
421
|
+
: null;
|
|
408
422
|
if (argError)
|
|
409
423
|
return usageError(argError);
|
|
410
424
|
let runsSub;
|
|
@@ -554,6 +568,29 @@ export async function runEvalCommand(args, options = {}) {
|
|
|
554
568
|
out.log(`Review and accept in the console, or POST /api/eval-runs/${positionals[0]}/failures/suggestions/accept`);
|
|
555
569
|
});
|
|
556
570
|
}
|
|
571
|
+
case "calibrate": {
|
|
572
|
+
const result = await httpCalibrateEvalGrader(base, positionals[0], positionals[1], {
|
|
573
|
+
project_root: wireRoot,
|
|
574
|
+
...(parsed.split !== undefined ? { split: parsed.split } : {}),
|
|
575
|
+
...(parsed.maxCost !== undefined ? { max_cost_usd: parsed.maxCost } : {}),
|
|
576
|
+
...(parsed.stability ? { stability: true } : {}),
|
|
577
|
+
});
|
|
578
|
+
return show(result, (b) => {
|
|
579
|
+
const st = b.stats;
|
|
580
|
+
const num = (v) => (typeof v === "number" ? v.toFixed(2) : "n/a");
|
|
581
|
+
out.log(`${b.grader_id} (${b.judge}): ${st.n} labels, agreement ${num(st.agreement)}, kappa ${num(st.cohen_kappa)}, pass recall ${num(st.tpr)}, fail recall ${num(st.tnr)}; cost ${usd(b.cost_usd)}`);
|
|
582
|
+
const cm = st.confusion;
|
|
583
|
+
out.log(`judge pass / human pass ${cm.tp}\tjudge pass / human fail ${cm.fp}\tjudge fail / human fail ${cm.tn}\tjudge fail / human pass ${cm.fn}`);
|
|
584
|
+
if (st.stability) {
|
|
585
|
+
out.log(`stability over ${st.stability.k} judge calls per output: ${st.stability.unstable} of ${st.stability.n} outputs split (flip rate ${num(st.stability.flip_rate)})`);
|
|
586
|
+
}
|
|
587
|
+
for (const w of st.warnings)
|
|
588
|
+
out.log(`warning: ${w}`);
|
|
589
|
+
for (const d of b.disagreements) {
|
|
590
|
+
out.log(`${d.case_id}#${d.repeat_index}\thuman ${d.label}\tjudge ${d.judge}\t${d.reason ?? ""}`);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
557
594
|
case "suggestions":
|
|
558
595
|
return show(await httpListEvalSuggestions(base, positionals[0], parsed.status), (b) => {
|
|
559
596
|
for (const s of b.items)
|
package/dist/cli/hostClient.d.ts
CHANGED
|
@@ -119,6 +119,12 @@ export declare function httpSortEvalFailures(base: string, evalRunId: string, in
|
|
|
119
119
|
only_unsorted?: boolean;
|
|
120
120
|
limit?: number;
|
|
121
121
|
}): Promise<HttpEvalResult<Record<string, any>>>;
|
|
122
|
+
export declare function httpCalibrateEvalGrader(base: string, evalId: string, graderId: string, input: {
|
|
123
|
+
project_root?: string;
|
|
124
|
+
split?: "all" | "dev" | "held_out";
|
|
125
|
+
max_cost_usd?: number;
|
|
126
|
+
stability?: boolean;
|
|
127
|
+
}): Promise<HttpEvalResult<Record<string, any>>>;
|
|
122
128
|
export declare function httpListEvalSuggestions(base: string, evalRunId: string, status?: string): Promise<HttpEvalResult<{
|
|
123
129
|
items: Array<Record<string, any>>;
|
|
124
130
|
summary: Record<string, any>;
|
package/dist/cli/hostClient.js
CHANGED
|
@@ -342,6 +342,10 @@ export async function httpSortEvalFailures(base, evalRunId, input) {
|
|
|
342
342
|
const { status, body } = await postJson(base, `/api/eval-runs/${enc(evalRunId)}/failures/sort`, input);
|
|
343
343
|
return status === 200 ? { ok: true, body: body } : evalFailure(status, body);
|
|
344
344
|
}
|
|
345
|
+
export async function httpCalibrateEvalGrader(base, evalId, graderId, input) {
|
|
346
|
+
const { status, body } = await postJson(base, `/api/evals/${enc(evalId)}/graders/${enc(graderId)}/calibrate`, input);
|
|
347
|
+
return status === 200 ? { ok: true, body: body } : evalFailure(status, body);
|
|
348
|
+
}
|
|
345
349
|
export function httpListEvalSuggestions(base, evalRunId, status) {
|
|
346
350
|
return evalGet(base, `/api/eval-runs/${enc(evalRunId)}/failures/suggestions${evalQuery({ status })}`);
|
|
347
351
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EvalConversation, EvalFile, ResolvedConversation, ResolvedGrader } from "../evals/types.js";
|
|
1
|
+
import type { EvalConversation, JudgeSettings, EvalFile, ResolvedConversation, ResolvedGrader } from "../evals/types.js";
|
|
2
2
|
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
3
|
type UnhashedGrader = ResolvedGrader extends infer G ? (G extends unknown ? Omit<G, "hash"> : never) : never;
|
|
4
4
|
export type LoadedEval = {
|
|
@@ -25,6 +25,8 @@ export type EvalPaths = {
|
|
|
25
25
|
};
|
|
26
26
|
/** Paths in an eval file resolve against the eval file's directory (P6). */
|
|
27
27
|
export declare function resolveEvalPaths(file: EvalFile, evalAbsPath: string): EvalPaths;
|
|
28
|
+
/** Grader settings over the eval defaults, key by key; undefined when neither sets anything. */
|
|
29
|
+
export declare function mergeJudgeSettings(base: JudgeSettings | undefined, over: JudgeSettings | undefined): JudgeSettings | undefined;
|
|
28
30
|
export declare function graderHash(grader: UnhashedGrader): string;
|
|
29
31
|
export declare function graderHashShort(grader: Pick<ResolvedGrader, "hash">): string;
|
|
30
32
|
export declare function parseEvalOutcome(yamlText: string, filePath: string, projectRoot: string): LoadOutcome<LoadedEval>;
|
package/dist/config/loadEval.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { AGENT_ID_PATTERN, isReservedAgentId } from "../agents/agentId.js";
|
|
5
5
|
import { canonicalJson, hash6, sha256Hex } from "../evals/canonical.js";
|
|
6
|
+
import { EVAL_LIMITS } from "../evals/constants.js";
|
|
6
7
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
7
8
|
import { yamlParsePosition } from "./yamlParsePosition.js";
|
|
8
9
|
export const EVAL_DEFAULT_REPEATS = 3;
|
|
@@ -89,6 +90,7 @@ const TASK_KEYS = ["goal", "context", "constraints"];
|
|
|
89
90
|
const DEFAULTS_KEYS = [
|
|
90
91
|
"repeats",
|
|
91
92
|
"judge",
|
|
93
|
+
"judge_settings",
|
|
92
94
|
"pass_threshold",
|
|
93
95
|
"max_cost_usd",
|
|
94
96
|
"concurrency",
|
|
@@ -101,11 +103,11 @@ const GRADER_TYPE_KEYS = {
|
|
|
101
103
|
schema: [],
|
|
102
104
|
field: ["checks"],
|
|
103
105
|
reference: ["mode", "path", "expected", "pass_at"],
|
|
104
|
-
rubric: ["rubric", "scale", "pass_at", "judge", "examples"],
|
|
106
|
+
rubric: ["rubric", "scale", "pass_at", "judge", "judge_settings", "examples"],
|
|
105
107
|
code: ["code"],
|
|
106
108
|
tool_calls: ["must_call", "must_not_call", "must_ask_first", "max_calls", "args"],
|
|
107
109
|
conversation_check: ["max_turns", "max_agent_questions_per_turn", "final_reply"],
|
|
108
|
-
goal_reached: ["judge", "guidance"],
|
|
110
|
+
goal_reached: ["judge", "judge_settings", "guidance"],
|
|
109
111
|
};
|
|
110
112
|
const TOOL_ARG_KEYS = ["tool", "path", "op", "value"];
|
|
111
113
|
const FINAL_REPLY_KEYS = ["op", "value"];
|
|
@@ -285,6 +287,51 @@ function parseTask(c, raw) {
|
|
|
285
287
|
}
|
|
286
288
|
return out;
|
|
287
289
|
}
|
|
290
|
+
const JUDGE_SETTINGS_KEYS = ["temperature", "reasoning", "votes"];
|
|
291
|
+
/** A mapping of judge call settings, or undefined when absent or invalid (findings added). */
|
|
292
|
+
function parseJudgeSettings(c, raw, label) {
|
|
293
|
+
if (raw === undefined)
|
|
294
|
+
return undefined;
|
|
295
|
+
if (!isPlainObject(raw)) {
|
|
296
|
+
c.add("eval.invalid_field", `${label} "judge_settings" must be a mapping`);
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
c.unknownKeys(raw, JUDGE_SETTINGS_KEYS, `${label} judge_settings`);
|
|
300
|
+
const out = {};
|
|
301
|
+
let ok = true;
|
|
302
|
+
if (raw.temperature !== undefined) {
|
|
303
|
+
if (typeof raw.temperature === "number" && raw.temperature >= 0 && raw.temperature <= 1) {
|
|
304
|
+
out.temperature = raw.temperature;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
c.add("eval.invalid_field", `${label} judge_settings "temperature" must be a number from 0 to 1`);
|
|
308
|
+
ok = false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (raw.reasoning !== undefined) {
|
|
312
|
+
if (typeof raw.reasoning === "boolean")
|
|
313
|
+
out.reasoning = raw.reasoning;
|
|
314
|
+
else {
|
|
315
|
+
c.add("eval.invalid_field", `${label} judge_settings "reasoning" must be true or false`);
|
|
316
|
+
ok = false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (raw.votes !== undefined) {
|
|
320
|
+
if (typeof raw.votes === "number" && Number.isInteger(raw.votes) && raw.votes >= 1 && raw.votes <= 5 && raw.votes % 2 === 1) {
|
|
321
|
+
out.votes = raw.votes;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
c.add("eval.invalid_field", `${label} judge_settings "votes" must be 1, 3 or 5`);
|
|
325
|
+
ok = false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return ok ? out : undefined;
|
|
329
|
+
}
|
|
330
|
+
/** Grader settings over the eval defaults, key by key; undefined when neither sets anything. */
|
|
331
|
+
export function mergeJudgeSettings(base, over) {
|
|
332
|
+
const merged = { ...(base ?? {}), ...(over ?? {}) };
|
|
333
|
+
return Object.keys(merged).length === 0 ? undefined : merged;
|
|
334
|
+
}
|
|
288
335
|
function parseDefaults(c, raw, kind) {
|
|
289
336
|
if (raw === undefined)
|
|
290
337
|
return undefined;
|
|
@@ -300,6 +347,9 @@ function parseDefaults(c, raw, kind) {
|
|
|
300
347
|
const judge = optionalString(c, raw, "judge", "defaults", { min: 1 });
|
|
301
348
|
if (judge !== undefined)
|
|
302
349
|
out.judge = judge;
|
|
350
|
+
const judgeSettings = parseJudgeSettings(c, raw.judge_settings, "defaults");
|
|
351
|
+
if (judgeSettings !== undefined)
|
|
352
|
+
out.judge_settings = judgeSettings;
|
|
303
353
|
const passThreshold = optionalNumber(c, raw, "pass_threshold", "defaults", "a number greater than 0 and at most 1", (n) => n > 0 && n <= 1);
|
|
304
354
|
if (passThreshold !== undefined)
|
|
305
355
|
out.pass_threshold = passThreshold;
|
|
@@ -483,8 +533,8 @@ function parseChecks(c, raw, where) {
|
|
|
483
533
|
return ok ? checks : undefined;
|
|
484
534
|
}
|
|
485
535
|
function parseExamples(c, raw, scale, where) {
|
|
486
|
-
if (!Array.isArray(raw) || raw.length >
|
|
487
|
-
c.add("eval.grader_invalid", `${where} "examples" must be an array of at most
|
|
536
|
+
if (!Array.isArray(raw) || raw.length > EVAL_LIMITS.rubricExamplesMax) {
|
|
537
|
+
c.add("eval.grader_invalid", `${where} "examples" must be an array of at most ${EVAL_LIMITS.rubricExamplesMax} items`);
|
|
488
538
|
return undefined;
|
|
489
539
|
}
|
|
490
540
|
const out = [];
|
|
@@ -610,7 +660,7 @@ function parseFinalReply(c, raw, where) {
|
|
|
610
660
|
});
|
|
611
661
|
return ok ? out : undefined;
|
|
612
662
|
}
|
|
613
|
-
function parseGrader(c, raw, index, defaultJudge, kind) {
|
|
663
|
+
function parseGrader(c, raw, index, defaultJudge, kind, defaultJudgeSettings) {
|
|
614
664
|
if (!isPlainObject(raw)) {
|
|
615
665
|
c.add("eval.grader_invalid", `graders[${index}] must be a mapping`);
|
|
616
666
|
return undefined;
|
|
@@ -769,6 +819,10 @@ function parseGrader(c, raw, index, defaultJudge, kind) {
|
|
|
769
819
|
if (examples === undefined)
|
|
770
820
|
ok = false;
|
|
771
821
|
}
|
|
822
|
+
const ownSettings = parseJudgeSettings(c, raw.judge_settings, label);
|
|
823
|
+
if (raw.judge_settings !== undefined && ownSettings === undefined)
|
|
824
|
+
ok = false;
|
|
825
|
+
const judgeSettings = mergeJudgeSettings(defaultJudgeSettings, ownSettings);
|
|
772
826
|
if (ok) {
|
|
773
827
|
result = {
|
|
774
828
|
config: {
|
|
@@ -778,6 +832,7 @@ function parseGrader(c, raw, index, defaultJudge, kind) {
|
|
|
778
832
|
...(raw.scale !== undefined ? { scale } : {}),
|
|
779
833
|
...(passAt !== undefined ? { pass_at: passAt } : {}),
|
|
780
834
|
...(judge !== undefined ? { judge } : {}),
|
|
835
|
+
...(ownSettings !== undefined ? { judge_settings: ownSettings } : {}),
|
|
781
836
|
...(examples !== undefined ? { examples } : {}),
|
|
782
837
|
},
|
|
783
838
|
resolved: {
|
|
@@ -787,6 +842,7 @@ function parseGrader(c, raw, index, defaultJudge, kind) {
|
|
|
787
842
|
scale,
|
|
788
843
|
passAt: scale === "1_to_5" ? (passAt ?? 4) : 1,
|
|
789
844
|
judge: judge ?? defaultJudge ?? "",
|
|
845
|
+
...(judgeSettings !== undefined ? { judgeSettings } : {}),
|
|
790
846
|
examples: examples ?? [],
|
|
791
847
|
},
|
|
792
848
|
};
|
|
@@ -882,18 +938,24 @@ function parseGrader(c, raw, index, defaultJudge, kind) {
|
|
|
882
938
|
ok = false;
|
|
883
939
|
}
|
|
884
940
|
}
|
|
941
|
+
const goalSettings = parseJudgeSettings(c, raw.judge_settings, label);
|
|
942
|
+
if (raw.judge_settings !== undefined && goalSettings === undefined)
|
|
943
|
+
ok = false;
|
|
944
|
+
const goalMerged = mergeJudgeSettings(defaultJudgeSettings, goalSettings);
|
|
885
945
|
if (ok) {
|
|
886
946
|
result = {
|
|
887
947
|
config: {
|
|
888
948
|
...common,
|
|
889
949
|
type: "goal_reached",
|
|
890
950
|
...(judge !== undefined ? { judge } : {}),
|
|
951
|
+
...(goalSettings !== undefined ? { judge_settings: goalSettings } : {}),
|
|
891
952
|
...(guidance !== undefined ? { guidance } : {}),
|
|
892
953
|
},
|
|
893
954
|
resolved: {
|
|
894
955
|
...resolvedCommon,
|
|
895
956
|
type: "goal_reached",
|
|
896
957
|
judge: judge ?? defaultJudge ?? "",
|
|
958
|
+
...(goalMerged !== undefined ? { judgeSettings: goalMerged } : {}),
|
|
897
959
|
...(guidance !== undefined ? { guidance } : {}),
|
|
898
960
|
},
|
|
899
961
|
};
|
|
@@ -932,6 +994,7 @@ function graderHashObject(grader) {
|
|
|
932
994
|
scale: grader.scale,
|
|
933
995
|
...(grader.scale === "1_to_5" ? { pass_at: grader.passAt } : {}),
|
|
934
996
|
judge: grader.judge,
|
|
997
|
+
...(grader.judgeSettings !== undefined ? { judge_settings: grader.judgeSettings } : {}),
|
|
935
998
|
examples: grader.examples,
|
|
936
999
|
};
|
|
937
1000
|
case "code":
|
|
@@ -953,7 +1016,12 @@ function graderHashObject(grader) {
|
|
|
953
1016
|
final_reply: grader.finalReply,
|
|
954
1017
|
};
|
|
955
1018
|
case "goal_reached":
|
|
956
|
-
return {
|
|
1019
|
+
return {
|
|
1020
|
+
...base,
|
|
1021
|
+
judge: grader.judge,
|
|
1022
|
+
...(grader.judgeSettings !== undefined ? { judge_settings: grader.judgeSettings } : {}),
|
|
1023
|
+
guidance: grader.guidance,
|
|
1024
|
+
};
|
|
957
1025
|
}
|
|
958
1026
|
}
|
|
959
1027
|
export function graderHash(grader) {
|
|
@@ -1047,7 +1115,7 @@ export function parseEvalOutcome(yamlText, filePath, projectRoot) {
|
|
|
1047
1115
|
else {
|
|
1048
1116
|
const seen = new Set();
|
|
1049
1117
|
raw.graders.forEach((rawGrader, index) => {
|
|
1050
|
-
const parsed = parseGrader(c, rawGrader, index, defaults?.judge, target?.kind);
|
|
1118
|
+
const parsed = parseGrader(c, rawGrader, index, defaults?.judge, target?.kind, defaults?.judge_settings);
|
|
1051
1119
|
if (isPlainObject(rawGrader) && typeof rawGrader.id === "string") {
|
|
1052
1120
|
if (seen.has(rawGrader.id)) {
|
|
1053
1121
|
c.add("eval.grader_duplicate_id", `Duplicate grader id "${rawGrader.id}"`);
|
|
@@ -2,7 +2,7 @@ import { internalAgentsVisible } from "../operatorAgent/internalAgents.js";
|
|
|
2
2
|
import { PROFILE_GRANTS } from "../toolkit/profiles.js";
|
|
3
3
|
import { realpath, stat } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { mapJudge } from "../evals/judgeProviders.js";
|
|
5
|
+
import { isFloatingModelId, mapJudge } from "../evals/judgeProviders.js";
|
|
6
6
|
import { classifyTool } from "../agents/toolRisk.js";
|
|
7
7
|
import { checkDatasetAgainstAgent } from "./datasetAgentCheck.js";
|
|
8
8
|
import { checkDatasetAgainstStage } from "./datasetStageCheck.js";
|
|
@@ -106,6 +106,7 @@ export async function validateEval(loaded, ctx) {
|
|
|
106
106
|
}
|
|
107
107
|
const stageModel = file.defaults?.model ?? resolved?.modelResolved;
|
|
108
108
|
const unknownChecked = new Set();
|
|
109
|
+
const unpinnedWarned = new Set();
|
|
109
110
|
for (const grader of loaded.graders) {
|
|
110
111
|
if (grader.type !== "rubric")
|
|
111
112
|
continue;
|
|
@@ -122,6 +123,13 @@ export async function validateEval(loaded, ctx) {
|
|
|
122
123
|
if (stageModel !== undefined && grader.judge === stageModel) {
|
|
123
124
|
add("warning", "eval.judge_same_as_stage", `Rubric grader "${grader.id}" uses judge "${grader.judge}", the same model as the stage (self-preference bias)`);
|
|
124
125
|
}
|
|
126
|
+
if (isFloatingModelId(grader.judge) && !unpinnedWarned.has(grader.judge)) {
|
|
127
|
+
unpinnedWarned.add(grader.judge);
|
|
128
|
+
add("warning", "eval.judge_unpinned", `Judge model "${grader.judge}" is a floating id; its behavior can change under stored runs. Pin a dated version`);
|
|
129
|
+
}
|
|
130
|
+
if ((grader.judgeSettings?.votes ?? 1) > 1 && grader.judgeSettings?.temperature === 0) {
|
|
131
|
+
add("warning", "eval.judge_votes_no_variation", `Grader "${grader.id}" asks for ${grader.judgeSettings?.votes} votes at temperature 0, so every vote is the same call and the cost is multiplied for nothing`);
|
|
132
|
+
}
|
|
125
133
|
}
|
|
126
134
|
const paths = resolveEvalPaths(file, loaded.path);
|
|
127
135
|
const datasetProblem = ctx.datasetInline
|
|
@@ -267,6 +275,7 @@ async function validateAgentEval(loaded, ctx, findings) {
|
|
|
267
275
|
}
|
|
268
276
|
const agentModel = file.defaults?.model ?? resolved?.modelResolved;
|
|
269
277
|
const unknownChecked = new Set();
|
|
278
|
+
const unpinnedWarned = new Set();
|
|
270
279
|
for (const grader of loaded.graders) {
|
|
271
280
|
if (grader.type !== "rubric" && grader.type !== "goal_reached")
|
|
272
281
|
continue;
|
|
@@ -283,6 +292,13 @@ async function validateAgentEval(loaded, ctx, findings) {
|
|
|
283
292
|
if (agentModel !== undefined && grader.judge === agentModel) {
|
|
284
293
|
add("warning", "eval.judge_same_as_stage", `Grader "${grader.id}" uses judge "${grader.judge}", the same model as the agent (self-preference bias)`);
|
|
285
294
|
}
|
|
295
|
+
if (isFloatingModelId(grader.judge) && !unpinnedWarned.has(grader.judge)) {
|
|
296
|
+
unpinnedWarned.add(grader.judge);
|
|
297
|
+
add("warning", "eval.judge_unpinned", `Judge model "${grader.judge}" is a floating id; its behavior can change under stored runs. Pin a dated version`);
|
|
298
|
+
}
|
|
299
|
+
if ((grader.judgeSettings?.votes ?? 1) > 1 && grader.judgeSettings?.temperature === 0) {
|
|
300
|
+
add("warning", "eval.judge_votes_no_variation", `Grader "${grader.id}" asks for ${grader.judgeSettings?.votes} votes at temperature 0, so every vote is the same call and the cost is multiplied for nothing`);
|
|
301
|
+
}
|
|
286
302
|
}
|
|
287
303
|
if (resolved) {
|
|
288
304
|
const grantWarn = (names, where) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { EvalCalibrationSplit, EvalGraderLabelRow } from "./storeTypes.js";
|
|
3
|
+
import type { CaseOutput } from "./types.js";
|
|
4
|
+
export type CalibrationSampleSource = {
|
|
5
|
+
caseId: string;
|
|
6
|
+
/** Position within the case's samples; the replay repeat index. */
|
|
7
|
+
repeatIndex: number;
|
|
8
|
+
label: "pass" | "fail";
|
|
9
|
+
output: CaseOutput;
|
|
10
|
+
originalRunId: string;
|
|
11
|
+
originalRepeatIndex: number;
|
|
12
|
+
};
|
|
13
|
+
export type CalibrationStore = Pick<RunStore, "listEvalGraderLabelsForEval" | "listEvalResults">;
|
|
14
|
+
/** Rebuilds the in-memory case output from the stored result JSON. */
|
|
15
|
+
export declare function replayOutput(stored: unknown): CaseOutput | null;
|
|
16
|
+
/**
|
|
17
|
+
* The labeled samples for one grader across every run of the eval: the newest label per identical
|
|
18
|
+
* (case, output), grouped per case with a fresh repeat index so the engine can replay them.
|
|
19
|
+
*/
|
|
20
|
+
export declare function collectCalibrationSamples(store: CalibrationStore, projectRoot: string, evalId: string, graderId: string, split: EvalCalibrationSplit): Promise<CalibrationSampleSource[]>;
|
|
21
|
+
export type { EvalGraderLabelRow };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { calibrationSplitOf } from "./stats.js";
|
|
3
|
+
import { renderTranscript } from "./graders/transcript.js";
|
|
4
|
+
function outputKey(caseId, output) {
|
|
5
|
+
return createHash("sha256")
|
|
6
|
+
.update(caseId)
|
|
7
|
+
.update("\u0000")
|
|
8
|
+
.update(JSON.stringify(output ?? null))
|
|
9
|
+
.digest("hex");
|
|
10
|
+
}
|
|
11
|
+
/** Rebuilds the in-memory case output from the stored result JSON. */
|
|
12
|
+
export function replayOutput(stored) {
|
|
13
|
+
if (stored === null || typeof stored !== "object")
|
|
14
|
+
return null;
|
|
15
|
+
const o = stored;
|
|
16
|
+
if (o.kind === "conversation") {
|
|
17
|
+
const out = { ...o };
|
|
18
|
+
out.transcript = renderTranscript(out);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
if (typeof o.status !== "string" || typeof o.summary !== "string")
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
kind: "envelope",
|
|
25
|
+
status: o.status === "failure" ? "failure" : "success",
|
|
26
|
+
summary: o.summary,
|
|
27
|
+
...(o.payload !== undefined && typeof o.payload === "object" ? { payload: o.payload } : {}),
|
|
28
|
+
artifacts: Array.isArray(o.artifacts) ? o.artifacts : [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The labeled samples for one grader across every run of the eval: the newest label per identical
|
|
33
|
+
* (case, output), grouped per case with a fresh repeat index so the engine can replay them.
|
|
34
|
+
*/
|
|
35
|
+
export async function collectCalibrationSamples(store, projectRoot, evalId, graderId, split) {
|
|
36
|
+
const labels = await store.listEvalGraderLabelsForEval(projectRoot, evalId, graderId);
|
|
37
|
+
const resultsByRun = new Map();
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const perCase = new Map();
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const label of labels) {
|
|
42
|
+
if (split !== "all" && calibrationSplitOf(label.caseId) !== split)
|
|
43
|
+
continue;
|
|
44
|
+
let results = resultsByRun.get(label.evalRunId);
|
|
45
|
+
if (!results) {
|
|
46
|
+
results = new Map((await store.listEvalResults(label.evalRunId)).map((r) => [`${r.caseId}#${r.repeatIndex}`, r]));
|
|
47
|
+
resultsByRun.set(label.evalRunId, results);
|
|
48
|
+
}
|
|
49
|
+
const result = results.get(`${label.caseId}#${label.repeatIndex}`);
|
|
50
|
+
const output = result ? replayOutput(result.output) : null;
|
|
51
|
+
if (!output)
|
|
52
|
+
continue;
|
|
53
|
+
const key = outputKey(label.caseId, result?.output);
|
|
54
|
+
if (seen.has(key))
|
|
55
|
+
continue;
|
|
56
|
+
seen.add(key);
|
|
57
|
+
const repeatIndex = perCase.get(label.caseId) ?? 0;
|
|
58
|
+
perCase.set(label.caseId, repeatIndex + 1);
|
|
59
|
+
out.push({
|
|
60
|
+
caseId: label.caseId,
|
|
61
|
+
repeatIndex,
|
|
62
|
+
label: label.label,
|
|
63
|
+
output,
|
|
64
|
+
originalRunId: label.evalRunId,
|
|
65
|
+
originalRepeatIndex: label.repeatIndex,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { EvalCalibrationRow } from "./storeTypes.js";
|
|
3
|
+
import type { CaseOutput, RubricExample } from "./types.js";
|
|
4
|
+
export declare const EXAMPLE_SUGGESTION_LIMIT = 3;
|
|
5
|
+
export type SuggestedExample = {
|
|
6
|
+
eval_run_id: string;
|
|
7
|
+
case_id: string;
|
|
8
|
+
repeat_index: number;
|
|
9
|
+
/** What the judge said, so the author can see why this example would help. */
|
|
10
|
+
judge: "pass" | "fail";
|
|
11
|
+
example: RubricExample;
|
|
12
|
+
};
|
|
13
|
+
export type SuggestStore = Pick<RunStore, "listEvalResults" | "listEvalGraderLabelsForEval">;
|
|
14
|
+
/** The text a judge example shows for one stored output. */
|
|
15
|
+
export declare function exampleOutputText(output: CaseOutput): string;
|
|
16
|
+
/**
|
|
17
|
+
* Up to three few-shot examples built from where the judge disagreed with the author on the dev split (a held-out row
|
|
18
|
+
* is never offered, so the held-out check stays a fair test): one of each kind of
|
|
19
|
+
* disagreement first (judge too lenient, judge too strict), distinct outputs only. The author's label note,
|
|
20
|
+
* when there is one, becomes the example's reason. Nothing is written.
|
|
21
|
+
*/
|
|
22
|
+
export declare function suggestExamples(store: SuggestStore, input: {
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
evalId: string;
|
|
25
|
+
graderId: string;
|
|
26
|
+
scale: "pass_fail" | "1_to_5";
|
|
27
|
+
calibration: Pick<EvalCalibrationRow, "disagreements">;
|
|
28
|
+
limit?: number;
|
|
29
|
+
}): Promise<SuggestedExample[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { replayOutput } from "./calibration.js";
|
|
2
|
+
import { calibrationSplitOf } from "./stats.js";
|
|
3
|
+
export const EXAMPLE_SUGGESTION_LIMIT = 3;
|
|
4
|
+
const EXAMPLE_OUTPUT_MAX_CHARS = 1500;
|
|
5
|
+
function cut(text) {
|
|
6
|
+
return text.length <= EXAMPLE_OUTPUT_MAX_CHARS ? text : `${text.slice(0, EXAMPLE_OUTPUT_MAX_CHARS)}…`;
|
|
7
|
+
}
|
|
8
|
+
/** The text a judge example shows for one stored output. */
|
|
9
|
+
export function exampleOutputText(output) {
|
|
10
|
+
if (output.kind === "conversation")
|
|
11
|
+
return cut(output.transcript ?? output.final_reply);
|
|
12
|
+
const payload = output.payload === undefined ? "" : `\n${JSON.stringify(output.payload)}`;
|
|
13
|
+
return cut(`${output.summary}${payload}`);
|
|
14
|
+
}
|
|
15
|
+
function exampleFor(scale, label, output, reason) {
|
|
16
|
+
const base = { output, ...(reason !== undefined && reason !== "" ? { reason } : {}) };
|
|
17
|
+
if (scale === "pass_fail")
|
|
18
|
+
return { verdict: label, ...base };
|
|
19
|
+
return { score: label === "pass" ? 5 : 1, ...base };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Up to three few-shot examples built from where the judge disagreed with the author on the dev split (a held-out row
|
|
23
|
+
* is never offered, so the held-out check stays a fair test): one of each kind of
|
|
24
|
+
* disagreement first (judge too lenient, judge too strict), distinct outputs only. The author's label note,
|
|
25
|
+
* when there is one, becomes the example's reason. Nothing is written.
|
|
26
|
+
*/
|
|
27
|
+
export async function suggestExamples(store, input) {
|
|
28
|
+
const limit = input.limit ?? EXAMPLE_SUGGESTION_LIMIT;
|
|
29
|
+
const labels = await store.listEvalGraderLabelsForEval(input.projectRoot, input.evalId, input.graderId);
|
|
30
|
+
const notes = new Map(labels.map((l) => [`${l.evalRunId}#${l.caseId}#${l.repeatIndex}`, l.note]));
|
|
31
|
+
const dev = input.calibration.disagreements.filter((d) => calibrationSplitOf(d.caseId) === "dev");
|
|
32
|
+
const lenient = dev.filter((d) => d.judge === "pass");
|
|
33
|
+
const strict = dev.filter((d) => d.judge === "fail");
|
|
34
|
+
const ordered = [];
|
|
35
|
+
for (let i = 0; ordered.length < lenient.length + strict.length; i++) {
|
|
36
|
+
if (lenient[i])
|
|
37
|
+
ordered.push(lenient[i]);
|
|
38
|
+
if (strict[i])
|
|
39
|
+
ordered.push(strict[i]);
|
|
40
|
+
}
|
|
41
|
+
const runs = new Map();
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const d of ordered) {
|
|
45
|
+
if (out.length >= limit)
|
|
46
|
+
break;
|
|
47
|
+
if (d.evalRunId === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
let results = runs.get(d.evalRunId);
|
|
50
|
+
if (!results) {
|
|
51
|
+
results = await store.listEvalResults(d.evalRunId);
|
|
52
|
+
runs.set(d.evalRunId, results);
|
|
53
|
+
}
|
|
54
|
+
const result = results.find((r) => r.caseId === d.caseId && r.repeatIndex === d.repeatIndex);
|
|
55
|
+
const output = result ? replayOutput(result.output) : null;
|
|
56
|
+
if (!output)
|
|
57
|
+
continue;
|
|
58
|
+
const text = exampleOutputText(output);
|
|
59
|
+
if (seen.has(text))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(text);
|
|
62
|
+
const note = notes.get(`${d.evalRunId}#${d.caseId}#${d.repeatIndex}`) ?? undefined;
|
|
63
|
+
out.push({
|
|
64
|
+
eval_run_id: d.evalRunId,
|
|
65
|
+
case_id: d.caseId,
|
|
66
|
+
repeat_index: d.repeatIndex,
|
|
67
|
+
judge: d.judge,
|
|
68
|
+
example: exampleFor(input.scale, d.label, text, note),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CalibrationStore } from "./calibration.js";
|
|
2
|
+
import type { JudgedGrader } from "./judged.js";
|
|
3
|
+
import { type CalibrationStats, type StabilityStats } from "./stats.js";
|
|
4
|
+
import type { EvalCalibrationDisagreement, EvalCalibrationRow, EvalCalibrationSplit } from "./storeTypes.js";
|
|
5
|
+
import type { EvalEngine, EvalPlan, JudgeSettings, RubricExample } from "./types.js";
|
|
6
|
+
import type { RunStore } from "../runstore/port.js";
|
|
7
|
+
export type CalibrateInput = {
|
|
8
|
+
projectRoot: string;
|
|
9
|
+
evalPath: string;
|
|
10
|
+
graderId: string;
|
|
11
|
+
split?: EvalCalibrationSplit;
|
|
12
|
+
/** A rubric to measure instead of the saved one; the result is stored as a candidate. */
|
|
13
|
+
candidate?: {
|
|
14
|
+
rubric?: string;
|
|
15
|
+
examples?: RubricExample[];
|
|
16
|
+
judge?: string;
|
|
17
|
+
judgeSettings?: JudgeSettings;
|
|
18
|
+
};
|
|
19
|
+
maxCostUsd?: number;
|
|
20
|
+
/** Judge each output 3 times (or the grader's own larger vote count) and report how often the verdict splits. */
|
|
21
|
+
stability?: boolean;
|
|
22
|
+
/** Cancels the check (for example when the request closes); nothing is stored when it fires. */
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
};
|
|
25
|
+
export type CalibrateResult = {
|
|
26
|
+
calibration: EvalCalibrationRow;
|
|
27
|
+
stats: CalibrationStats;
|
|
28
|
+
disagreements: EvalCalibrationDisagreement[];
|
|
29
|
+
errored: number;
|
|
30
|
+
stability?: StabilityStats;
|
|
31
|
+
costUsd: number;
|
|
32
|
+
};
|
|
33
|
+
export type CalibrationDeps = {
|
|
34
|
+
store: CalibrationStore & Pick<RunStore, "insertEvalCalibration">;
|
|
35
|
+
engine: EvalEngine;
|
|
36
|
+
log: (level: "info" | "warn" | "error", message: string) => void;
|
|
37
|
+
};
|
|
38
|
+
export declare const CALIBRATION_DEFAULT_MAX_COST_USD = 1;
|
|
39
|
+
/**
|
|
40
|
+
* Re-judges the stored outputs of results the author labeled for one judged grader and compares the verdicts to the
|
|
41
|
+
* labels. No stage or agent runs: the engine's case callback replays the stored output. `base` is the eval's built
|
|
42
|
+
* plan and `found` is the saved grader; the caller owns the one-at-a-time claim and the work directory.
|
|
43
|
+
*/
|
|
44
|
+
export declare function measureCalibration({ store, engine, log }: CalibrationDeps, input: CalibrateInput, base: EvalPlan, found: JudgedGrader, abort: AbortController,
|
|
45
|
+
/** Fires when the caller cancels; the budget limit uses `abort` instead and still stores the partial reading. */
|
|
46
|
+
cancel: AbortSignal): Promise<CalibrateResult>;
|