@graphorin/evals 0.5.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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/cli/index.d.ts +43 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/csv.d.ts +23 -0
- package/dist/loaders/csv.d.ts.map +1 -0
- package/dist/loaders/csv.js +116 -0
- package/dist/loaders/csv.js.map +1 -0
- package/dist/loaders/dmr.d.ts +31 -0
- package/dist/loaders/dmr.d.ts.map +1 -0
- package/dist/loaders/dmr.js +121 -0
- package/dist/loaders/dmr.js.map +1 -0
- package/dist/loaders/from-traces.d.ts +32 -0
- package/dist/loaders/from-traces.d.ts.map +1 -0
- package/dist/loaders/from-traces.js +66 -0
- package/dist/loaders/from-traces.js.map +1 -0
- package/dist/loaders/index.d.ts +9 -0
- package/dist/loaders/index.js +9 -0
- package/dist/loaders/iterable.d.ts +12 -0
- package/dist/loaders/iterable.d.ts.map +1 -0
- package/dist/loaders/iterable.js +16 -0
- package/dist/loaders/iterable.js.map +1 -0
- package/dist/loaders/jsonl.d.ts +34 -0
- package/dist/loaders/jsonl.d.ts.map +1 -0
- package/dist/loaders/jsonl.js +64 -0
- package/dist/loaders/jsonl.js.map +1 -0
- package/dist/loaders/locomo.d.ts +32 -0
- package/dist/loaders/locomo.d.ts.map +1 -0
- package/dist/loaders/locomo.js +155 -0
- package/dist/loaders/locomo.js.map +1 -0
- package/dist/loaders/longmemeval.d.ts +36 -0
- package/dist/loaders/longmemeval.d.ts.map +1 -0
- package/dist/loaders/longmemeval.js +135 -0
- package/dist/loaders/longmemeval.js.map +1 -0
- package/dist/loaders/memory-eval.d.ts +67 -0
- package/dist/loaders/memory-eval.d.ts.map +1 -0
- package/dist/regression.d.ts +13 -0
- package/dist/regression.d.ts.map +1 -0
- package/dist/regression.js +63 -0
- package/dist/regression.js.map +1 -0
- package/dist/reporters/html.d.ts +11 -0
- package/dist/reporters/html.d.ts.map +1 -0
- package/dist/reporters/html.js +60 -0
- package/dist/reporters/html.js.map +1 -0
- package/dist/reporters/index.d.ts +6 -0
- package/dist/reporters/index.js +7 -0
- package/dist/reporters/json.d.ts +11 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +10 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/junit.d.ts +11 -0
- package/dist/reporters/junit.d.ts.map +1 -0
- package/dist/reporters/junit.js +29 -0
- package/dist/reporters/junit.js.map +1 -0
- package/dist/reporters/markdown.d.ts +9 -0
- package/dist/reporters/markdown.d.ts.map +1 -0
- package/dist/reporters/markdown.js +59 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/reporters/terminal.d.ts +9 -0
- package/dist/reporters/terminal.d.ts.map +1 -0
- package/dist/reporters/terminal.js +38 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/runner.d.ts +11 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +181 -0
- package/dist/runner.js.map +1 -0
- package/dist/scorers/code/exact-match.d.ts +22 -0
- package/dist/scorers/code/exact-match.d.ts.map +1 -0
- package/dist/scorers/code/exact-match.js +67 -0
- package/dist/scorers/code/exact-match.js.map +1 -0
- package/dist/scorers/code/json-path.d.ts +18 -0
- package/dist/scorers/code/json-path.d.ts.map +1 -0
- package/dist/scorers/code/json-path.js +71 -0
- package/dist/scorers/code/json-path.js.map +1 -0
- package/dist/scorers/code/predicate.d.ts +18 -0
- package/dist/scorers/code/predicate.d.ts.map +1 -0
- package/dist/scorers/code/predicate.js +19 -0
- package/dist/scorers/code/predicate.js.map +1 -0
- package/dist/scorers/code/regex.d.ts +15 -0
- package/dist/scorers/code/regex.d.ts.map +1 -0
- package/dist/scorers/code/regex.js +37 -0
- package/dist/scorers/code/regex.js.map +1 -0
- package/dist/scorers/index.d.ts +13 -0
- package/dist/scorers/index.js +13 -0
- package/dist/scorers/llm/judge.d.ts +57 -0
- package/dist/scorers/llm/judge.d.ts.map +1 -0
- package/dist/scorers/llm/judge.js +108 -0
- package/dist/scorers/llm/judge.js.map +1 -0
- package/dist/scorers/prebuilt/index.d.ts +22 -0
- package/dist/scorers/prebuilt/index.d.ts.map +1 -0
- package/dist/scorers/prebuilt/index.js +49 -0
- package/dist/scorers/prebuilt/index.js.map +1 -0
- package/dist/scorers/trajectory/argument-validity.d.ts +25 -0
- package/dist/scorers/trajectory/argument-validity.d.ts.map +1 -0
- package/dist/scorers/trajectory/argument-validity.js +46 -0
- package/dist/scorers/trajectory/argument-validity.js.map +1 -0
- package/dist/scorers/trajectory/correct-tool-selected.d.ts +19 -0
- package/dist/scorers/trajectory/correct-tool-selected.d.ts.map +1 -0
- package/dist/scorers/trajectory/correct-tool-selected.js +45 -0
- package/dist/scorers/trajectory/correct-tool-selected.js.map +1 -0
- package/dist/scorers/trajectory/final-state-correct.d.ts +21 -0
- package/dist/scorers/trajectory/final-state-correct.d.ts.map +1 -0
- package/dist/scorers/trajectory/final-state-correct.js +42 -0
- package/dist/scorers/trajectory/final-state-correct.js.map +1 -0
- package/dist/scorers/trajectory/recovery-after-error.d.ts +15 -0
- package/dist/scorers/trajectory/recovery-after-error.d.ts.map +1 -0
- package/dist/scorers/trajectory/recovery-after-error.js +37 -0
- package/dist/scorers/trajectory/recovery-after-error.js.map +1 -0
- package/dist/scorers/trajectory/redundant-call-detection.d.ts +19 -0
- package/dist/scorers/trajectory/redundant-call-detection.d.ts.map +1 -0
- package/dist/scorers/trajectory/redundant-call-detection.js +39 -0
- package/dist/scorers/trajectory/redundant-call-detection.js.map +1 -0
- package/dist/scorers/trajectory/types.d.ts +49 -0
- package/dist/scorers/trajectory/types.d.ts.map +1 -0
- package/dist/scorers/trajectory/util.js +71 -0
- package/dist/scorers/trajectory/util.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/scorers/trajectory/recovery-after-error.ts
|
|
2
|
+
/** @stable */
|
|
3
|
+
function recoveryAfterError(options = {}) {
|
|
4
|
+
return {
|
|
5
|
+
name: options.name ?? "recovery-after-error",
|
|
6
|
+
async score({ output }) {
|
|
7
|
+
const calls = output.calls;
|
|
8
|
+
const errorIdx = [];
|
|
9
|
+
for (let i = 0; i < calls.length; i++) if (calls[i]?.status === "error") errorIdx.push(i);
|
|
10
|
+
if (errorIdx.length === 0) return {
|
|
11
|
+
pass: true,
|
|
12
|
+
score: 1,
|
|
13
|
+
reason: "no tool errors to recover from."
|
|
14
|
+
};
|
|
15
|
+
const unrecovered = errorIdx.filter((i) => {
|
|
16
|
+
for (let j = i + 1; j < calls.length; j++) if (calls[j]?.status === "ok") return false;
|
|
17
|
+
return true;
|
|
18
|
+
});
|
|
19
|
+
const pass = unrecovered.length === 0;
|
|
20
|
+
const score = (errorIdx.length - unrecovered.length) / errorIdx.length;
|
|
21
|
+
if (pass) return {
|
|
22
|
+
pass,
|
|
23
|
+
score,
|
|
24
|
+
reason: `recovered from ${errorIdx.length} tool error(s).`
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
pass,
|
|
28
|
+
score,
|
|
29
|
+
reason: `${unrecovered.length}/${errorIdx.length} tool error(s) had no successful follow-up call.`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { recoveryAfterError };
|
|
37
|
+
//# sourceMappingURL=recovery-after-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-after-error.js","names":["errorIdx: number[]"],"sources":["../../../src/scorers/trajectory/recovery-after-error.ts"],"sourcesContent":["/**\n * `recoveryAfterError` — passes when the harness recovered from every\n * tool error: each `'error'` call must be followed by at least one later\n * `'ok'` call (the run did not dead-end on a failure). A trajectory with\n * no errors passes trivially. This measures harness resilience — that a\n * surfaced `ToolError` re-enters the loop as a tool message and the agent\n * makes forward progress afterwards.\n *\n * @packageDocumentation\n */\n\nimport type { Scorer } from '@graphorin/observability/eval';\nimport type { Trajectory } from './types.js';\n\n/** @stable */\nexport interface RecoveryAfterErrorOptions {\n /** Optional name override. */\n readonly name?: string;\n}\n\n/** @stable */\nexport function recoveryAfterError<I = unknown>(\n options: RecoveryAfterErrorOptions = {},\n): Scorer<I, Trajectory> {\n const name = options.name ?? 'recovery-after-error';\n return {\n name,\n async score({ output }) {\n const calls = output.calls;\n const errorIdx: number[] = [];\n for (let i = 0; i < calls.length; i++) {\n if (calls[i]?.status === 'error') errorIdx.push(i);\n }\n if (errorIdx.length === 0) {\n return { pass: true, score: 1, reason: 'no tool errors to recover from.' };\n }\n const unrecovered = errorIdx.filter((i) => {\n for (let j = i + 1; j < calls.length; j++) {\n if (calls[j]?.status === 'ok') return false;\n }\n return true;\n });\n const pass = unrecovered.length === 0;\n const score = (errorIdx.length - unrecovered.length) / errorIdx.length;\n if (pass) {\n return { pass, score, reason: `recovered from ${errorIdx.length} tool error(s).` };\n }\n return {\n pass,\n score,\n reason: `${unrecovered.length}/${errorIdx.length} tool error(s) had no successful follow-up call.`,\n };\n },\n };\n}\n"],"mappings":";;AAqBA,SAAgB,mBACd,UAAqC,EAAE,EAChB;AAEvB,QAAO;EACL,MAFW,QAAQ,QAAQ;EAG3B,MAAM,MAAM,EAAE,UAAU;GACtB,MAAM,QAAQ,OAAO;GACrB,MAAMA,WAAqB,EAAE;AAC7B,QAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAChC,KAAI,MAAM,IAAI,WAAW,QAAS,UAAS,KAAK,EAAE;AAEpD,OAAI,SAAS,WAAW,EACtB,QAAO;IAAE,MAAM;IAAM,OAAO;IAAG,QAAQ;IAAmC;GAE5E,MAAM,cAAc,SAAS,QAAQ,MAAM;AACzC,SAAK,IAAI,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IACpC,KAAI,MAAM,IAAI,WAAW,KAAM,QAAO;AAExC,WAAO;KACP;GACF,MAAM,OAAO,YAAY,WAAW;GACpC,MAAM,SAAS,SAAS,SAAS,YAAY,UAAU,SAAS;AAChE,OAAI,KACF,QAAO;IAAE;IAAM;IAAO,QAAQ,kBAAkB,SAAS,OAAO;IAAkB;AAEpF,UAAO;IACL;IACA;IACA,QAAQ,GAAG,YAAY,OAAO,GAAG,SAAS,OAAO;IAClD;;EAEJ"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Trajectory } from "./types.js";
|
|
2
|
+
import { Scorer } from "@graphorin/observability/eval";
|
|
3
|
+
|
|
4
|
+
//#region src/scorers/trajectory/redundant-call-detection.d.ts
|
|
5
|
+
|
|
6
|
+
/** @stable */
|
|
7
|
+
interface RedundantCallDetectionOptions {
|
|
8
|
+
/** Maximum tolerated redundant repeats before the scorer fails. Default `0`. */
|
|
9
|
+
readonly maxRedundant?: number;
|
|
10
|
+
/** Tool names exempt from the check (legitimately repeatable). */
|
|
11
|
+
readonly ignore?: ReadonlyArray<string>;
|
|
12
|
+
/** Optional name override. */
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
}
|
|
15
|
+
/** @stable */
|
|
16
|
+
declare function redundantCallDetection<I = unknown>(options?: RedundantCallDetectionOptions): Scorer<I, Trajectory>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { RedundantCallDetectionOptions, redundantCallDetection };
|
|
19
|
+
//# sourceMappingURL=redundant-call-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redundant-call-detection.d.ts","names":[],"sources":["../../../src/scorers/trajectory/redundant-call-detection.ts"],"sourcesContent":[],"mappings":";;;;;;UAeiB,6BAAA;;;;oBAIG;;;;;iBAMJ,8CACL,gCACR,OAAO,GAAG"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { canonicalize } from "./util.js";
|
|
2
|
+
|
|
3
|
+
//#region src/scorers/trajectory/redundant-call-detection.ts
|
|
4
|
+
/** @stable */
|
|
5
|
+
function redundantCallDetection(options = {}) {
|
|
6
|
+
const maxRedundant = options.maxRedundant ?? 0;
|
|
7
|
+
const ignore = new Set(options.ignore ?? []);
|
|
8
|
+
return {
|
|
9
|
+
name: options.name ?? "redundant-call-detection",
|
|
10
|
+
async score({ output }) {
|
|
11
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12
|
+
const redundant = [];
|
|
13
|
+
let total = 0;
|
|
14
|
+
for (const call of output.calls) {
|
|
15
|
+
if (ignore.has(call.toolName)) continue;
|
|
16
|
+
total++;
|
|
17
|
+
if (call.status !== "ok") continue;
|
|
18
|
+
const key = `${call.toolName}(${canonicalize(call.args)})`;
|
|
19
|
+
if (seen.has(key)) redundant.push(call.toolName);
|
|
20
|
+
else seen.add(key);
|
|
21
|
+
}
|
|
22
|
+
const pass = redundant.length <= maxRedundant;
|
|
23
|
+
const score = total > 0 ? (total - redundant.length) / total : 1;
|
|
24
|
+
if (pass) return {
|
|
25
|
+
pass,
|
|
26
|
+
score
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
pass,
|
|
30
|
+
score,
|
|
31
|
+
reason: `${redundant.length} redundant repeat call(s) (max ${maxRedundant}): [${redundant.join(", ")}].`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { redundantCallDetection };
|
|
39
|
+
//# sourceMappingURL=redundant-call-detection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redundant-call-detection.js","names":["redundant: string[]"],"sources":["../../../src/scorers/trajectory/redundant-call-detection.ts"],"sourcesContent":["/**\n * `redundantCallDetection` — passes when the harness made no more than\n * `maxRedundant` (default `0`) redundant repeat calls. A call is redundant\n * when an *earlier successful* call with the same name and deep-equal\n * arguments already produced that result. Only prior `'ok'` calls seed the\n * set, so a retry after an error is never flagged as redundant.\n *\n * @packageDocumentation\n */\n\nimport type { Scorer } from '@graphorin/observability/eval';\nimport type { Trajectory } from './types.js';\nimport { canonicalize } from './util.js';\n\n/** @stable */\nexport interface RedundantCallDetectionOptions {\n /** Maximum tolerated redundant repeats before the scorer fails. Default `0`. */\n readonly maxRedundant?: number;\n /** Tool names exempt from the check (legitimately repeatable). */\n readonly ignore?: ReadonlyArray<string>;\n /** Optional name override. */\n readonly name?: string;\n}\n\n/** @stable */\nexport function redundantCallDetection<I = unknown>(\n options: RedundantCallDetectionOptions = {},\n): Scorer<I, Trajectory> {\n const maxRedundant = options.maxRedundant ?? 0;\n const ignore = new Set(options.ignore ?? []);\n const name = options.name ?? 'redundant-call-detection';\n return {\n name,\n async score({ output }) {\n const seen = new Set<string>();\n const redundant: string[] = [];\n let total = 0;\n for (const call of output.calls) {\n if (ignore.has(call.toolName)) continue;\n total++;\n // Only a successful call establishes a \"result already obtained\"\n // fact; a retry after an error is expected, not redundant.\n if (call.status !== 'ok') continue;\n const key = `${call.toolName}(${canonicalize(call.args)})`;\n if (seen.has(key)) redundant.push(call.toolName);\n else seen.add(key);\n }\n const pass = redundant.length <= maxRedundant;\n const score = total > 0 ? (total - redundant.length) / total : 1;\n if (pass) return { pass, score };\n return {\n pass,\n score,\n reason: `${redundant.length} redundant repeat call(s) (max ${maxRedundant}): [${redundant.join(', ')}].`,\n };\n },\n };\n}\n"],"mappings":";;;;AAyBA,SAAgB,uBACd,UAAyC,EAAE,EACpB;CACvB,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,SAAS,IAAI,IAAI,QAAQ,UAAU,EAAE,CAAC;AAE5C,QAAO;EACL,MAFW,QAAQ,QAAQ;EAG3B,MAAM,MAAM,EAAE,UAAU;GACtB,MAAM,uBAAO,IAAI,KAAa;GAC9B,MAAMA,YAAsB,EAAE;GAC9B,IAAI,QAAQ;AACZ,QAAK,MAAM,QAAQ,OAAO,OAAO;AAC/B,QAAI,OAAO,IAAI,KAAK,SAAS,CAAE;AAC/B;AAGA,QAAI,KAAK,WAAW,KAAM;IAC1B,MAAM,MAAM,GAAG,KAAK,SAAS,GAAG,aAAa,KAAK,KAAK,CAAC;AACxD,QAAI,KAAK,IAAI,IAAI,CAAE,WAAU,KAAK,KAAK,SAAS;QAC3C,MAAK,IAAI,IAAI;;GAEpB,MAAM,OAAO,UAAU,UAAU;GACjC,MAAM,QAAQ,QAAQ,KAAK,QAAQ,UAAU,UAAU,QAAQ;AAC/D,OAAI,KAAM,QAAO;IAAE;IAAM;IAAO;AAChC,UAAO;IACL;IACA;IACA,QAAQ,GAAG,UAAU,OAAO,iCAAiC,aAAa,MAAM,UAAU,KAAK,KAAK,CAAC;IACtG;;EAEJ"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/scorers/trajectory/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the trajectory scorer family. A {@link Trajectory} is
|
|
4
|
+
* the recorded sequence of tool calls a harness made while attempting a
|
|
5
|
+
* task, plus an optional goal-state snapshot and the final text output.
|
|
6
|
+
*
|
|
7
|
+
* The scorers in this folder are pure functions over a `Trajectory` — no
|
|
8
|
+
* network, no model — so they can gate harness reliability in CI. Build a
|
|
9
|
+
* `Trajectory` by folding an agent's `AgentEvent` stream (correlate
|
|
10
|
+
* `tool.call.start` / `tool.call.end` with `tool.execute.end` /
|
|
11
|
+
* `tool.execute.error` by `toolCallId`).
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* One executed tool call as observed on the `AgentEvent` stream.
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
interface TrajectoryToolCall {
|
|
21
|
+
readonly toolCallId: string;
|
|
22
|
+
readonly toolName: string;
|
|
23
|
+
/** The arguments the model emitted (the resolved `tool.call.end.finalArgs`). */
|
|
24
|
+
readonly args: unknown;
|
|
25
|
+
/** `'ok'` when the call returned; `'error'` when the executor surfaced a `ToolError`. */
|
|
26
|
+
readonly status: 'ok' | 'error';
|
|
27
|
+
/** The tool output, present when `status === 'ok'`. */
|
|
28
|
+
readonly result?: unknown;
|
|
29
|
+
/** The surfaced error, present when `status === 'error'`. */
|
|
30
|
+
readonly error?: {
|
|
31
|
+
readonly kind?: string;
|
|
32
|
+
readonly message?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The full record of a single harness attempt at a task.
|
|
37
|
+
*
|
|
38
|
+
* @stable
|
|
39
|
+
*/
|
|
40
|
+
interface Trajectory {
|
|
41
|
+
readonly calls: ReadonlyArray<TrajectoryToolCall>;
|
|
42
|
+
/** Goal-state snapshot compared by {@link finalStateCorrect}. */
|
|
43
|
+
readonly finalState?: unknown;
|
|
44
|
+
/** The assistant's final text, when the run completed. */
|
|
45
|
+
readonly finalOutput?: string;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { Trajectory, TrajectoryToolCall };
|
|
49
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/scorers/trajectory/types.ts"],"sourcesContent":[],"mappings":";;AAmBA;AAkBA;;;;;;;;;;;;;;;;UAlBiB,kBAAA;;;;;;;;;;;;;;;;;;;;UAkBA,UAAA;kBACC,cAAc"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/scorers/trajectory/util.ts
|
|
2
|
+
/**
|
|
3
|
+
* Internal helpers shared by the trajectory scorers: structural deep
|
|
4
|
+
* equality, dot-path reads, and an order-insensitive canonical key used to
|
|
5
|
+
* de-duplicate tool calls by `(name, args)`. Private to the family — not
|
|
6
|
+
* exported from the package surface.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/** Structural deep-equality with order-insensitive object key comparison. */
|
|
11
|
+
function deepEqual(a, b) {
|
|
12
|
+
if (a === b) return true;
|
|
13
|
+
if (typeof a !== typeof b) return false;
|
|
14
|
+
if (a === null || b === null) return a === b;
|
|
15
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
16
|
+
if (a.length !== b.length) return false;
|
|
17
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
21
|
+
const aObj = a;
|
|
22
|
+
const bObj = b;
|
|
23
|
+
const aKeys = Object.keys(aObj).sort();
|
|
24
|
+
const bKeys = Object.keys(bObj).sort();
|
|
25
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
26
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
27
|
+
const key = aKeys[i];
|
|
28
|
+
if (key === void 0 || key !== bKeys[i]) return false;
|
|
29
|
+
if (!deepEqual(aObj[key], bObj[key])) return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/** Read a dot-separated path (`'orders.0.status'`) out of a value. */
|
|
36
|
+
function readPath(root, path) {
|
|
37
|
+
if (path.length === 0) return root;
|
|
38
|
+
let cursor = root;
|
|
39
|
+
for (const segment of path.split(".")) {
|
|
40
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
41
|
+
if (Array.isArray(cursor)) {
|
|
42
|
+
const idx = Number.parseInt(segment, 10);
|
|
43
|
+
if (!Number.isFinite(idx)) return void 0;
|
|
44
|
+
cursor = cursor[idx];
|
|
45
|
+
} else if (typeof cursor === "object") cursor = cursor[segment];
|
|
46
|
+
else return;
|
|
47
|
+
}
|
|
48
|
+
return cursor;
|
|
49
|
+
}
|
|
50
|
+
/** Order-insensitive canonical JSON, so `{a,b}` and `{b,a}` key alike. */
|
|
51
|
+
function canonicalize(value) {
|
|
52
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
53
|
+
if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
|
|
54
|
+
const obj = value;
|
|
55
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`).join(",")}}`;
|
|
56
|
+
}
|
|
57
|
+
function truncate(s, max = 80) {
|
|
58
|
+
return s.length > max ? `${s.slice(0, max)}…` : s;
|
|
59
|
+
}
|
|
60
|
+
function stringifySafe(value) {
|
|
61
|
+
if (value === void 0) return "undefined";
|
|
62
|
+
try {
|
|
63
|
+
return JSON.stringify(value) ?? String(value);
|
|
64
|
+
} catch {
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { canonicalize, deepEqual, readPath, stringifySafe, truncate };
|
|
71
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","names":["cursor: unknown"],"sources":["../../../src/scorers/trajectory/util.ts"],"sourcesContent":["/**\n * Internal helpers shared by the trajectory scorers: structural deep\n * equality, dot-path reads, and an order-insensitive canonical key used to\n * de-duplicate tool calls by `(name, args)`. Private to the family — not\n * exported from the package surface.\n *\n * @packageDocumentation\n */\n\n/** Structural deep-equality with order-insensitive object key comparison. */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (typeof a === 'object' && typeof b === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj).sort();\n const bKeys = Object.keys(bObj).sort();\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i++) {\n const key = aKeys[i];\n if (key === undefined || key !== bKeys[i]) return false;\n if (!deepEqual(aObj[key], bObj[key])) return false;\n }\n return true;\n }\n return false;\n}\n\n/** Read a dot-separated path (`'orders.0.status'`) out of a value. */\nexport function readPath(root: unknown, path: string): unknown {\n if (path.length === 0) return root;\n let cursor: unknown = root;\n for (const segment of path.split('.')) {\n if (cursor === null || cursor === undefined) return undefined;\n if (Array.isArray(cursor)) {\n const idx = Number.parseInt(segment, 10);\n if (!Number.isFinite(idx)) return undefined;\n cursor = cursor[idx];\n } else if (typeof cursor === 'object') {\n cursor = (cursor as Record<string, unknown>)[segment];\n } else {\n return undefined;\n }\n }\n return cursor;\n}\n\n/** Order-insensitive canonical JSON, so `{a,b}` and `{b,a}` key alike. */\nexport function canonicalize(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value) ?? 'null';\n }\n if (Array.isArray(value)) {\n return `[${value.map(canonicalize).join(',')}]`;\n }\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`).join(',')}}`;\n}\n\nexport function truncate(s: string, max = 80): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n\nexport function stringifySafe(value: unknown): string {\n if (value === undefined) return 'undefined';\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,UAAU,GAAY,GAAqB;AACzD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAClC,KAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAE,QAAO;AAErC,SAAO;;AAET,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;EAClD,MAAM,OAAO;EACb,MAAM,OAAO;EACb,MAAM,QAAQ,OAAO,KAAK,KAAK,CAAC,MAAM;EACtC,MAAM,QAAQ,OAAO,KAAK,KAAK,CAAC,MAAM;AACtC,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,MAAM,MAAM;AAClB,OAAI,QAAQ,UAAa,QAAQ,MAAM,GAAI,QAAO;AAClD,OAAI,CAAC,UAAU,KAAK,MAAM,KAAK,KAAK,CAAE,QAAO;;AAE/C,SAAO;;AAET,QAAO;;;AAIT,SAAgB,SAAS,MAAe,MAAuB;AAC7D,KAAI,KAAK,WAAW,EAAG,QAAO;CAC9B,IAAIA,SAAkB;AACtB,MAAK,MAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AACrC,MAAI,WAAW,QAAQ,WAAW,OAAW,QAAO;AACpD,MAAI,MAAM,QAAQ,OAAO,EAAE;GACzB,MAAM,MAAM,OAAO,SAAS,SAAS,GAAG;AACxC,OAAI,CAAC,OAAO,SAAS,IAAI,CAAE,QAAO;AAClC,YAAS,OAAO;aACP,OAAO,WAAW,SAC3B,UAAU,OAAmC;MAE7C;;AAGJ,QAAO;;;AAIT,SAAgB,aAAa,OAAwB;AACnD,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC,QAAO,KAAK,UAAU,MAAM,IAAI;AAElC,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,IAAI,MAAM,IAAI,aAAa,CAAC,KAAK,IAAI,CAAC;CAE/C,MAAM,MAAM;AAEZ,QAAO,IADM,OAAO,KAAK,IAAI,CAAC,MAAM,CACpB,KAAK,MAAM,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,aAAa,IAAI,GAAG,GAAG,CAAC,KAAK,IAAI,CAAC;;AAGvF,SAAgB,SAAS,GAAW,MAAM,IAAY;AACpD,QAAO,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK;;AAGlD,SAAgB,cAAc,OAAwB;AACpD,KAAI,UAAU,OAAW,QAAO;AAChC,KAAI;AACF,SAAO,KAAK,UAAU,MAAM,IAAI,OAAO,MAAM;SACvC;AACN,SAAO,OAAO,MAAM"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Case as Case$1, Dataset as Dataset$1, EvalCaseResult, EvalReport, EvalReport as EvalReport$1, RunEvalOptions, ScoreResult as ScoreResult$1, Scorer, Scorer as Scorer$1 } from "@graphorin/observability/eval";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Agent shape consumed by the runner. Anything with a `run(input)`
|
|
7
|
+
* method satisfies the contract — the framework's own `Agent` type
|
|
8
|
+
* matches by structural typing.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
interface AgentLike<I, O> {
|
|
13
|
+
run(input: I, ctx?: {
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}): Promise<O>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options accepted by the parallel runner.
|
|
19
|
+
*
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
interface RunOptions<I, O> {
|
|
23
|
+
readonly agent: AgentLike<I, O>;
|
|
24
|
+
readonly dataset: {
|
|
25
|
+
readonly cases: ReadonlyArray<{
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
readonly input: I;
|
|
28
|
+
readonly expected?: O;
|
|
29
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
readonly scorers: ReadonlyArray<Scorer<I, O>>;
|
|
33
|
+
/** Default `1`. */
|
|
34
|
+
readonly iterations?: number;
|
|
35
|
+
/** Default `1` (sequential). Set higher for parallel evaluation. */
|
|
36
|
+
readonly concurrency?: number;
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
38
|
+
/**
|
|
39
|
+
* How to handle `signal` abort. Default (`false`): stop dispatching and
|
|
40
|
+
* resolve with a **partial** {@link EvalReport} (`aborted: true`) covering
|
|
41
|
+
* the cases that finished — a long judged run shouldn't lose all completed
|
|
42
|
+
* work to a Ctrl+C. Set `true` to throw the abort reason instead.
|
|
43
|
+
*/
|
|
44
|
+
readonly throwOnAbort?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Optional progress hook invoked after every case. Useful for
|
|
47
|
+
* terminal reporters that want a per-case heartbeat.
|
|
48
|
+
*/
|
|
49
|
+
readonly onProgress?: (event: ProgressEvent) => void;
|
|
50
|
+
}
|
|
51
|
+
/** @stable */
|
|
52
|
+
interface ProgressEvent {
|
|
53
|
+
readonly index: number;
|
|
54
|
+
readonly total: number;
|
|
55
|
+
readonly caseId: string;
|
|
56
|
+
readonly durationMs: number;
|
|
57
|
+
readonly passed: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Regression-detection options.
|
|
61
|
+
*
|
|
62
|
+
* @stable
|
|
63
|
+
*/
|
|
64
|
+
interface RegressionOptions {
|
|
65
|
+
/** Minimum drop in pass-rate (in percentage points) that counts as a regression. */
|
|
66
|
+
readonly maxPassRateDropPct?: number;
|
|
67
|
+
/** Minimum drop in average score per scorer that counts as a regression. */
|
|
68
|
+
readonly maxAvgScoreDrop?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Maximum allowed increase in `avgDurationMs` before it counts as a
|
|
71
|
+
* regression. **Opt-in: defaults to `Infinity` (gate off)** because absolute
|
|
72
|
+
* wall-clock budgets are environment-sensitive (workstation baseline vs CI
|
|
73
|
+
* runner, real LLM-latency jitter). Pass a finite ms budget to enable an
|
|
74
|
+
* absolute duration gate; leave unset to ignore duration entirely.
|
|
75
|
+
*/
|
|
76
|
+
readonly maxAvgDurationIncreaseMs?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Result of {@link detectRegressions}.
|
|
80
|
+
*
|
|
81
|
+
* @stable
|
|
82
|
+
*/
|
|
83
|
+
interface RegressionReport<I, O> {
|
|
84
|
+
readonly hasRegressions: boolean;
|
|
85
|
+
readonly findings: ReadonlyArray<RegressionFinding>;
|
|
86
|
+
readonly current: EvalReport<I, O>;
|
|
87
|
+
readonly baseline: EvalReport<I, O>;
|
|
88
|
+
}
|
|
89
|
+
/** @stable */
|
|
90
|
+
interface RegressionFinding {
|
|
91
|
+
readonly kind: 'pass-rate-drop' | 'avg-score-drop' | 'avg-duration-increase' | 'scorer-removed';
|
|
92
|
+
readonly scorer?: string;
|
|
93
|
+
readonly message: string;
|
|
94
|
+
readonly delta: number;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { AgentLike, type Case$1 as Case, type Dataset$1 as Dataset, type EvalCaseResult, type EvalReport$1 as EvalReport, ProgressEvent, RegressionFinding, RegressionOptions, RegressionReport, type RunEvalOptions, RunOptions, type ScoreResult$1 as ScoreResult, type Scorer$1 as Scorer };
|
|
98
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAiD4C,UAnB3B,SAmB2B,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EAAV,GAAA,CAAA,KAAA,EAlBrB,CAkBqB,EAAA,GAKd,CALc,EAAA;IAAd,MAAA,CAAA,EAlBa,WAkBb;EAKA,CAAA,CAAA,EAvB6B,OAuB7B,CAvBqC,CAuBrC,CAAA;;;AAgBpB;AAaA;AAoBA;;AAEqB,UAlEJ,UAkEI,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EACU,SAAA,KAAA,EAlEb,SAkEa,CAlEH,CAkEG,EAlEA,CAkEA,CAAA;EAAG,SAAA,OAAA,EAAA;IAAd,SAAA,KAAA,EAhEA,aAgEA,CAAA;MACY,SAAA,EAAA,CAAA,EAAA,MAAA;MAAG,SAAA,KAAA,EA/Db,CA+Da;MAAd,SAAA,QAAA,CAAA,EA9DK,CA8DL;MAAU,SAAA,QAAA,CAAA,EA7DL,QA6DK,CA7DI,MA6DJ,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA;IAId,CAAA,CAAA;;oBA9DG,cAAc,OAAO,GAAG;;;;;oBAKxB;;;;;;;;;;;;gCAYY;;;UAIf,aAAA;;;;;;;;;;;;UAaA,iBAAA;;;;;;;;;;;;;;;;;;;UAoBA;;qBAEI,cAAc;oBACf,WAAW,GAAG;qBACb,WAAW,GAAG;;;UAIlB,iBAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphorin/evals",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Eval framework for the Graphorin framework. Ships scorer libraries (`code/` exact-match, regex, JSON path, predicates; `llm/` LLM-judge; `prebuilt/` toxicity / factuality / helpfulness), dataset loaders (JSONL, CSV, from-traces, generic iterable), reporters (terminal, markdown, JSON, JUnit XML), a parallel runner with `concurrency` config, and regression detection that compares the current run against a stored baseline. Builds on the eval primitives shipped from `@graphorin/observability` (RB-17 / DEC-152 — full orchestrator lives here, post-MVP, decoupled from the core observability bundle so consumers do not pay the dataset / reporter cost when only the inline runner is needed). Created and maintained by Oleksiy Stepurenko.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Oleksiy Stepurenko",
|
|
7
|
+
"homepage": "https://github.com/o-stepper/graphorin/tree/main/packages/evals",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/o-stepper/graphorin.git",
|
|
11
|
+
"directory": "packages/evals"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/o-stepper/graphorin/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"graphorin",
|
|
18
|
+
"ai",
|
|
19
|
+
"agents",
|
|
20
|
+
"framework",
|
|
21
|
+
"evals",
|
|
22
|
+
"evaluation",
|
|
23
|
+
"scorers",
|
|
24
|
+
"llm-judge",
|
|
25
|
+
"datasets",
|
|
26
|
+
"reporters",
|
|
27
|
+
"regression-detection"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./scorers": {
|
|
43
|
+
"types": "./dist/scorers/index.d.ts",
|
|
44
|
+
"import": "./dist/scorers/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./loaders": {
|
|
47
|
+
"types": "./dist/loaders/index.d.ts",
|
|
48
|
+
"import": "./dist/loaders/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./reporters": {
|
|
51
|
+
"types": "./dist/reporters/index.d.ts",
|
|
52
|
+
"import": "./dist/reporters/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./cli": {
|
|
55
|
+
"types": "./dist/cli/index.d.ts",
|
|
56
|
+
"import": "./dist/cli/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./package.json": "./package.json"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"dist",
|
|
62
|
+
"README.md",
|
|
63
|
+
"CHANGELOG.md",
|
|
64
|
+
"LICENSE"
|
|
65
|
+
],
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@graphorin/core": "0.5.0",
|
|
68
|
+
"@graphorin/observability": "0.5.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"provenance": true
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsdown",
|
|
76
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"lint": "biome check .",
|
|
79
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
80
|
+
}
|
|
81
|
+
}
|