@gaunt-sloth/batch 2.0.0-alpha.19
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/LICENSE +7 -0
- package/dist/BatchRunner.d.ts +18 -0
- package/dist/BatchRunner.js +100 -0
- package/dist/BatchRunner.js.map +1 -0
- package/dist/deterministicChecks.d.ts +16 -0
- package/dist/deterministicChecks.js +34 -0
- package/dist/deterministicChecks.js.map +1 -0
- package/dist/evalOutput.d.ts +11 -0
- package/dist/evalOutput.js +19 -0
- package/dist/evalOutput.js.map +1 -0
- package/dist/evalRunner.d.ts +25 -0
- package/dist/evalRunner.js +100 -0
- package/dist/evalRunner.js.map +1 -0
- package/dist/evalSuite.d.ts +24 -0
- package/dist/evalSuite.js +129 -0
- package/dist/evalSuite.js.map +1 -0
- package/dist/evalTypes.d.ts +98 -0
- package/dist/evalTypes.js +14 -0
- package/dist/evalTypes.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +16 -0
- package/dist/interpolate.js +48 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/judge.d.ts +62 -0
- package/dist/judge.js +119 -0
- package/dist/judge.js.map +1 -0
- package/dist/matrix.d.ts +11 -0
- package/dist/matrix.js +31 -0
- package/dist/matrix.js.map +1 -0
- package/dist/output.d.ts +13 -0
- package/dist/output.js +24 -0
- package/dist/output.js.map +1 -0
- package/dist/parseOver.d.ts +14 -0
- package/dist/parseOver.js +116 -0
- package/dist/parseOver.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow/runWorkflow.d.ts +79 -0
- package/dist/workflow/runWorkflow.js +142 -0
- package/dist/workflow/runWorkflow.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evalTypes.js","sourceRoot":"","sources":["../src/evalTypes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;mGAImG;AACnG,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { buildMatrix } from '#src/matrix.js';
|
|
2
|
+
export { bindCellContent } from '#src/interpolate.js';
|
|
3
|
+
export { parseOverFile } from '#src/parseOver.js';
|
|
4
|
+
export { runBatchMatrix, buildBatchSummary } from '#src/BatchRunner.js';
|
|
5
|
+
export { writeBatchOutput } from '#src/output.js';
|
|
6
|
+
export type { BatchRunnerOptions, BatchSummary, CellResult, CellRunOutcome, MatrixCell, MatrixRow, RunCellFn, } from '#src/types.js';
|
|
7
|
+
export { DEFAULT_CONCURRENCY } from '#src/types.js';
|
|
8
|
+
export { parseEvalSuite } from '#src/evalSuite.js';
|
|
9
|
+
export { runDeterministicChecks } from '#src/deterministicChecks.js';
|
|
10
|
+
export { judgeEvalCase, buildJudgeMessages, EvalVerdictSchema, EVAL_JUDGE_DEFAULT_TIMEOUT_MS, } from '#src/judge.js';
|
|
11
|
+
export type { EvalJudgeVerdict } from '#src/judge.js';
|
|
12
|
+
export { runEvalSuite } from '#src/evalRunner.js';
|
|
13
|
+
export { writeEvalOutput } from '#src/evalOutput.js';
|
|
14
|
+
export type { DeterministicCheckResult, EvalCase, EvalCaseResult, EvalSuite, EvalSuiteSummary, EvalTarget, JudgeFn, JudgeOutcome, JudgeVerdict, } from '#src/evalTypes.js';
|
|
15
|
+
export type { RunEvalSuiteOptions } from '#src/evalRunner.js';
|
|
16
|
+
export { DEFAULT_EVAL_PASS_THRESHOLD } from '#src/evalTypes.js';
|
|
17
|
+
export { runWorkflow } from '#src/workflow/runWorkflow.js';
|
|
18
|
+
export type { WorkflowAgentOptions, WorkflowContext, RunWorkflowOptions, } from '#src/workflow/runWorkflow.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { buildMatrix } from '#src/matrix.js';
|
|
2
|
+
export { bindCellContent } from '#src/interpolate.js';
|
|
3
|
+
export { parseOverFile } from '#src/parseOver.js';
|
|
4
|
+
export { runBatchMatrix, buildBatchSummary } from '#src/BatchRunner.js';
|
|
5
|
+
export { writeBatchOutput } from '#src/output.js';
|
|
6
|
+
export { DEFAULT_CONCURRENCY } from '#src/types.js';
|
|
7
|
+
// BATCH-2 — `gth eval`'s suite parsing, deterministic checks, judge, runner, and output writer.
|
|
8
|
+
export { parseEvalSuite } from '#src/evalSuite.js';
|
|
9
|
+
export { runDeterministicChecks } from '#src/deterministicChecks.js';
|
|
10
|
+
export { judgeEvalCase, buildJudgeMessages, EvalVerdictSchema, EVAL_JUDGE_DEFAULT_TIMEOUT_MS, } from '#src/judge.js';
|
|
11
|
+
export { runEvalSuite } from '#src/evalRunner.js';
|
|
12
|
+
export { writeEvalOutput } from '#src/evalOutput.js';
|
|
13
|
+
export { DEFAULT_EVAL_PASS_THRESHOLD } from '#src/evalTypes.js';
|
|
14
|
+
// BATCH-3 — the `gth workflow` host: runs a local JS orchestration script that drives one or more
|
|
15
|
+
// LLM calls through a small WorkflowContext.
|
|
16
|
+
export { runWorkflow } from '#src/workflow/runWorkflow.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAUlD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEpD,gGAAgG;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAarD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAEhE,kGAAkG;AAClG,6CAA6C;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MatrixRow } from '#src/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bind one input row into a script's content.
|
|
4
|
+
*
|
|
5
|
+
* - Every `{{field}}` placeholder whose `field` is a key of `row` is replaced with that value.
|
|
6
|
+
* - A placeholder naming a field the row doesn't have is left untouched (fail-soft: a typo in the
|
|
7
|
+
* script doesn't blow up the run, it just doesn't get substituted — visible in the recorded
|
|
8
|
+
* per-cell content if you go looking).
|
|
9
|
+
* - When the script contains **no** placeholder that matched any row field at all, the row is
|
|
10
|
+
* appended as a fenced context block instead of silently dropped. This covers the "script has no
|
|
11
|
+
* placeholders" case BATCH-1's own scope note calls out explicitly: a script that never mentions
|
|
12
|
+
* `{{...}}` still needs the row's data available to the model, just not spliced into prose.
|
|
13
|
+
*
|
|
14
|
+
* `row` undefined (no `--over`) is a no-op passthrough.
|
|
15
|
+
*/
|
|
16
|
+
export declare function bindCellContent(baseContent: string, row: MatrixRow | undefined): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BATCH-1 cell-binding syntax: `{{field}}` placeholders.
|
|
3
|
+
*
|
|
4
|
+
* Matches a bare field name (letters/digits/`_`/`.`/`-`), optionally surrounded by whitespace
|
|
5
|
+
* inside the braces (`{{ field }}` also works). Chosen over a heavier templating engine (mustache,
|
|
6
|
+
* handlebars-with-helpers, …) because BATCH-1's job is a literal find/replace of csv/jsonl column
|
|
7
|
+
* values into prose — no conditionals or loops belong in a prompt-executable script, and `{{x}}` is
|
|
8
|
+
* the syntax #405's own case format already uses for template-ish values, so it needs no new
|
|
9
|
+
* mental model for that user.
|
|
10
|
+
*/
|
|
11
|
+
const PLACEHOLDER_RE = /\{\{\s*([\w.-]+)\s*\}\}/g;
|
|
12
|
+
/**
|
|
13
|
+
* Bind one input row into a script's content.
|
|
14
|
+
*
|
|
15
|
+
* - Every `{{field}}` placeholder whose `field` is a key of `row` is replaced with that value.
|
|
16
|
+
* - A placeholder naming a field the row doesn't have is left untouched (fail-soft: a typo in the
|
|
17
|
+
* script doesn't blow up the run, it just doesn't get substituted — visible in the recorded
|
|
18
|
+
* per-cell content if you go looking).
|
|
19
|
+
* - When the script contains **no** placeholder that matched any row field at all, the row is
|
|
20
|
+
* appended as a fenced context block instead of silently dropped. This covers the "script has no
|
|
21
|
+
* placeholders" case BATCH-1's own scope note calls out explicitly: a script that never mentions
|
|
22
|
+
* `{{...}}` still needs the row's data available to the model, just not spliced into prose.
|
|
23
|
+
*
|
|
24
|
+
* `row` undefined (no `--over`) is a no-op passthrough.
|
|
25
|
+
*/
|
|
26
|
+
export function bindCellContent(baseContent, row) {
|
|
27
|
+
if (!row) {
|
|
28
|
+
return baseContent;
|
|
29
|
+
}
|
|
30
|
+
let matchedAny = false;
|
|
31
|
+
const bound = baseContent.replace(PLACEHOLDER_RE, (full, field) => {
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(row, field)) {
|
|
33
|
+
matchedAny = true;
|
|
34
|
+
return row[field];
|
|
35
|
+
}
|
|
36
|
+
return full;
|
|
37
|
+
});
|
|
38
|
+
if (matchedAny) {
|
|
39
|
+
return bound;
|
|
40
|
+
}
|
|
41
|
+
return `${bound}\n\n${formatRowAsContextBlock(row)}`;
|
|
42
|
+
}
|
|
43
|
+
/** Render a row as a small fenced block of `key: value` lines, for the no-placeholder fallback. */
|
|
44
|
+
function formatRowAsContextBlock(row) {
|
|
45
|
+
const lines = Object.entries(row).map(([key, value]) => `${key}: ${value}`);
|
|
46
|
+
return ['Batch input row for this cell:', '<batch-row>', ...lines, '</batch-row>'].join('\n');
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=interpolate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interpolate.js","sourceRoot":"","sources":["../src/interpolate.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,GAA0B;IAC7E,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,KAAa,EAAE,EAAE;QACxE,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;YACrD,UAAU,GAAG,IAAI,CAAC;YAClB,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,GAAG,KAAK,OAAO,uBAAuB,CAAC,GAAG,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,mGAAmG;AACnG,SAAS,uBAAuB,CAAC,GAAc;IAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC;IAC5E,OAAO,CAAC,gCAAgC,EAAE,aAAa,EAAE,GAAG,KAAK,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChG,CAAC"}
|
package/dist/judge.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module judge
|
|
3
|
+
*
|
|
4
|
+
* BATCH-2 — LLM-as-judge grading for `gth eval`. Adapts the *mechanism* of EXT-10's shell-safety
|
|
5
|
+
* judge (`packages/core/src/core/shell/judge.ts`): `model.withStructuredOutput(zodSchema)` for a
|
|
6
|
+
* single non-agentic structured call, raced against a timeout. The failure policy differs on
|
|
7
|
+
* purpose — EXT-10 fails CLOSED (escalates to a human approval prompt); eval has no human in the
|
|
8
|
+
* loop, so a judge that can't produce a verdict simply FAILS the case ({@link JudgeOutcome.ok}
|
|
9
|
+
* `false`), it does not "escalate" anywhere.
|
|
10
|
+
*
|
|
11
|
+
* Verdict scale matches `review`'s existing convention (`packages/review/src/middleware/
|
|
12
|
+
* reviewRateMiddleware.ts`'s `RateSchema`: `rate` 0-10 + a reason/comment string) for UX
|
|
13
|
+
* consistency — a user who knows `review`'s threshold semantics already knows eval's. `RateSchema`
|
|
14
|
+
* itself is not imported/reused: it is coupled to review's middleware/tool-call/artifact-store
|
|
15
|
+
* plumbing, which doesn't fit a plain structured-output call here.
|
|
16
|
+
*
|
|
17
|
+
* Model source: `config.llm` by default, i.e. the SAME model config as the SUT — matching
|
|
18
|
+
* `judgeShellCommand`'s own default. A separate `--judge <profile>` model is BATCH-2's own
|
|
19
|
+
* "Not in scope" list (identity-matrix/pluggable-target work); grading with the SUT's own model
|
|
20
|
+
* config is a known, real simplification for this first slice.
|
|
21
|
+
*/
|
|
22
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
23
|
+
import * as z from 'zod';
|
|
24
|
+
import type { JudgeOutcome } from '#src/evalTypes.js';
|
|
25
|
+
/** Structured verdict the judge model must return — 0-10 `rate` + a `reason`, matching `review`'s
|
|
26
|
+
* `RateSchema` shape (`rate`/`comment`) closely enough that the scale/semantics are the same;
|
|
27
|
+
* named `reason` here to read naturally as "why this rate" rather than a generic review comment. */
|
|
28
|
+
export declare const EvalVerdictSchema: z.ZodObject<{
|
|
29
|
+
rate: z.ZodNumber;
|
|
30
|
+
reason: z.ZodString;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type EvalJudgeVerdict = z.infer<typeof EvalVerdictSchema>;
|
|
33
|
+
/** Default wall-clock budget (ms) for the judge LLM call — same default as `judgeShellCommand`'s
|
|
34
|
+
* `JUDGE_DEFAULT_TIMEOUT_MS`, kept as eval's own constant since the two judges live in different
|
|
35
|
+
* packages (`@gaunt-sloth/core` vs `@gaunt-sloth/batch`) and are conceptually independent gates. */
|
|
36
|
+
export declare const EVAL_JUDGE_DEFAULT_TIMEOUT_MS = 30000;
|
|
37
|
+
/**
|
|
38
|
+
* Build the judge's messages: a fixed system preamble and a user message embedding the rubric and
|
|
39
|
+
* the SUT's answer. Exposed (and returning plain strings) so tests can assert structure without a
|
|
40
|
+
* live model.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildJudgeMessages(answer: string, rubric: string): {
|
|
43
|
+
system: string;
|
|
44
|
+
user: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Grade one case's SUT answer against its rubric via LLM-as-judge.
|
|
48
|
+
*
|
|
49
|
+
* - Builds the judge prompt ({@link buildJudgeMessages}).
|
|
50
|
+
* - Calls `model.withStructuredOutput(EvalVerdictSchema)`.
|
|
51
|
+
* - Races the call against `timeoutMs` (default {@link EVAL_JUDGE_DEFAULT_TIMEOUT_MS}).
|
|
52
|
+
* - **Fails the case, not closed-to-a-human:** any throw / timeout / unparseable output / unusable
|
|
53
|
+
* model returns `{ ok: false, error }` — never a verdict, and never an auto-pass.
|
|
54
|
+
*
|
|
55
|
+
* @param answer The SUT's answer text to grade.
|
|
56
|
+
* @param rubric The case's judge rubric (already validated non-blank by `parseEvalSuite`).
|
|
57
|
+
* @param model The judge model — the caller passes `config.llm` (see module doc); `undefined`
|
|
58
|
+
* fails the case immediately, same as an unusable model.
|
|
59
|
+
*/
|
|
60
|
+
export declare function judgeEvalCase(answer: string, rubric: string, model: BaseChatModel | undefined, options?: {
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
}): Promise<JudgeOutcome>;
|
package/dist/judge.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module judge
|
|
3
|
+
*
|
|
4
|
+
* BATCH-2 — LLM-as-judge grading for `gth eval`. Adapts the *mechanism* of EXT-10's shell-safety
|
|
5
|
+
* judge (`packages/core/src/core/shell/judge.ts`): `model.withStructuredOutput(zodSchema)` for a
|
|
6
|
+
* single non-agentic structured call, raced against a timeout. The failure policy differs on
|
|
7
|
+
* purpose — EXT-10 fails CLOSED (escalates to a human approval prompt); eval has no human in the
|
|
8
|
+
* loop, so a judge that can't produce a verdict simply FAILS the case ({@link JudgeOutcome.ok}
|
|
9
|
+
* `false`), it does not "escalate" anywhere.
|
|
10
|
+
*
|
|
11
|
+
* Verdict scale matches `review`'s existing convention (`packages/review/src/middleware/
|
|
12
|
+
* reviewRateMiddleware.ts`'s `RateSchema`: `rate` 0-10 + a reason/comment string) for UX
|
|
13
|
+
* consistency — a user who knows `review`'s threshold semantics already knows eval's. `RateSchema`
|
|
14
|
+
* itself is not imported/reused: it is coupled to review's middleware/tool-call/artifact-store
|
|
15
|
+
* plumbing, which doesn't fit a plain structured-output call here.
|
|
16
|
+
*
|
|
17
|
+
* Model source: `config.llm` by default, i.e. the SAME model config as the SUT — matching
|
|
18
|
+
* `judgeShellCommand`'s own default. A separate `--judge <profile>` model is BATCH-2's own
|
|
19
|
+
* "Not in scope" list (identity-matrix/pluggable-target work); grading with the SUT's own model
|
|
20
|
+
* config is a known, real simplification for this first slice.
|
|
21
|
+
*/
|
|
22
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
23
|
+
import * as z from 'zod';
|
|
24
|
+
/** Structured verdict the judge model must return — 0-10 `rate` + a `reason`, matching `review`'s
|
|
25
|
+
* `RateSchema` shape (`rate`/`comment`) closely enough that the scale/semantics are the same;
|
|
26
|
+
* named `reason` here to read naturally as "why this rate" rather than a generic review comment. */
|
|
27
|
+
export const EvalVerdictSchema = z.object({
|
|
28
|
+
rate: z
|
|
29
|
+
.number()
|
|
30
|
+
.min(0)
|
|
31
|
+
.max(10)
|
|
32
|
+
.describe('How well the answer satisfies the rubric, from 0 to 10.'),
|
|
33
|
+
reason: z.string().describe('One or two sentences explaining the rate.'),
|
|
34
|
+
});
|
|
35
|
+
/** Default wall-clock budget (ms) for the judge LLM call — same default as `judgeShellCommand`'s
|
|
36
|
+
* `JUDGE_DEFAULT_TIMEOUT_MS`, kept as eval's own constant since the two judges live in different
|
|
37
|
+
* packages (`@gaunt-sloth/core` vs `@gaunt-sloth/batch`) and are conceptually independent gates. */
|
|
38
|
+
export const EVAL_JUDGE_DEFAULT_TIMEOUT_MS = 30_000;
|
|
39
|
+
const EVAL_JUDGE_SYSTEM_PROMPT = [
|
|
40
|
+
"You are gaunt-sloth's eval judge.",
|
|
41
|
+
'You are given a rubric and an AI assistant answer, and must rate how well the answer satisfies',
|
|
42
|
+
'that rubric.',
|
|
43
|
+
'',
|
|
44
|
+
'Rate on a scale from 0 to 10, where 0 is a complete failure to satisfy the rubric and 10 is a',
|
|
45
|
+
'perfect match. Base the rating only on whether the answer satisfies the rubric — not on style,',
|
|
46
|
+
'tone, or preferences the rubric does not mention.',
|
|
47
|
+
'Use the reason field to briefly justify the rate, referencing what the rubric asked for and',
|
|
48
|
+
'what the answer actually did.',
|
|
49
|
+
].join('\n');
|
|
50
|
+
/**
|
|
51
|
+
* Build the judge's messages: a fixed system preamble and a user message embedding the rubric and
|
|
52
|
+
* the SUT's answer. Exposed (and returning plain strings) so tests can assert structure without a
|
|
53
|
+
* live model.
|
|
54
|
+
*/
|
|
55
|
+
export function buildJudgeMessages(answer, rubric) {
|
|
56
|
+
const userLines = [
|
|
57
|
+
'Rubric:',
|
|
58
|
+
rubric,
|
|
59
|
+
'',
|
|
60
|
+
'Answer to evaluate:',
|
|
61
|
+
'<answer>',
|
|
62
|
+
answer || '(empty answer)',
|
|
63
|
+
'</answer>',
|
|
64
|
+
];
|
|
65
|
+
return { system: EVAL_JUDGE_SYSTEM_PROMPT, user: userLines.join('\n') };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Grade one case's SUT answer against its rubric via LLM-as-judge.
|
|
69
|
+
*
|
|
70
|
+
* - Builds the judge prompt ({@link buildJudgeMessages}).
|
|
71
|
+
* - Calls `model.withStructuredOutput(EvalVerdictSchema)`.
|
|
72
|
+
* - Races the call against `timeoutMs` (default {@link EVAL_JUDGE_DEFAULT_TIMEOUT_MS}).
|
|
73
|
+
* - **Fails the case, not closed-to-a-human:** any throw / timeout / unparseable output / unusable
|
|
74
|
+
* model returns `{ ok: false, error }` — never a verdict, and never an auto-pass.
|
|
75
|
+
*
|
|
76
|
+
* @param answer The SUT's answer text to grade.
|
|
77
|
+
* @param rubric The case's judge rubric (already validated non-blank by `parseEvalSuite`).
|
|
78
|
+
* @param model The judge model — the caller passes `config.llm` (see module doc); `undefined`
|
|
79
|
+
* fails the case immediately, same as an unusable model.
|
|
80
|
+
*/
|
|
81
|
+
export async function judgeEvalCase(answer, rubric, model, options) {
|
|
82
|
+
const timeoutMs = options?.timeoutMs ?? EVAL_JUDGE_DEFAULT_TIMEOUT_MS;
|
|
83
|
+
if (!model || typeof model.withStructuredOutput !== 'function') {
|
|
84
|
+
return { attempted: true, ok: false, error: 'No usable judge model configured.' };
|
|
85
|
+
}
|
|
86
|
+
const { system, user } = buildJudgeMessages(answer, rubric);
|
|
87
|
+
let timer;
|
|
88
|
+
try {
|
|
89
|
+
const structured = model.withStructuredOutput(EvalVerdictSchema);
|
|
90
|
+
const judgePromise = structured.invoke([new SystemMessage(system), new HumanMessage(user)]);
|
|
91
|
+
const TIMEOUT = Symbol('eval-judge-timeout');
|
|
92
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
93
|
+
timer = setTimeout(() => resolve(TIMEOUT), timeoutMs);
|
|
94
|
+
});
|
|
95
|
+
const raced = await Promise.race([judgePromise, timeoutPromise]);
|
|
96
|
+
if (raced === TIMEOUT) {
|
|
97
|
+
return { attempted: true, ok: false, error: `Judge timed out after ${timeoutMs}ms.` };
|
|
98
|
+
}
|
|
99
|
+
// withStructuredOutput already coerces to the schema, but re-validate defensively: a fake or
|
|
100
|
+
// misbehaving model could return a non-conforming object.
|
|
101
|
+
const parsed = EvalVerdictSchema.safeParse(raced);
|
|
102
|
+
if (!parsed.success) {
|
|
103
|
+
return { attempted: true, ok: false, error: 'Judge returned unparseable output.' };
|
|
104
|
+
}
|
|
105
|
+
return { attempted: true, ok: true, verdict: parsed.data };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
attempted: true,
|
|
110
|
+
ok: false,
|
|
111
|
+
error: error instanceof Error ? error.message : String(error),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
if (timer)
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=judge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"judge.js","sourceRoot":"","sources":["../src/judge.ts"],"names":[],"mappings":"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"}
|
package/dist/matrix.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MatrixCell, MatrixRow } from '#src/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the batch matrix: the cross product of the model axis (`--models`, absent = a single
|
|
4
|
+
* `undefined`-model cell using the resolved config's model) and the input axis (`--over`, absent =
|
|
5
|
+
* a single `undefined`-row cell using the script's own content, exactly like `exec`).
|
|
6
|
+
*
|
|
7
|
+
* `models`/`rows` pass `undefined` (not an empty array) to mean "axis absent" — an empty array is
|
|
8
|
+
* treated as a caller bug and also collapses to "absent" defensively, since a 0-cell matrix would
|
|
9
|
+
* silently do nothing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildMatrix(baseContent: string, models: string[] | undefined, rows: MatrixRow[] | undefined): MatrixCell[];
|
package/dist/matrix.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { bindCellContent } from '#src/interpolate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the batch matrix: the cross product of the model axis (`--models`, absent = a single
|
|
4
|
+
* `undefined`-model cell using the resolved config's model) and the input axis (`--over`, absent =
|
|
5
|
+
* a single `undefined`-row cell using the script's own content, exactly like `exec`).
|
|
6
|
+
*
|
|
7
|
+
* `models`/`rows` pass `undefined` (not an empty array) to mean "axis absent" — an empty array is
|
|
8
|
+
* treated as a caller bug and also collapses to "absent" defensively, since a 0-cell matrix would
|
|
9
|
+
* silently do nothing.
|
|
10
|
+
*/
|
|
11
|
+
export function buildMatrix(baseContent, models, rows) {
|
|
12
|
+
const modelAxis = models && models.length > 0 ? models : [undefined];
|
|
13
|
+
const rowAxis = rows && rows.length > 0 ? rows : [undefined];
|
|
14
|
+
const cells = [];
|
|
15
|
+
for (let mi = 0; mi < modelAxis.length; mi++) {
|
|
16
|
+
for (let ri = 0; ri < rowAxis.length; ri++) {
|
|
17
|
+
const model = modelAxis[mi];
|
|
18
|
+
const row = rowAxis[ri];
|
|
19
|
+
cells.push({
|
|
20
|
+
id: `cell-${mi}-${ri}`,
|
|
21
|
+
modelIndex: mi,
|
|
22
|
+
model,
|
|
23
|
+
inputIndex: ri,
|
|
24
|
+
inputRow: row,
|
|
25
|
+
content: bindCellContent(baseContent, row),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return cells;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matrix.js","sourceRoot":"","sources":["../src/matrix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,WAAmB,EACnB,MAA4B,EAC5B,IAA6B;IAE7B,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAE7D,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;QAC7C,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;YAC3C,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;YAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC;gBACT,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACtB,UAAU,EAAE,EAAE;gBACd,KAAK;gBACL,UAAU,EAAE,EAAE;gBACd,QAAQ,EAAE,GAAG;gBACb,OAAO,EAAE,eAAe,CAAC,WAAW,EAAE,GAAG,CAAC;aAC3C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BatchSummary, CellResult } from '#src/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Write one structured JSON record per cell (`<id>.json`) plus one aggregate `results.json`
|
|
4
|
+
* (pass/fail counts + a per-cell one-liner — a lightweight flake report) into `outputDir`.
|
|
5
|
+
* Creates `outputDir` (and any missing parents) if it doesn't exist.
|
|
6
|
+
*
|
|
7
|
+
* Pure I/O, deliberately separate from {@link runBatchMatrix}: the runner never touches the
|
|
8
|
+
* filesystem, so unit tests can exercise matrix/concurrency/retry logic without a tmp dir, and this
|
|
9
|
+
* function can be tested in isolation with a fixed set of results.
|
|
10
|
+
*
|
|
11
|
+
* @returns The aggregate {@link BatchSummary} that was written to `results.json`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeBatchOutput(outputDir: string, results: CellResult[]): BatchSummary;
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { buildBatchSummary } from '#src/BatchRunner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Write one structured JSON record per cell (`<id>.json`) plus one aggregate `results.json`
|
|
6
|
+
* (pass/fail counts + a per-cell one-liner — a lightweight flake report) into `outputDir`.
|
|
7
|
+
* Creates `outputDir` (and any missing parents) if it doesn't exist.
|
|
8
|
+
*
|
|
9
|
+
* Pure I/O, deliberately separate from {@link runBatchMatrix}: the runner never touches the
|
|
10
|
+
* filesystem, so unit tests can exercise matrix/concurrency/retry logic without a tmp dir, and this
|
|
11
|
+
* function can be tested in isolation with a fixed set of results.
|
|
12
|
+
*
|
|
13
|
+
* @returns The aggregate {@link BatchSummary} that was written to `results.json`.
|
|
14
|
+
*/
|
|
15
|
+
export function writeBatchOutput(outputDir, results) {
|
|
16
|
+
mkdirSync(outputDir, { recursive: true });
|
|
17
|
+
for (const result of results) {
|
|
18
|
+
writeFileSync(join(outputDir, `${result.id}.json`), `${JSON.stringify(result, null, 2)}\n`);
|
|
19
|
+
}
|
|
20
|
+
const summary = buildBatchSummary(results);
|
|
21
|
+
writeFileSync(join(outputDir, 'results.json'), `${JSON.stringify(summary, null, 2)}\n`);
|
|
22
|
+
return summary;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,OAAqB;IACvE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3C,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAExF,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MatrixRow } from '#src/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse `--over <path>` content into matrix rows. Format is chosen by file extension:
|
|
4
|
+
* `.jsonl`/`.ndjson` → one JSON object per line; anything else (including `.csv`) → CSV.
|
|
5
|
+
*
|
|
6
|
+
* This is content binding only (BATCH-1 scope): every row becomes an object of string fields that
|
|
7
|
+
* {@link bindCellContent} interpolates into the script. A glob-of-binary-files path binding is out
|
|
8
|
+
* of scope for this task.
|
|
9
|
+
*
|
|
10
|
+
* Throws a descriptive `Error` on malformed input — the harness-level failure the CLI surface doc
|
|
11
|
+
* calls out as the one thing that should make `gth batch` exit non-zero (a bad row/cell answer
|
|
12
|
+
* never should).
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseOverFile(filePath: string, content: string): MatrixRow[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `--over <path>` content into matrix rows. Format is chosen by file extension:
|
|
3
|
+
* `.jsonl`/`.ndjson` → one JSON object per line; anything else (including `.csv`) → CSV.
|
|
4
|
+
*
|
|
5
|
+
* This is content binding only (BATCH-1 scope): every row becomes an object of string fields that
|
|
6
|
+
* {@link bindCellContent} interpolates into the script. A glob-of-binary-files path binding is out
|
|
7
|
+
* of scope for this task.
|
|
8
|
+
*
|
|
9
|
+
* Throws a descriptive `Error` on malformed input — the harness-level failure the CLI surface doc
|
|
10
|
+
* calls out as the one thing that should make `gth batch` exit non-zero (a bad row/cell answer
|
|
11
|
+
* never should).
|
|
12
|
+
*/
|
|
13
|
+
export function parseOverFile(filePath, content) {
|
|
14
|
+
const isJsonl = /\.(jsonl|ndjson)$/i.test(filePath);
|
|
15
|
+
return isJsonl ? parseJsonl(filePath, content) : parseCsv(filePath, content);
|
|
16
|
+
}
|
|
17
|
+
function parseJsonl(filePath, content) {
|
|
18
|
+
const rows = [];
|
|
19
|
+
const lines = content.split('\n');
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
const line = lines[i].trim();
|
|
22
|
+
if (line.length === 0)
|
|
23
|
+
continue;
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(line);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new Error(`--over ${filePath}: invalid JSON on line ${i + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
}
|
|
31
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
32
|
+
throw new Error(`--over ${filePath}: line ${i + 1} must be a JSON object, got ${JSON.stringify(parsed)}`);
|
|
33
|
+
}
|
|
34
|
+
rows.push(stringifyRowValues(parsed));
|
|
35
|
+
}
|
|
36
|
+
if (rows.length === 0) {
|
|
37
|
+
throw new Error(`--over ${filePath}: no rows found (empty JSONL file)`);
|
|
38
|
+
}
|
|
39
|
+
return rows;
|
|
40
|
+
}
|
|
41
|
+
function stringifyRowValues(record) {
|
|
42
|
+
const row = {};
|
|
43
|
+
for (const [key, value] of Object.entries(record)) {
|
|
44
|
+
row[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
return row;
|
|
47
|
+
}
|
|
48
|
+
/** Minimal RFC4180 CSV parser (quoted fields, escaped `""`, CRLF/LF), header row = field names. */
|
|
49
|
+
function parseCsvLines(text) {
|
|
50
|
+
const rows = [];
|
|
51
|
+
let field = '';
|
|
52
|
+
let row = [];
|
|
53
|
+
let inQuotes = false;
|
|
54
|
+
for (let i = 0; i < text.length; i++) {
|
|
55
|
+
const c = text[i];
|
|
56
|
+
if (inQuotes) {
|
|
57
|
+
if (c === '"') {
|
|
58
|
+
if (text[i + 1] === '"') {
|
|
59
|
+
field += '"';
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
inQuotes = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
field += c;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (c === '"') {
|
|
71
|
+
inQuotes = true;
|
|
72
|
+
}
|
|
73
|
+
else if (c === ',') {
|
|
74
|
+
row.push(field);
|
|
75
|
+
field = '';
|
|
76
|
+
}
|
|
77
|
+
else if (c === '\n') {
|
|
78
|
+
row.push(field);
|
|
79
|
+
rows.push(row);
|
|
80
|
+
row = [];
|
|
81
|
+
field = '';
|
|
82
|
+
}
|
|
83
|
+
else if (c !== '\r') {
|
|
84
|
+
field += c;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (field.length > 0 || row.length > 0) {
|
|
88
|
+
row.push(field);
|
|
89
|
+
rows.push(row);
|
|
90
|
+
}
|
|
91
|
+
return rows;
|
|
92
|
+
}
|
|
93
|
+
function parseCsv(filePath, content) {
|
|
94
|
+
const raw = parseCsvLines(content);
|
|
95
|
+
if (raw.length === 0) {
|
|
96
|
+
throw new Error(`--over ${filePath}: empty CSV file (no header row)`);
|
|
97
|
+
}
|
|
98
|
+
const header = raw[0].map((h) => h.trim());
|
|
99
|
+
if (header.length === 0 || header.every((h) => h.length === 0)) {
|
|
100
|
+
throw new Error(`--over ${filePath}: CSV header row is empty`);
|
|
101
|
+
}
|
|
102
|
+
const rows = raw
|
|
103
|
+
.slice(1)
|
|
104
|
+
.filter((r) => r.some((c) => c.trim() !== ''))
|
|
105
|
+
.map((r) => {
|
|
106
|
+
if (r.length !== header.length) {
|
|
107
|
+
throw new Error(`--over ${filePath}: row has ${r.length} field(s), expected ${header.length} to match the header`);
|
|
108
|
+
}
|
|
109
|
+
return Object.fromEntries(header.map((h, i) => [h, (r[i] ?? '').trim()]));
|
|
110
|
+
});
|
|
111
|
+
if (rows.length === 0) {
|
|
112
|
+
throw new Error(`--over ${filePath}: no data rows found (only a header row)`);
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=parseOver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseOver.js","sourceRoot":"","sources":["../src/parseOver.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* BATCH-1 — the shapes shared across matrix construction, the concurrency/retry runner, and the
|
|
4
|
+
* structured output writer. Deliberately independent of any LLM/runner types (`GthConfig`,
|
|
5
|
+
* `runSingleShot`, …): this package only knows about *cells* and *outcomes*. The production
|
|
6
|
+
* adapter that turns a cell into an actual `runSingleShot` call lives in
|
|
7
|
+
* `packages/app/src/commands/batchCommand.ts`, which is what keeps {@link RunCellFn} injectable —
|
|
8
|
+
* unit tests here fake it, the CLI wires the real one.
|
|
9
|
+
*/
|
|
10
|
+
/** One row/record parsed from `--over <path.csv|path.jsonl>`. Values are always strings for csv. */
|
|
11
|
+
export type MatrixRow = Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* One cell of the batch matrix — the cross product of the model axis (`--models`) and the input
|
|
14
|
+
* axis (`--over`). Either axis may be absent (single cell using the resolved config's model and/or
|
|
15
|
+
* the script's own content, mirroring `exec` with no matrix at all).
|
|
16
|
+
*/
|
|
17
|
+
export interface MatrixCell {
|
|
18
|
+
/**
|
|
19
|
+
* Filename-safe, deterministic identifier: `cell-<modelIndex>-<rowIndex>` (0-based; both
|
|
20
|
+
* default to 0 when their axis is absent). Never derived from the model name or row content so
|
|
21
|
+
* it stays stable and filesystem-safe regardless of what a model/provider string or row data
|
|
22
|
+
* contains.
|
|
23
|
+
*/
|
|
24
|
+
id: string;
|
|
25
|
+
/** 0-based index into the (possibly single-element) model axis. */
|
|
26
|
+
modelIndex: number;
|
|
27
|
+
/** The model name for this cell, when `--models` was supplied. Absent = the configured model. */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** 0-based index into the (possibly single-element) input axis. */
|
|
30
|
+
inputIndex: number;
|
|
31
|
+
/** The source row for this cell, when `--over` was supplied. Absent = no input axis. */
|
|
32
|
+
inputRow?: MatrixRow;
|
|
33
|
+
/** The fully-bound prompt content for this cell (base script content with the row interpolated). */
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
/** What one attempt at running a cell through the shared single-shot runtime produced. */
|
|
37
|
+
export interface CellRunOutcome {
|
|
38
|
+
/** `true` when the cell's run completed without error; mirrors `runSingleShot`'s contract. */
|
|
39
|
+
ok: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The model's final answer text, when the injected run-cell function can cleanly obtain it.
|
|
42
|
+
* The production adapter (wired around `runSingleShot`) populates this from
|
|
43
|
+
* `runSingleShot`'s `SingleShotResult.answer` (BATCH-2); still optional because fakes used in
|
|
44
|
+
* tests, or a future non-`runSingleShot`-backed `RunCellFn`, may leave it unset.
|
|
45
|
+
*/
|
|
46
|
+
answer?: string;
|
|
47
|
+
/** Total prompt/input tokens for the run, when available (see {@link answer}'s caveat). */
|
|
48
|
+
tokensInput?: number;
|
|
49
|
+
/** Total completion/output tokens for the run, when available (see {@link answer}'s caveat). */
|
|
50
|
+
tokensOutput?: number;
|
|
51
|
+
/** Names of tools invoked during the run, when available (see {@link answer}'s caveat). */
|
|
52
|
+
tools?: string[];
|
|
53
|
+
/** A human-readable failure reason, set when `ok` is `false`. */
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Injectable "run one prompt" function — the seam that lets {@link runBatchMatrix} be fully unit
|
|
58
|
+
* tested without any real LLM call. The production wiring (`batchCommand.ts`) adapts
|
|
59
|
+
* `runSingleShot` to this shape; tests inject a fake that resolves/rejects/throws as needed.
|
|
60
|
+
*/
|
|
61
|
+
export type RunCellFn = (cell: MatrixCell) => Promise<CellRunOutcome>;
|
|
62
|
+
/** One cell's full structured record, as written to `<id>.json` and summarized in `results.json`. */
|
|
63
|
+
export interface CellResult extends CellRunOutcome {
|
|
64
|
+
id: string;
|
|
65
|
+
model?: string;
|
|
66
|
+
inputIndex: number;
|
|
67
|
+
/** The source row for this cell, echoed for traceability (absent when no `--over` was given). */
|
|
68
|
+
inputRow?: MatrixRow;
|
|
69
|
+
/** Wall-clock time across all attempts (including retries) for this cell, in milliseconds. */
|
|
70
|
+
durationMs: number;
|
|
71
|
+
/** How many retries were consumed (0 = succeeded, or failed, on the first attempt). */
|
|
72
|
+
retries: number;
|
|
73
|
+
}
|
|
74
|
+
/** Options for {@link runBatchMatrix}. */
|
|
75
|
+
export interface BatchRunnerOptions {
|
|
76
|
+
/** Max in-flight cells. Must be >= 1; non-finite/invalid values fall back to the default. */
|
|
77
|
+
concurrency?: number;
|
|
78
|
+
/** Number of retries on a failed cell (0 = no retry, the default). */
|
|
79
|
+
retry?: number;
|
|
80
|
+
/** The injectable per-cell run function (see {@link RunCellFn}). */
|
|
81
|
+
runCell: RunCellFn;
|
|
82
|
+
}
|
|
83
|
+
/** A lightweight, `--repeat`-free version of the "flake report" §3 of the requirements asks for. */
|
|
84
|
+
export interface BatchSummary {
|
|
85
|
+
total: number;
|
|
86
|
+
passed: number;
|
|
87
|
+
failed: number;
|
|
88
|
+
cells: Array<{
|
|
89
|
+
id: string;
|
|
90
|
+
model?: string;
|
|
91
|
+
inputIndex: number;
|
|
92
|
+
ok: boolean;
|
|
93
|
+
retries: number;
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
96
|
+
/** The default concurrency cap when `-j/--concurrency` is not supplied. */
|
|
97
|
+
export declare const DEFAULT_CONCURRENCY = 4;
|