@fede0089/skill-eval 1.0.0 → 1.1.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 +33 -7
- package/dist/commands/functional.js +130 -94
- package/dist/commands/trigger.js +114 -58
- package/dist/core/environment.js +18 -6
- package/dist/index.js +6 -2
- package/dist/reporters/html-reporter.js +71 -103
- package/dist/utils/exec.js +9 -2
- package/dist/utils/git.js +30 -0
- package/dist/utils/table-renderer.js +75 -72
- package/package.json +4 -1
- package/dist/commands/rate.js +0 -20
- package/dist/commands/show.js +0 -43
- package/dist/commands/view.js +0 -30
- package/dist/core/config.js +0 -58
- package/dist/core/reporters/html-reporter.js +0 -354
- package/dist/core/reporters/index.js +0 -9
- package/dist/core/reporters/json-reporter.js +0 -7
- package/dist/core/reporters/reporter.js +0 -1
- package/dist/core/runner.js +0 -75
- package/dist/core/runners/factory.js +0 -18
- package/dist/core/runners/gemini-cli.runner.js +0 -138
- package/dist/core/runners/index.js +0 -3
- package/dist/core/runners/runner.interface.js +0 -1
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ program
|
|
|
40
40
|
.option('--trials <number>', 'Number of trials per task for pass@k calculation')
|
|
41
41
|
.option('--timeout <seconds>', 'Agent timeout in seconds')
|
|
42
42
|
.option('--eval-id <id>', 'Run only the eval with this ID (numeric)')
|
|
43
|
+
.option('--compare-ref [refs...]', 'Compare against historical git references')
|
|
43
44
|
.action((agent, options) => {
|
|
44
45
|
const workspace = path.resolve(options.workspace);
|
|
45
46
|
const selectedAgent = agent || DEFAULT_AGENT;
|
|
@@ -47,7 +48,8 @@ program
|
|
|
47
48
|
const numTrials = options.trials !== undefined ? (parseInt(options.trials, 10) || 3) : 3;
|
|
48
49
|
const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1000 : undefined;
|
|
49
50
|
const evalId = options.evalId !== undefined ? parseInt(options.evalId, 10) : undefined;
|
|
50
|
-
|
|
51
|
+
const compareRefs = options.compareRef || [];
|
|
52
|
+
triggerCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId, compareRefs).catch(errorHandler);
|
|
51
53
|
});
|
|
52
54
|
program
|
|
53
55
|
.command('functional [agent]')
|
|
@@ -58,6 +60,7 @@ program
|
|
|
58
60
|
.option('--trials <number>', 'Number of trials per task for pass@k calculation')
|
|
59
61
|
.option('--timeout <seconds>', 'Agent timeout in seconds')
|
|
60
62
|
.option('--eval-id <id>', 'Run only the eval with this ID (numeric)')
|
|
63
|
+
.option('--compare-ref [refs...]', 'Compare against historical git references')
|
|
61
64
|
.action((agent, options) => {
|
|
62
65
|
const workspace = path.resolve(options.workspace);
|
|
63
66
|
const selectedAgent = agent || DEFAULT_AGENT;
|
|
@@ -65,7 +68,8 @@ program
|
|
|
65
68
|
const numTrials = options.trials !== undefined ? (parseInt(options.trials, 10) || 3) : 3;
|
|
66
69
|
const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1000 : undefined;
|
|
67
70
|
const evalId = options.evalId !== undefined ? parseInt(options.evalId, 10) : undefined;
|
|
68
|
-
|
|
71
|
+
const compareRefs = options.compareRef || [];
|
|
72
|
+
functionalCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId, compareRefs).catch(errorHandler);
|
|
69
73
|
});
|
|
70
74
|
const isMain = process.argv[1] && (() => {
|
|
71
75
|
try {
|
|
@@ -32,7 +32,7 @@ function passColorClass(val) {
|
|
|
32
32
|
return 'red';
|
|
33
33
|
}
|
|
34
34
|
function isFunctional(report) {
|
|
35
|
-
return report.metrics.
|
|
35
|
+
return report.metrics.assertionPassRate['baseline'] !== undefined;
|
|
36
36
|
}
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
38
38
|
// Metrics Grid
|
|
@@ -47,71 +47,44 @@ function renderDeltaCell(base, target, format, cellClass) {
|
|
|
47
47
|
return `<span class="${cellClass} ${cls}">${sign}${pct}%</span>`;
|
|
48
48
|
}
|
|
49
49
|
function renderMetricsGrid(report) {
|
|
50
|
-
const { metrics } = report;
|
|
50
|
+
const { metrics, results } = report;
|
|
51
51
|
const functional = isFunctional(report);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
|
|
53
|
+
const allVersions = functional ? ['baseline', ...skillVersions] : skillVersions;
|
|
54
|
+
const headerCells = allVersions.map(v => `<th>${v}</th>`).join('');
|
|
55
|
+
const successRows = `<tr>
|
|
56
|
+
<td>Success Rate</td>
|
|
57
|
+
${allVersions.map(v => {
|
|
58
|
+
const val = metrics.assertionPassRate[v] ?? metrics.passAtK[v] ?? 0;
|
|
59
|
+
return `<td><span class="metric-val ${passColorClass(val)}">${formatPercent(val)}</span></td>`;
|
|
60
|
+
}).join('')}
|
|
61
|
+
</tr>`;
|
|
62
|
+
const tokenRows = `<tr>
|
|
63
|
+
<td>Tokens (avg)</td>
|
|
64
|
+
${allVersions.map(v => {
|
|
65
|
+
const stats = metrics.tokenStats?.[v];
|
|
66
|
+
return `<td>${stats ? `<span class="metric-val">${formatTokens(stats.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>`;
|
|
67
|
+
}).join('')}
|
|
68
|
+
</tr>`;
|
|
69
|
+
const timeRows = `<tr>
|
|
70
|
+
<td>Time (avg)</td>
|
|
71
|
+
${allVersions.map(v => {
|
|
72
|
+
const stats = metrics.durationStats?.[v];
|
|
73
|
+
return `<td>${stats ? `<span class="metric-val">${formatDuration(stats.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>`;
|
|
74
|
+
}).join('')}
|
|
75
|
+
</tr>`;
|
|
76
|
+
return `<div class="metrics-grid">
|
|
62
77
|
<table>
|
|
63
78
|
<thead>
|
|
64
|
-
<tr><th></th
|
|
79
|
+
<tr><th></th>${headerCells}</tr>
|
|
65
80
|
</thead>
|
|
66
81
|
<tbody>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<td><span class="metric-val ${passColorClass(tk)}">${formatPercent(tk)}</span></td>
|
|
71
|
-
<td><span class="metric-val ${upliftClass}">${escapeHtml(metrics.skillUplift ?? '0%')}</span></td>
|
|
72
|
-
</tr>
|
|
73
|
-
<tr>
|
|
74
|
-
<td>Tokens (avg)</td>
|
|
75
|
-
<td>${wo ? `<span class="metric-val">${formatTokens(wo.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
|
|
76
|
-
<td>${wi ? `<span class="metric-val">${formatTokens(wi.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
|
|
77
|
-
<td>${wo && wi ? renderDeltaCell(wo.avgTotal, wi.avgTotal, formatTokens, 'metric-val') : '<span class="metric-val muted">—</span>'}</td>
|
|
78
|
-
</tr>
|
|
79
|
-
<tr>
|
|
80
|
-
<td>Time (avg)</td>
|
|
81
|
-
<td>${wod ? `<span class="metric-val">${formatDuration(wod.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
|
|
82
|
-
<td>${wid ? `<span class="metric-val">${formatDuration(wid.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
|
|
83
|
-
<td>${wod && wid ? renderDeltaCell(wod.avgMs, wid.avgMs, formatDuration, 'metric-val') : '<span class="metric-val muted">—</span>'}</td>
|
|
84
|
-
</tr>
|
|
82
|
+
${successRows}
|
|
83
|
+
${tokenRows}
|
|
84
|
+
${timeRows}
|
|
85
85
|
</tbody>
|
|
86
86
|
</table>
|
|
87
87
|
</div>`;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
const k = metrics.passAtK ?? 0;
|
|
91
|
-
const wi = metrics.tokenStats?.withSkill;
|
|
92
|
-
const wid = metrics.durationStats?.withSkill;
|
|
93
|
-
return `<div class="metrics-grid">
|
|
94
|
-
<table>
|
|
95
|
-
<thead>
|
|
96
|
-
<tr><th></th><th>Score</th></tr>
|
|
97
|
-
</thead>
|
|
98
|
-
<tbody>
|
|
99
|
-
<tr>
|
|
100
|
-
<td>Success Rate</td>
|
|
101
|
-
<td><span class="metric-val ${passColorClass(k)}">${formatPercent(k)}</span></td>
|
|
102
|
-
</tr>
|
|
103
|
-
<tr>
|
|
104
|
-
<td>Tokens (avg)</td>
|
|
105
|
-
<td>${wi ? `<span class="metric-val">${formatTokens(wi.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
|
|
106
|
-
</tr>
|
|
107
|
-
<tr>
|
|
108
|
-
<td>Time (avg)</td>
|
|
109
|
-
<td>${wid ? `<span class="metric-val">${formatDuration(wid.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
|
|
110
|
-
</tr>
|
|
111
|
-
</tbody>
|
|
112
|
-
</table>
|
|
113
|
-
</div>`;
|
|
114
|
-
}
|
|
115
88
|
}
|
|
116
89
|
// ---------------------------------------------------------------------------
|
|
117
90
|
// Trial details
|
|
@@ -160,63 +133,58 @@ function avgTrialDuration(trials) {
|
|
|
160
133
|
return null;
|
|
161
134
|
return Math.round(withDuration.reduce((s, t) => s + t.durationMs, 0) / withDuration.length);
|
|
162
135
|
}
|
|
163
|
-
function renderTaskMiniGrid(result) {
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
: '<span class="metric-val-sm muted">—</span>'
|
|
136
|
+
function renderTaskMiniGrid(result, isFunctionalEval) {
|
|
137
|
+
const skillVersions = Object.keys(result.skillTrials);
|
|
138
|
+
const allVersions = isFunctionalEval ? ['baseline', ...skillVersions] : skillVersions;
|
|
139
|
+
const headerCells = allVersions.map(v => `<th>${v}</th>`).join('');
|
|
140
|
+
const successRows = `<tr>
|
|
141
|
+
<td>Success Rate</td>
|
|
142
|
+
${allVersions.map(v => {
|
|
143
|
+
const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
|
|
144
|
+
const rate = trials.length ? Math.round(computeAssertionPassRate(trials) * 100) : 0;
|
|
145
|
+
return `<td><span class="metric-val-sm ${passColorClass(rate / 100)}">${rate}%</span></td>`;
|
|
146
|
+
}).join('')}
|
|
147
|
+
</tr>`;
|
|
148
|
+
const tokenRows = `<tr>
|
|
149
|
+
<td>Tokens (avg)</td>
|
|
150
|
+
${allVersions.map(v => {
|
|
151
|
+
const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
|
|
152
|
+
const tokens = avgTrialTokens(trials);
|
|
153
|
+
return `<td>${tokens != null ? `<span class="metric-val-sm">${formatTokens(tokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>`;
|
|
154
|
+
}).join('')}
|
|
155
|
+
</tr>`;
|
|
156
|
+
const timeRows = `<tr>
|
|
157
|
+
<td>Time (avg)</td>
|
|
158
|
+
${allVersions.map(v => {
|
|
159
|
+
const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
|
|
160
|
+
const ms = avgTrialDuration(trials);
|
|
161
|
+
return `<td>${ms != null ? `<span class="metric-val-sm">${formatDuration(ms)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>`;
|
|
162
|
+
}).join('')}
|
|
163
|
+
</tr>`;
|
|
181
164
|
return `<div class="metrics-grid-sm">
|
|
182
165
|
<table>
|
|
183
166
|
<thead>
|
|
184
|
-
<tr><th></th
|
|
167
|
+
<tr><th></th>${headerCells}</tr>
|
|
185
168
|
</thead>
|
|
186
169
|
<tbody>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
<td><span class="metric-val-sm ${passColorClass(tk / 100)}">${tk}%</span></td>
|
|
191
|
-
<td><span class="metric-val-sm ${rateDeltaClass}">${rateDeltaSign}${rateDelta}%</span></td>
|
|
192
|
-
</tr>
|
|
193
|
-
<tr>
|
|
194
|
-
<td>Tokens (avg)</td>
|
|
195
|
-
<td>${woTokens != null ? `<span class="metric-val-sm">${formatTokens(woTokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
|
|
196
|
-
<td>${wiTokens != null ? `<span class="metric-val-sm">${formatTokens(wiTokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
|
|
197
|
-
<td>${tokenDeltaCell}</td>
|
|
198
|
-
</tr>
|
|
199
|
-
<tr>
|
|
200
|
-
<td>Time (avg)</td>
|
|
201
|
-
<td>${woMs != null ? `<span class="metric-val-sm">${formatDuration(woMs)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
|
|
202
|
-
<td>${wiMs != null ? `<span class="metric-val-sm">${formatDuration(wiMs)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
|
|
203
|
-
<td>${durationDeltaCell}</td>
|
|
204
|
-
</tr>
|
|
170
|
+
${successRows}
|
|
171
|
+
${tokenRows}
|
|
172
|
+
${timeRows}
|
|
205
173
|
</tbody>
|
|
206
174
|
</table>
|
|
207
175
|
</div>`;
|
|
208
176
|
}
|
|
209
177
|
function renderTaskDetails(result, isFunctionalEval) {
|
|
210
178
|
const sections = [];
|
|
211
|
-
|
|
212
|
-
|
|
179
|
+
sections.push(renderTaskMiniGrid(result, isFunctionalEval));
|
|
180
|
+
if (isFunctionalEval && result.baselineTrials && result.baselineTrials.length > 0) {
|
|
181
|
+
sections.push('<div class="trial-group-label">Baseline</div>');
|
|
182
|
+
sections.push(...result.baselineTrials.map(t => renderTrial(t)));
|
|
213
183
|
}
|
|
214
|
-
|
|
215
|
-
sections.push(
|
|
216
|
-
sections.push(...result.
|
|
217
|
-
sections.push('<div class="trial-group-label">With Skill</div>');
|
|
184
|
+
for (const version of Object.keys(result.skillTrials)) {
|
|
185
|
+
sections.push(`<div class="trial-group-label">${version}</div>`);
|
|
186
|
+
sections.push(...result.skillTrials[version].map(t => renderTrial(t)));
|
|
218
187
|
}
|
|
219
|
-
sections.push(...result.trials.map(t => renderTrial(t)));
|
|
220
188
|
return `<div class="task-details" id="details-${result.taskId}">${sections.join('')}</div>`;
|
|
221
189
|
}
|
|
222
190
|
// ---------------------------------------------------------------------------
|
|
@@ -241,7 +209,7 @@ export function generateHtml(report) {
|
|
|
241
209
|
const { skill_name, agent, timestamp, metrics } = report;
|
|
242
210
|
const functional = isFunctional(report);
|
|
243
211
|
const evalType = functional ? 'Functional' : 'Trigger';
|
|
244
|
-
const overallScore = metrics.passAtK ?? 0;
|
|
212
|
+
const overallScore = metrics.passAtK['local'] ?? 0;
|
|
245
213
|
const statusClass = passColorClass(overallScore);
|
|
246
214
|
const formattedDate = new Date(timestamp).toLocaleString();
|
|
247
215
|
return `<!DOCTYPE html>
|
package/dist/utils/exec.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { execSync, spawnSync } from 'child_process';
|
|
2
2
|
export const executor = {
|
|
3
3
|
execSync(command, options) {
|
|
4
|
-
return execSync(command,
|
|
4
|
+
return execSync(command, {
|
|
5
|
+
...options,
|
|
6
|
+
shell: process.env.SHELL || '/bin/sh',
|
|
7
|
+
env: { ...process.env, ...options?.env }
|
|
8
|
+
});
|
|
5
9
|
},
|
|
6
10
|
spawnSync(command, args, options) {
|
|
7
|
-
return spawnSync(command, args,
|
|
11
|
+
return spawnSync(command, args, {
|
|
12
|
+
...options,
|
|
13
|
+
env: { ...process.env, ...options?.env }
|
|
14
|
+
});
|
|
8
15
|
}
|
|
9
16
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { executor } from './exec.js';
|
|
4
|
+
import { ExecutionError } from '../core/errors.js';
|
|
5
|
+
export const git = {
|
|
6
|
+
extractSkillRef(skillPath, ref, targetDir) {
|
|
7
|
+
// 1. Identify repo root
|
|
8
|
+
let repoRoot;
|
|
9
|
+
try {
|
|
10
|
+
repoRoot = executor.execSync('git rev-parse --show-toplevel', { cwd: skillPath }).toString().trim();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
throw new ExecutionError(`Path is not inside a git repository: ${skillPath}`);
|
|
14
|
+
}
|
|
15
|
+
// 2. Resolve relative path of skill within repo
|
|
16
|
+
const relativeSkillPath = path.relative(repoRoot, path.resolve(skillPath));
|
|
17
|
+
// 3. Create target directory
|
|
18
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
19
|
+
// 4. Extract via git archive
|
|
20
|
+
try {
|
|
21
|
+
const cmd = `git archive ${ref} | tar -x -C ${targetDir}`;
|
|
22
|
+
executor.execSync(cmd, { cwd: repoRoot });
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
throw new ExecutionError(`Failed to extract git reference '${ref}': ${err instanceof Error ? err.message : String(err)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// Also keep named export for backward compatibility if needed
|
|
30
|
+
export const extractSkillRef = git.extractSkillRef;
|
|
@@ -126,42 +126,60 @@ export function renderRunHeader(config) {
|
|
|
126
126
|
export function renderTriggerTable(report) {
|
|
127
127
|
const { results, metrics } = report;
|
|
128
128
|
const numTrials = metrics.numTrials || 1;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
// Identify all skill versions present in the results
|
|
130
|
+
const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
|
|
131
|
+
const header = ['ID', 'Prompt'];
|
|
132
|
+
if (numTrials > 1) {
|
|
133
|
+
for (const version of skillVersions) {
|
|
134
|
+
header.push(`${version} Trials`, `${version} Rate`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
for (const version of skillVersions) {
|
|
139
|
+
header.push(`${version} Rate`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const tableData = [header];
|
|
132
143
|
let hasPartialErrors = false;
|
|
133
144
|
for (const result of results) {
|
|
134
145
|
const promptSnippet = result.prompt.substring(0, 40) + (result.prompt.length > 40 ? '...' : '');
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
const row = [result.taskId.toString(), promptSnippet];
|
|
147
|
+
for (const version of skillVersions) {
|
|
148
|
+
const trials = result.skillTrials[version] || [];
|
|
149
|
+
const p1Cell = formatPassAt1(trials);
|
|
150
|
+
const someError = trials.some(t => t.isError);
|
|
151
|
+
const allError = trials.length > 0 && trials.every(t => t.isError);
|
|
152
|
+
if (someError && !allError)
|
|
153
|
+
hasPartialErrors = true;
|
|
154
|
+
if (numTrials > 1) {
|
|
155
|
+
const errorCount = trials.filter(t => t.isError).length;
|
|
156
|
+
const passedCount = trials.filter(t => t.trialPassed).length;
|
|
157
|
+
const trialsBase = `${passedCount}/${trials.length}`;
|
|
158
|
+
const trialsStr = errorCount > 0 ? `${trialsBase} (${errorCount}!)` : trialsBase;
|
|
159
|
+
const trialsCell = passedCount === trials.length ? chalk.green(trialsStr) : errorCount > 0 ? chalk.yellow(trialsStr) : chalk.red(trialsStr);
|
|
160
|
+
row.push(trialsCell, p1Cell);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
row.push(p1Cell);
|
|
164
|
+
}
|
|
150
165
|
}
|
|
166
|
+
tableData.push(row);
|
|
151
167
|
}
|
|
152
168
|
Logger.table(tableData);
|
|
153
169
|
if (hasPartialErrors) {
|
|
154
170
|
Logger.write(chalk.yellow('\n * Some trials did not complete due to infrastructure errors. success rate is computed over the trials that ran.'));
|
|
155
171
|
}
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
for (const version of skillVersions) {
|
|
173
|
+
const percentage = Math.round((metrics.passAtK[version] || 0) * 100);
|
|
174
|
+
Logger.write(`\n ${version} Success Rate: ${percentage}%`);
|
|
175
|
+
const tokenStats = metrics.tokenStats?.[version];
|
|
176
|
+
if (tokenStats) {
|
|
177
|
+
Logger.write(`\n Avg Tokens (${version}): ${formatTokenStatsLine(tokenStats)}`);
|
|
178
|
+
}
|
|
179
|
+
const durationStats = metrics.durationStats?.[version];
|
|
180
|
+
if (durationStats) {
|
|
181
|
+
Logger.write(`\n Avg Time (${version}): ${formatDuration(durationStats.avgMs)}`);
|
|
182
|
+
}
|
|
165
183
|
}
|
|
166
184
|
}
|
|
167
185
|
/**
|
|
@@ -169,61 +187,46 @@ export function renderTriggerTable(report) {
|
|
|
169
187
|
*/
|
|
170
188
|
export function renderFunctionalTable(report) {
|
|
171
189
|
const { results, metrics } = report;
|
|
172
|
-
|
|
190
|
+
// Identify all versions present (baseline + skill versions)
|
|
191
|
+
const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
|
|
192
|
+
const allVersions = ['baseline', ...skillVersions];
|
|
193
|
+
const header = ['ID', 'Prompt'];
|
|
194
|
+
for (const version of allVersions) {
|
|
195
|
+
header.push(version);
|
|
196
|
+
}
|
|
197
|
+
const tableData = [header];
|
|
173
198
|
let hasPartialErrors = false;
|
|
174
199
|
for (const result of results) {
|
|
175
200
|
const promptSnippet = result.prompt.substring(0, 40) + (result.prompt.length > 40 ? '...' : '');
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const withSkillSomeError = withSkillTrials.some(t => t.isError);
|
|
181
|
-
const withSkillAllError = withSkillTrials.length > 0 && withSkillTrials.every(t => t.isError);
|
|
182
|
-
if ((baselineSomeError && !baselineAllError) || (withSkillSomeError && !withSkillAllError))
|
|
201
|
+
const row = [result.taskId.toString(), promptSnippet];
|
|
202
|
+
// Baseline
|
|
203
|
+
const woTrials = result.baselineTrials || [];
|
|
204
|
+
if (woTrials.some(t => t.isError) && !woTrials.every(t => t.isError))
|
|
183
205
|
hasPartialErrors = true;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
206
|
+
row.push(formatAssertionRate(woTrials));
|
|
207
|
+
// Skills
|
|
208
|
+
for (const version of skillVersions) {
|
|
209
|
+
const wiTrials = result.skillTrials[version] || [];
|
|
210
|
+
if (wiTrials.some(t => t.isError) && !wiTrials.every(t => t.isError))
|
|
211
|
+
hasPartialErrors = true;
|
|
212
|
+
row.push(formatAssertionRate(wiTrials));
|
|
213
|
+
}
|
|
214
|
+
tableData.push(row);
|
|
190
215
|
}
|
|
191
216
|
Logger.table(tableData);
|
|
192
217
|
if (hasPartialErrors) {
|
|
193
218
|
Logger.write(chalk.yellow('\n * Some trials did not complete due to infrastructure errors. success rate is computed over the trials that ran.'));
|
|
194
219
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (baselineTokenStats || withSkillTokenStats) {
|
|
202
|
-
if (baselineTokenStats)
|
|
203
|
-
Logger.write(`\n Tokens (w/o skill): ${formatTokenStatsLine(baselineTokenStats)}`);
|
|
204
|
-
if (withSkillTokenStats)
|
|
205
|
-
Logger.write(`\n Tokens (w/ skill): ${formatTokenStatsLine(withSkillTokenStats)}`);
|
|
206
|
-
if (baselineTokenStats && withSkillTokenStats && baselineTokenStats.avgTotal > 0) {
|
|
207
|
-
const delta = withSkillTokenStats.avgTotal - baselineTokenStats.avgTotal;
|
|
208
|
-
const deltaSign = delta >= 0 ? '+' : '';
|
|
209
|
-
const deltaPct = Math.round((delta / baselineTokenStats.avgTotal) * 100);
|
|
210
|
-
const deltaStr = `${deltaSign}${formatTokens(Math.abs(delta))} (${deltaSign}${deltaPct}%)`;
|
|
211
|
-
Logger.write(`\n Token Delta: ${delta >= 0 ? chalk.yellow(deltaStr) : chalk.green(deltaStr)}`);
|
|
220
|
+
for (const version of allVersions) {
|
|
221
|
+
const rate = Math.round(((metrics.assertionPassRate[version] ?? metrics.passAtK[version]) || 0) * 100);
|
|
222
|
+
Logger.write(`\n ${version} Rate: ${rate}%`);
|
|
223
|
+
const stats = metrics.tokenStats?.[version];
|
|
224
|
+
if (stats) {
|
|
225
|
+
Logger.write(`\n Tokens (${version}): ${formatTokenStatsLine(stats)}`);
|
|
212
226
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (baselineDurationStats || withSkillDurationStats) {
|
|
217
|
-
if (baselineDurationStats)
|
|
218
|
-
Logger.write(`\n Time (w/o skill): ${formatDuration(baselineDurationStats.avgMs)} avg`);
|
|
219
|
-
if (withSkillDurationStats)
|
|
220
|
-
Logger.write(`\n Time (w/ skill): ${formatDuration(withSkillDurationStats.avgMs)} avg`);
|
|
221
|
-
if (baselineDurationStats && withSkillDurationStats && baselineDurationStats.avgMs > 0) {
|
|
222
|
-
const delta = withSkillDurationStats.avgMs - baselineDurationStats.avgMs;
|
|
223
|
-
const deltaSign = delta >= 0 ? '+' : '';
|
|
224
|
-
const deltaPct = Math.round((delta / baselineDurationStats.avgMs) * 100);
|
|
225
|
-
const deltaStr = `${deltaSign}${formatDuration(Math.abs(delta))} (${deltaSign}${deltaPct}%)`;
|
|
226
|
-
Logger.write(`\n Time Delta: ${delta >= 0 ? chalk.yellow(deltaStr) : chalk.green(deltaStr)}`);
|
|
227
|
+
const dStats = metrics.durationStats?.[version];
|
|
228
|
+
if (dStats) {
|
|
229
|
+
Logger.write(`\n Time (${version}): ${formatDuration(dStats.avgMs)} avg`);
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fede0089/skill-eval",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI to evaluate agent skills triggering",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"LICENSE"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
+
"release:patch": "npm version patch && git push --follow-tags",
|
|
16
|
+
"release:minor": "npm version minor && git push --follow-tags",
|
|
17
|
+
"release:major": "npm version major && git push --follow-tags",
|
|
15
18
|
"prepare": "npm run build",
|
|
16
19
|
"build": "tsc",
|
|
17
20
|
"start": "node dist/index.js",
|
package/dist/commands/rate.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.rateCommand = rateCommand;
|
|
4
|
-
const logger_1 = require("../utils/logger");
|
|
5
|
-
const errors_1 = require("../core/errors");
|
|
6
|
-
async function rateCommand(score, options) {
|
|
7
|
-
const rating = parseInt(score, 10);
|
|
8
|
-
if (isNaN(rating) || rating < 1 || rating > 5) {
|
|
9
|
-
throw new errors_1.ValidationError('Rating must be a number between 1 and 5.');
|
|
10
|
-
}
|
|
11
|
-
logger_1.Logger.info('\n==========================================');
|
|
12
|
-
logger_1.Logger.info(' EXPERIENCE RATING VIEW ');
|
|
13
|
-
logger_1.Logger.info('==========================================');
|
|
14
|
-
logger_1.Logger.info(`Rating: ${'★'.repeat(rating)}${'☆'.repeat(5 - rating)}`);
|
|
15
|
-
if (options.comment) {
|
|
16
|
-
logger_1.Logger.info(`Comment: ${options.comment}`);
|
|
17
|
-
}
|
|
18
|
-
logger_1.Logger.info('==========================================');
|
|
19
|
-
logger_1.Logger.info('Thank you for your feedback!\n');
|
|
20
|
-
}
|
package/dist/commands/show.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { Logger } from '../utils/logger.js';
|
|
5
|
-
import { AppError } from '../core/errors.js';
|
|
6
|
-
import { renderTriggerTable, renderFunctionalTable } from '../utils/table-renderer.js';
|
|
7
|
-
import { JsonReporter } from '../core/reporters/index.js';
|
|
8
|
-
export async function showCommand(workspace, reporter = new JsonReporter()) {
|
|
9
|
-
const runsDir = path.resolve(workspace, '.project-skill-evals', 'runs');
|
|
10
|
-
if (!fs.existsSync(runsDir)) {
|
|
11
|
-
throw new AppError('No evaluation runs found. Run an evaluation first.');
|
|
12
|
-
}
|
|
13
|
-
const runs = fs.readdirSync(runsDir)
|
|
14
|
-
.filter(dir => fs.statSync(path.join(runsDir, dir)).isDirectory())
|
|
15
|
-
.sort((a, b) => b.localeCompare(a)); // Sort descending to get latest
|
|
16
|
-
if (runs.length === 0) {
|
|
17
|
-
throw new AppError('No evaluation runs found. Run an evaluation first.');
|
|
18
|
-
}
|
|
19
|
-
const latestRun = runs[0];
|
|
20
|
-
const summaryPath = path.join(runsDir, latestRun, 'summary.json');
|
|
21
|
-
if (!fs.existsSync(summaryPath)) {
|
|
22
|
-
throw new AppError(`Summary file not found for the latest run: ${latestRun}`);
|
|
23
|
-
}
|
|
24
|
-
const report = JSON.parse(fs.readFileSync(summaryPath, 'utf-8'));
|
|
25
|
-
const { skill_name, agent, metrics, results, timestamp } = report;
|
|
26
|
-
const numTrials = metrics.numTrials || 1;
|
|
27
|
-
const isFunctional = metrics.withoutSkillScore !== undefined;
|
|
28
|
-
Logger.write(`\n${chalk.bold('LATEST EVALUATION RESULTS')}\n`);
|
|
29
|
-
Logger.write(`Timestamp: ${new Date(timestamp).toLocaleString()}\n`);
|
|
30
|
-
Logger.write(`Skill: ${skill_name}\n`);
|
|
31
|
-
Logger.write(`Agent: ${agent}\n`);
|
|
32
|
-
Logger.write(`Type: ${isFunctional ? 'Functional' : 'Trigger'}\n`);
|
|
33
|
-
Logger.write(`──────────────────────────────────────────────────\n`);
|
|
34
|
-
if (isFunctional) {
|
|
35
|
-
renderFunctionalTable(report);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
renderTriggerTable(report);
|
|
39
|
-
}
|
|
40
|
-
Logger.write('\n');
|
|
41
|
-
const runDir = path.join(runsDir, latestRun);
|
|
42
|
-
reporter.generate(report, runDir);
|
|
43
|
-
}
|
package/dist/commands/view.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.viewCommand = viewCommand;
|
|
4
|
-
const logger_1 = require("../utils/logger");
|
|
5
|
-
async function viewCommand(options) {
|
|
6
|
-
const ease = parseInt(options.ease || '0', 10);
|
|
7
|
-
const speed = parseInt(options.speed || '0', 10);
|
|
8
|
-
const accuracy = parseInt(options.accuracy || '0', 10);
|
|
9
|
-
const drawStars = (score) => {
|
|
10
|
-
const stars = '★'.repeat(score);
|
|
11
|
-
const empty = '☆'.repeat(5 - score);
|
|
12
|
-
return `[ ${stars}${empty} ]`;
|
|
13
|
-
};
|
|
14
|
-
logger_1.Logger.info('\n┌──────────────────────────────────────────┐');
|
|
15
|
-
logger_1.Logger.info('│ NUEVA VISTA DE CALIFICACIÓN │');
|
|
16
|
-
logger_1.Logger.info('├──────────────────────────────────────────┤');
|
|
17
|
-
logger_1.Logger.info(`│ Facilidad de uso: ${drawStars(ease)} │`);
|
|
18
|
-
logger_1.Logger.info(`│ Velocidad: ${drawStars(speed)} │`);
|
|
19
|
-
logger_1.Logger.info(`│ Precisión: ${drawStars(accuracy)} │`);
|
|
20
|
-
logger_1.Logger.info('├──────────────────────────────────────────┤');
|
|
21
|
-
if (options.comment) {
|
|
22
|
-
logger_1.Logger.info(`│ Comentario: │`);
|
|
23
|
-
// Basic wrapping for the comment
|
|
24
|
-
const comment = options.comment.substring(0, 38);
|
|
25
|
-
logger_1.Logger.info(`│ ${comment.padEnd(40)} │`);
|
|
26
|
-
}
|
|
27
|
-
logger_1.Logger.info('├──────────────────────────────────────────┤');
|
|
28
|
-
logger_1.Logger.info('│ ¡Gracias por calificar tu experiencia! │');
|
|
29
|
-
logger_1.Logger.info('└──────────────────────────────────────────┘\n');
|
|
30
|
-
}
|