@danypops/pi-eval-harness 0.2.0 → 0.3.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/README.md +14 -0
- package/dist/ablation.d.ts +2 -0
- package/dist/ablation.js +4 -2
- package/dist/trials.d.ts +5 -0
- package/dist/trials.js +4 -0
- package/dist/turns.d.ts +3 -0
- package/dist/turns.js +2 -0
- package/package.json +1 -1
- package/src/ablation.ts +6 -2
- package/src/trials.ts +9 -0
- package/src/turns.ts +5 -0
package/README.md
CHANGED
|
@@ -48,6 +48,20 @@ would only hide the assertion, not simplify it.
|
|
|
48
48
|
A `Checker` that only ever gets exercised against a real, expensive `pi-process-harness` run has
|
|
49
49
|
no fast, deterministic proof it is correct in isolation -- write the fixture test first.
|
|
50
50
|
|
|
51
|
+
## Real live-LLM smoke test (opt-in, real cost -- never run automatically)
|
|
52
|
+
|
|
53
|
+
`scripts/real-llm-smoke-test.ts` spawns a genuine `pi` process against a real model (no faux
|
|
54
|
+
provider, no scripted tool calls) and runs the captured trace through this package's own
|
|
55
|
+
matching/rollup functions -- confirming they handle a real model's own event shape, not just the
|
|
56
|
+
faux provider's conveniences every other test here relies on. Run explicitly:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
bun scripts/real-llm-smoke-test.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Requires real provider credentials already configured in your own ambient Pi profile (this
|
|
63
|
+
script deliberately runs with `isolatedHome: false`) and incurs a real, billed API call.
|
|
64
|
+
|
|
51
65
|
## License
|
|
52
66
|
|
|
53
67
|
MIT
|
package/dist/ablation.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface AblationDelta {
|
|
|
17
17
|
readonly meanScoreDelta: number;
|
|
18
18
|
readonly meanDurationMsDelta: number;
|
|
19
19
|
readonly meanTokensInDelta: number;
|
|
20
|
+
readonly meanCacheReadTokensDelta: number;
|
|
21
|
+
readonly meanCacheWriteTokensDelta: number;
|
|
20
22
|
}
|
|
21
23
|
/** One config's own trial metrics, plus its delta vs baseline. `delta` is undefined for the baseline itself. */
|
|
22
24
|
export interface AblationResult {
|
package/dist/ablation.js
CHANGED
|
@@ -32,6 +32,8 @@ export async function ablate(configs, n, options = {}) {
|
|
|
32
32
|
meanScoreDelta: metrics.meanScore - baseline.meanScore,
|
|
33
33
|
meanDurationMsDelta: metrics.meanDurationMs - baseline.meanDurationMs,
|
|
34
34
|
meanTokensInDelta: metrics.meanTokensIn - baseline.meanTokensIn,
|
|
35
|
+
meanCacheReadTokensDelta: metrics.meanCacheReadTokens - baseline.meanCacheReadTokens,
|
|
36
|
+
meanCacheWriteTokensDelta: metrics.meanCacheWriteTokens - baseline.meanCacheWriteTokens,
|
|
35
37
|
},
|
|
36
38
|
};
|
|
37
39
|
});
|
|
@@ -45,10 +47,10 @@ export function formatAblation(label, results) {
|
|
|
45
47
|
const lines = [`=== Ablation: ${label} ===`];
|
|
46
48
|
for (const result of results) {
|
|
47
49
|
const m = result.metrics;
|
|
48
|
-
lines.push(` ${result.config.name.padEnd(14)} pass_rate=${m.passRate.toFixed(2)} mean_score=${m.meanScore.toFixed(2)} mean_duration_ms=${m.meanDurationMs.toFixed(0)} tokens_in=${m.meanTokensIn.toFixed(0)}`);
|
|
50
|
+
lines.push(` ${result.config.name.padEnd(14)} pass_rate=${m.passRate.toFixed(2)} mean_score=${m.meanScore.toFixed(2)} mean_duration_ms=${m.meanDurationMs.toFixed(0)} tokens_in=${m.meanTokensIn.toFixed(0)} cache_read=${m.meanCacheReadTokens.toFixed(0)} cache_write=${m.meanCacheWriteTokens.toFixed(0)}`);
|
|
49
51
|
if (result.delta) {
|
|
50
52
|
const d = result.delta;
|
|
51
|
-
lines.push(` ${"(vs baseline)".padEnd(14)} Δpass_rate=${signed(d.passRateDelta, 2)} Δscore=${signed(d.meanScoreDelta, 2)} Δduration_ms=${signed(d.meanDurationMsDelta, 0)} Δtokens_in=${signed(d.meanTokensInDelta, 0)}`);
|
|
53
|
+
lines.push(` ${"(vs baseline)".padEnd(14)} Δpass_rate=${signed(d.passRateDelta, 2)} Δscore=${signed(d.meanScoreDelta, 2)} Δduration_ms=${signed(d.meanDurationMsDelta, 0)} Δtokens_in=${signed(d.meanTokensInDelta, 0)} Δcache_read=${signed(d.meanCacheReadTokensDelta, 0)} Δcache_write=${signed(d.meanCacheWriteTokensDelta, 0)}`);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
return lines.join("\n");
|
package/dist/trials.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export interface TrialResult {
|
|
|
11
11
|
readonly durationMs: number;
|
|
12
12
|
readonly tokensIn: number;
|
|
13
13
|
readonly tokensOut: number;
|
|
14
|
+
/** Real cache-read/cache-write tokens -- under prompt caching, the dominant real component of total context size; tokensIn alone (a provider's own incremental, non-cached count) understates it. */
|
|
15
|
+
readonly cacheReadTokens: number;
|
|
16
|
+
readonly cacheWriteTokens: number;
|
|
14
17
|
readonly costUsd: number;
|
|
15
18
|
readonly error?: string;
|
|
16
19
|
}
|
|
@@ -26,6 +29,8 @@ export interface TrialMetrics {
|
|
|
26
29
|
readonly meanDurationMs: number;
|
|
27
30
|
readonly meanTokensIn: number;
|
|
28
31
|
readonly meanTokensOut: number;
|
|
32
|
+
readonly meanCacheReadTokens: number;
|
|
33
|
+
readonly meanCacheWriteTokens: number;
|
|
29
34
|
readonly meanCostUsd: number;
|
|
30
35
|
}
|
|
31
36
|
/**
|
package/dist/trials.js
CHANGED
|
@@ -55,6 +55,8 @@ export function aggregateTrials(results, options = {}) {
|
|
|
55
55
|
meanDurationMs: mean(results.map((result) => result.durationMs)),
|
|
56
56
|
meanTokensIn: mean(results.map((result) => result.tokensIn)),
|
|
57
57
|
meanTokensOut: mean(results.map((result) => result.tokensOut)),
|
|
58
|
+
meanCacheReadTokens: mean(results.map((result) => result.cacheReadTokens)),
|
|
59
|
+
meanCacheWriteTokens: mean(results.map((result) => result.cacheWriteTokens)),
|
|
58
60
|
meanCostUsd: mean(results.map((result) => result.costUsd)),
|
|
59
61
|
};
|
|
60
62
|
}
|
|
@@ -80,6 +82,8 @@ export async function runTrials(runOne, n, options = {}) {
|
|
|
80
82
|
durationMs: 0,
|
|
81
83
|
tokensIn: 0,
|
|
82
84
|
tokensOut: 0,
|
|
85
|
+
cacheReadTokens: 0,
|
|
86
|
+
cacheWriteTokens: 0,
|
|
83
87
|
costUsd: 0,
|
|
84
88
|
error: error instanceof Error ? error.message : String(error),
|
|
85
89
|
};
|
package/dist/turns.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface Turn {
|
|
|
14
14
|
readonly tokensIn: number;
|
|
15
15
|
readonly tokensOut: number;
|
|
16
16
|
readonly cacheReadTokens: number;
|
|
17
|
+
/** New context tokens written to the provider's cache this turn -- the other real component of total context size under prompt caching, alongside cacheReadTokens. */
|
|
18
|
+
readonly cacheWriteTokens: number;
|
|
17
19
|
/** Real cost in USD from Usage.cost.total, when the provider reports pricing. */
|
|
18
20
|
readonly costUsd: number;
|
|
19
21
|
/** Number of tool calls dispatched from this turn. */
|
|
@@ -29,6 +31,7 @@ export interface RunUsageSummary {
|
|
|
29
31
|
readonly tokensIn: number;
|
|
30
32
|
readonly tokensOut: number;
|
|
31
33
|
readonly cacheReadTokens: number;
|
|
34
|
+
readonly cacheWriteTokens: number;
|
|
32
35
|
readonly costUsd: number;
|
|
33
36
|
readonly toolCalls: number;
|
|
34
37
|
/** Every tool name called across the whole run, in dispatch order. */
|
package/dist/turns.js
CHANGED
|
@@ -15,6 +15,7 @@ export function deriveTurns(events) {
|
|
|
15
15
|
tokensIn: usage?.input ?? 0,
|
|
16
16
|
tokensOut: usage?.output ?? 0,
|
|
17
17
|
cacheReadTokens: usage?.cacheRead ?? 0,
|
|
18
|
+
cacheWriteTokens: usage?.cacheWrite ?? 0,
|
|
18
19
|
costUsd: usage?.cost.total ?? 0,
|
|
19
20
|
toolCalls: event.toolResults.length,
|
|
20
21
|
toolNames: event.toolResults.map((result) => result.toolName),
|
|
@@ -29,6 +30,7 @@ export function summarizeRunUsage(turns) {
|
|
|
29
30
|
tokensIn: turns.reduce((sum, turn) => sum + turn.tokensIn, 0),
|
|
30
31
|
tokensOut: turns.reduce((sum, turn) => sum + turn.tokensOut, 0),
|
|
31
32
|
cacheReadTokens: turns.reduce((sum, turn) => sum + turn.cacheReadTokens, 0),
|
|
33
|
+
cacheWriteTokens: turns.reduce((sum, turn) => sum + turn.cacheWriteTokens, 0),
|
|
32
34
|
costUsd: turns.reduce((sum, turn) => sum + turn.costUsd, 0),
|
|
33
35
|
toolCalls: turns.reduce((sum, turn) => sum + turn.toolCalls, 0),
|
|
34
36
|
toolNames: turns.flatMap((turn) => turn.toolNames),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-eval-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scores a real agent run's own tool-call behavior -- AND/OR tool-call matching, graduated checker composition, and turn/tool-call/token-usage rollups -- over Pi's own real AgentSessionEvent stream (e.g. from @danypops/pi-process-harness).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/ablation.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface AblationDelta {
|
|
|
19
19
|
readonly meanScoreDelta: number;
|
|
20
20
|
readonly meanDurationMsDelta: number;
|
|
21
21
|
readonly meanTokensInDelta: number;
|
|
22
|
+
readonly meanCacheReadTokensDelta: number;
|
|
23
|
+
readonly meanCacheWriteTokensDelta: number;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/** One config's own trial metrics, plus its delta vs baseline. `delta` is undefined for the baseline itself. */
|
|
@@ -55,6 +57,8 @@ export async function ablate(configs: readonly AblationConfig[], n: number, opti
|
|
|
55
57
|
meanScoreDelta: metrics.meanScore - baseline.meanScore,
|
|
56
58
|
meanDurationMsDelta: metrics.meanDurationMs - baseline.meanDurationMs,
|
|
57
59
|
meanTokensInDelta: metrics.meanTokensIn - baseline.meanTokensIn,
|
|
60
|
+
meanCacheReadTokensDelta: metrics.meanCacheReadTokens - baseline.meanCacheReadTokens,
|
|
61
|
+
meanCacheWriteTokensDelta: metrics.meanCacheWriteTokens - baseline.meanCacheWriteTokens,
|
|
58
62
|
},
|
|
59
63
|
};
|
|
60
64
|
});
|
|
@@ -71,12 +75,12 @@ export function formatAblation(label: string, results: readonly AblationResult[]
|
|
|
71
75
|
for (const result of results) {
|
|
72
76
|
const m = result.metrics;
|
|
73
77
|
lines.push(
|
|
74
|
-
` ${result.config.name.padEnd(14)} pass_rate=${m.passRate.toFixed(2)} mean_score=${m.meanScore.toFixed(2)} mean_duration_ms=${m.meanDurationMs.toFixed(0)} tokens_in=${m.meanTokensIn.toFixed(0)}`,
|
|
78
|
+
` ${result.config.name.padEnd(14)} pass_rate=${m.passRate.toFixed(2)} mean_score=${m.meanScore.toFixed(2)} mean_duration_ms=${m.meanDurationMs.toFixed(0)} tokens_in=${m.meanTokensIn.toFixed(0)} cache_read=${m.meanCacheReadTokens.toFixed(0)} cache_write=${m.meanCacheWriteTokens.toFixed(0)}`,
|
|
75
79
|
);
|
|
76
80
|
if (result.delta) {
|
|
77
81
|
const d = result.delta;
|
|
78
82
|
lines.push(
|
|
79
|
-
` ${"(vs baseline)".padEnd(14)} Δpass_rate=${signed(d.passRateDelta, 2)} Δscore=${signed(d.meanScoreDelta, 2)} Δduration_ms=${signed(d.meanDurationMsDelta, 0)} Δtokens_in=${signed(d.meanTokensInDelta, 0)}`,
|
|
83
|
+
` ${"(vs baseline)".padEnd(14)} Δpass_rate=${signed(d.passRateDelta, 2)} Δscore=${signed(d.meanScoreDelta, 2)} Δduration_ms=${signed(d.meanDurationMsDelta, 0)} Δtokens_in=${signed(d.meanTokensInDelta, 0)} Δcache_read=${signed(d.meanCacheReadTokensDelta, 0)} Δcache_write=${signed(d.meanCacheWriteTokensDelta, 0)}`,
|
|
80
84
|
);
|
|
81
85
|
}
|
|
82
86
|
}
|
package/src/trials.ts
CHANGED
|
@@ -12,6 +12,9 @@ export interface TrialResult {
|
|
|
12
12
|
readonly durationMs: number;
|
|
13
13
|
readonly tokensIn: number;
|
|
14
14
|
readonly tokensOut: number;
|
|
15
|
+
/** Real cache-read/cache-write tokens -- under prompt caching, the dominant real component of total context size; tokensIn alone (a provider's own incremental, non-cached count) understates it. */
|
|
16
|
+
readonly cacheReadTokens: number;
|
|
17
|
+
readonly cacheWriteTokens: number;
|
|
15
18
|
readonly costUsd: number;
|
|
16
19
|
readonly error?: string;
|
|
17
20
|
}
|
|
@@ -28,6 +31,8 @@ export interface TrialMetrics {
|
|
|
28
31
|
readonly meanDurationMs: number;
|
|
29
32
|
readonly meanTokensIn: number;
|
|
30
33
|
readonly meanTokensOut: number;
|
|
34
|
+
readonly meanCacheReadTokens: number;
|
|
35
|
+
readonly meanCacheWriteTokens: number;
|
|
31
36
|
readonly meanCostUsd: number;
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -86,6 +91,8 @@ export function aggregateTrials(results: readonly TrialResult[], options: { read
|
|
|
86
91
|
meanDurationMs: mean(results.map((result) => result.durationMs)),
|
|
87
92
|
meanTokensIn: mean(results.map((result) => result.tokensIn)),
|
|
88
93
|
meanTokensOut: mean(results.map((result) => result.tokensOut)),
|
|
94
|
+
meanCacheReadTokens: mean(results.map((result) => result.cacheReadTokens)),
|
|
95
|
+
meanCacheWriteTokens: mean(results.map((result) => result.cacheWriteTokens)),
|
|
89
96
|
meanCostUsd: mean(results.map((result) => result.costUsd)),
|
|
90
97
|
};
|
|
91
98
|
}
|
|
@@ -119,6 +126,8 @@ export async function runTrials(runOne: () => Promise<TrialResult>, n: number, o
|
|
|
119
126
|
durationMs: 0,
|
|
120
127
|
tokensIn: 0,
|
|
121
128
|
tokensOut: 0,
|
|
129
|
+
cacheReadTokens: 0,
|
|
130
|
+
cacheWriteTokens: 0,
|
|
122
131
|
costUsd: 0,
|
|
123
132
|
error: error instanceof Error ? error.message : String(error),
|
|
124
133
|
};
|
package/src/turns.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface Turn {
|
|
|
15
15
|
readonly tokensIn: number;
|
|
16
16
|
readonly tokensOut: number;
|
|
17
17
|
readonly cacheReadTokens: number;
|
|
18
|
+
/** New context tokens written to the provider's cache this turn -- the other real component of total context size under prompt caching, alongside cacheReadTokens. */
|
|
19
|
+
readonly cacheWriteTokens: number;
|
|
18
20
|
/** Real cost in USD from Usage.cost.total, when the provider reports pricing. */
|
|
19
21
|
readonly costUsd: number;
|
|
20
22
|
/** Number of tool calls dispatched from this turn. */
|
|
@@ -39,6 +41,7 @@ export function deriveTurns(events: readonly AgentSessionEvent[]): Turn[] {
|
|
|
39
41
|
tokensIn: usage?.input ?? 0,
|
|
40
42
|
tokensOut: usage?.output ?? 0,
|
|
41
43
|
cacheReadTokens: usage?.cacheRead ?? 0,
|
|
44
|
+
cacheWriteTokens: usage?.cacheWrite ?? 0,
|
|
42
45
|
costUsd: usage?.cost.total ?? 0,
|
|
43
46
|
toolCalls: event.toolResults.length,
|
|
44
47
|
toolNames: event.toolResults.map((result) => result.toolName),
|
|
@@ -53,6 +56,7 @@ export interface RunUsageSummary {
|
|
|
53
56
|
readonly tokensIn: number;
|
|
54
57
|
readonly tokensOut: number;
|
|
55
58
|
readonly cacheReadTokens: number;
|
|
59
|
+
readonly cacheWriteTokens: number;
|
|
56
60
|
readonly costUsd: number;
|
|
57
61
|
readonly toolCalls: number;
|
|
58
62
|
/** Every tool name called across the whole run, in dispatch order. */
|
|
@@ -66,6 +70,7 @@ export function summarizeRunUsage(turns: readonly Turn[]): RunUsageSummary {
|
|
|
66
70
|
tokensIn: turns.reduce((sum, turn) => sum + turn.tokensIn, 0),
|
|
67
71
|
tokensOut: turns.reduce((sum, turn) => sum + turn.tokensOut, 0),
|
|
68
72
|
cacheReadTokens: turns.reduce((sum, turn) => sum + turn.cacheReadTokens, 0),
|
|
73
|
+
cacheWriteTokens: turns.reduce((sum, turn) => sum + turn.cacheWriteTokens, 0),
|
|
69
74
|
costUsd: turns.reduce((sum, turn) => sum + turn.costUsd, 0),
|
|
70
75
|
toolCalls: turns.reduce((sum, turn) => sum + turn.toolCalls, 0),
|
|
71
76
|
toolNames: turns.flatMap((turn) => turn.toolNames),
|