@m4trix/evals 0.1.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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/cli-simple.cjs +135 -9
- package/dist/cli-simple.cjs.map +1 -1
- package/dist/cli-simple.js +115 -9
- package/dist/cli-simple.js.map +1 -1
- package/dist/cli.cjs +132 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +113 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +141 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +118 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pascal Lohscheidt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
[](https://dl.circleci.com/status-badge/redirect/gh/Pascal-Lohscheidt/build-ai/tree/main)
|
|
2
|
+
[](https://www.npmjs.com/package/@m4trix/evals)
|
|
3
|
+
[](https://www.npmjs.com/package/@m4trix/evals)
|
|
4
|
+
|
|
5
|
+
# @m4trix/evals
|
|
6
|
+
|
|
7
|
+
`@m4trix/evals` helps you define datasets, test cases, and evaluators for repeatable AI evaluation runs.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
From the repository root:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm install
|
|
15
|
+
pnpm run evals:build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run the bundled example project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd examples/evals-example
|
|
22
|
+
pnpm run eval:run
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Generate a dataset case file from the example:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm run eval:generate
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Set Up Your First Eval
|
|
32
|
+
|
|
33
|
+
Create files under your project (for example, `src/evals/`) with these suffixes:
|
|
34
|
+
|
|
35
|
+
- `*.dataset.ts`
|
|
36
|
+
- `*.evaluator.ts`
|
|
37
|
+
- `*.test-case.ts`
|
|
38
|
+
|
|
39
|
+
Optional: create `m4trix-eval.config.ts` at your project root to customize discovery and output paths.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { defineConfig, type ConfigType } from '@m4trix/evals';
|
|
43
|
+
|
|
44
|
+
export default defineConfig((): ConfigType => ({
|
|
45
|
+
discovery: {
|
|
46
|
+
rootDir: 'src/evals',
|
|
47
|
+
datasetFilePatterns: ['.dataset.ts'],
|
|
48
|
+
evaluatorFilePatterns: ['.evaluator.ts'],
|
|
49
|
+
testCaseFilePatterns: ['.test-case.ts'],
|
|
50
|
+
excludeDirectories: ['node_modules', 'dist'],
|
|
51
|
+
},
|
|
52
|
+
artifactDirectory: 'src/evals/.eval-results',
|
|
53
|
+
}));
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 1) Dataset
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Dataset } from '@m4trix/evals';
|
|
60
|
+
|
|
61
|
+
export const myDataset = Dataset.define({
|
|
62
|
+
name: 'My Dataset',
|
|
63
|
+
includedTags: ['demo'],
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2) Evaluator
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Evaluator, S, latencyMetric, percentScore, tokenCountMetric } from '@m4trix/evals';
|
|
71
|
+
|
|
72
|
+
const inputSchema = S.Struct({ prompt: S.String });
|
|
73
|
+
|
|
74
|
+
export const myEvaluator = Evaluator.define({
|
|
75
|
+
name: 'My Evaluator',
|
|
76
|
+
inputSchema,
|
|
77
|
+
outputSchema: S.Unknown,
|
|
78
|
+
scoreSchema: S.Struct({ scores: S.Array(S.Unknown) }),
|
|
79
|
+
}).evaluate(async (input) => {
|
|
80
|
+
const start = Date.now();
|
|
81
|
+
const latencyMs = Date.now() - start;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
scores: [
|
|
85
|
+
percentScore.make({ value: 85 }, { definePassed: (d) => d.value >= 50 }),
|
|
86
|
+
],
|
|
87
|
+
metrics: [
|
|
88
|
+
tokenCountMetric.make({
|
|
89
|
+
input: input.prompt.length,
|
|
90
|
+
output: input.prompt.length,
|
|
91
|
+
inputCached: 0,
|
|
92
|
+
outputCached: 0,
|
|
93
|
+
}),
|
|
94
|
+
latencyMetric.make({ ms: latencyMs }),
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3) Test Case
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { TestCase, S } from '@m4trix/evals';
|
|
104
|
+
|
|
105
|
+
export const myTestCase = TestCase.describe({
|
|
106
|
+
name: 'my test case',
|
|
107
|
+
tags: ['demo'],
|
|
108
|
+
inputSchema: S.Struct({ prompt: S.String }),
|
|
109
|
+
input: { prompt: 'Hello from my first eval' },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4) Run
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
eval-agents-simple run --dataset "My Dataset" --evaluator "My Evaluator"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
You can also use patterns:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
eval-agents-simple run --dataset "*My*" --evaluator "*My*"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## CLI Commands
|
|
126
|
+
|
|
127
|
+
- `eval-agents`: interactive CLI
|
|
128
|
+
- `eval-agents-simple run --dataset "<name or pattern>" --evaluator "<name or pattern>"`
|
|
129
|
+
- `eval-agents-simple generate --dataset "<dataset name>"`
|
|
130
|
+
|
|
131
|
+
## Default Discovery and Artifacts
|
|
132
|
+
|
|
133
|
+
By default, the runner uses `process.cwd()` as discovery root and scans for:
|
|
134
|
+
|
|
135
|
+
- Datasets: `.dataset.ts`, `.dataset.tsx`, `.dataset.js`, `.dataset.mjs`
|
|
136
|
+
- Evaluators: `.evaluator.ts`, `.evaluator.tsx`, `.evaluator.js`, `.evaluator.mjs`
|
|
137
|
+
- Test cases: `.test-case.ts`, `.test-case.tsx`, `.test-case.js`, `.test-case.mjs`
|
|
138
|
+
|
|
139
|
+
Results are written to `.eval-results`.
|
|
140
|
+
|
|
141
|
+
## Config File
|
|
142
|
+
|
|
143
|
+
When present, `m4trix-eval.config.ts` is loaded automatically from `process.cwd()`.
|
|
144
|
+
|
|
145
|
+
- Config API: `defineConfig(() => ConfigType)`
|
|
146
|
+
- Supported exports: default object, or default function that returns config
|
|
147
|
+
- Discovery keys:
|
|
148
|
+
- `datasetFilePatterns` (or `datasetSuffixes`)
|
|
149
|
+
- `evaluatorFilePatterns` (or `evaluatorSuffixes`)
|
|
150
|
+
- `testCaseFilePatterns` (or `testCaseSuffixes`)
|
|
151
|
+
- `rootDir`, `excludeDirectories`
|
|
152
|
+
|
|
153
|
+
Precedence is:
|
|
154
|
+
|
|
155
|
+
1. built-in defaults
|
|
156
|
+
2. `m4trix-eval.config.ts`
|
|
157
|
+
3. explicit `createRunner({...})` overrides
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/dist/cli-simple.cjs
CHANGED
|
@@ -3,11 +3,33 @@
|
|
|
3
3
|
|
|
4
4
|
var crypto = require('crypto');
|
|
5
5
|
var effect = require('effect');
|
|
6
|
-
var
|
|
6
|
+
var fs = require('fs');
|
|
7
7
|
var path = require('path');
|
|
8
|
+
var jitiModule = require('jiti');
|
|
9
|
+
var promises = require('fs/promises');
|
|
8
10
|
var url = require('url');
|
|
9
11
|
|
|
10
12
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
function _interopNamespace(e) {
|
|
14
|
+
if (e && e.__esModule) return e;
|
|
15
|
+
var n = Object.create(null);
|
|
16
|
+
if (e) {
|
|
17
|
+
Object.keys(e).forEach(function (k) {
|
|
18
|
+
if (k !== 'default') {
|
|
19
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
20
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () { return e[k]; }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
n.default = e;
|
|
28
|
+
return Object.freeze(n);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var jitiModule__namespace = /*#__PURE__*/_interopNamespace(jitiModule);
|
|
32
|
+
|
|
11
33
|
// src/runner/config.ts
|
|
12
34
|
var defaultRunnerConfig = {
|
|
13
35
|
discovery: {
|
|
@@ -29,10 +51,104 @@ var defaultRunnerConfig = {
|
|
|
29
51
|
},
|
|
30
52
|
artifactDirectory: ".eval-results"
|
|
31
53
|
};
|
|
54
|
+
function toRunnerConfigOverrides(config) {
|
|
55
|
+
if (!config) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const rawDiscovery = config.discovery;
|
|
59
|
+
const discovery = {};
|
|
60
|
+
if (rawDiscovery?.rootDir !== void 0) {
|
|
61
|
+
discovery.rootDir = rawDiscovery.rootDir;
|
|
62
|
+
}
|
|
63
|
+
if (rawDiscovery?.datasetFilePatterns !== void 0) {
|
|
64
|
+
discovery.datasetSuffixes = rawDiscovery.datasetFilePatterns;
|
|
65
|
+
} else if (rawDiscovery?.datasetSuffixes !== void 0) {
|
|
66
|
+
discovery.datasetSuffixes = rawDiscovery.datasetSuffixes;
|
|
67
|
+
}
|
|
68
|
+
if (rawDiscovery?.evaluatorFilePatterns !== void 0) {
|
|
69
|
+
discovery.evaluatorSuffixes = rawDiscovery.evaluatorFilePatterns;
|
|
70
|
+
} else if (rawDiscovery?.evaluatorSuffixes !== void 0) {
|
|
71
|
+
discovery.evaluatorSuffixes = rawDiscovery.evaluatorSuffixes;
|
|
72
|
+
}
|
|
73
|
+
if (rawDiscovery?.testCaseFilePatterns !== void 0) {
|
|
74
|
+
discovery.testCaseSuffixes = rawDiscovery.testCaseFilePatterns;
|
|
75
|
+
} else if (rawDiscovery?.testCaseSuffixes !== void 0) {
|
|
76
|
+
discovery.testCaseSuffixes = rawDiscovery.testCaseSuffixes;
|
|
77
|
+
}
|
|
78
|
+
if (rawDiscovery?.excludeDirectories !== void 0) {
|
|
79
|
+
discovery.excludeDirectories = rawDiscovery.excludeDirectories;
|
|
80
|
+
}
|
|
81
|
+
const overrides = {};
|
|
82
|
+
if (config.artifactDirectory !== void 0) {
|
|
83
|
+
overrides.artifactDirectory = config.artifactDirectory;
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(discovery).length > 0) {
|
|
86
|
+
overrides.discovery = discovery;
|
|
87
|
+
}
|
|
88
|
+
return overrides;
|
|
89
|
+
}
|
|
32
90
|
function withRunnerConfig(overrides) {
|
|
33
|
-
{
|
|
91
|
+
if (!overrides) {
|
|
34
92
|
return defaultRunnerConfig;
|
|
35
93
|
}
|
|
94
|
+
const discovery = overrides.discovery ? {
|
|
95
|
+
...defaultRunnerConfig.discovery,
|
|
96
|
+
...overrides.discovery
|
|
97
|
+
} : defaultRunnerConfig.discovery;
|
|
98
|
+
return {
|
|
99
|
+
...defaultRunnerConfig,
|
|
100
|
+
...overrides,
|
|
101
|
+
discovery
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
var CONFIG_FILE_NAME = "m4trix-eval.config.ts";
|
|
105
|
+
var cachedLoader;
|
|
106
|
+
function getJitiLoader() {
|
|
107
|
+
if (cachedLoader) {
|
|
108
|
+
return cachedLoader;
|
|
109
|
+
}
|
|
110
|
+
const createJiti2 = jitiModule__namespace.createJiti ?? jitiModule__namespace.default;
|
|
111
|
+
if (typeof createJiti2 !== "function") {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"Failed to initialize jiti for m4trix eval config loading."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
cachedLoader = createJiti2((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('out.js', document.baseURI).href)), {
|
|
117
|
+
interopDefault: true,
|
|
118
|
+
moduleCache: true
|
|
119
|
+
});
|
|
120
|
+
return cachedLoader;
|
|
121
|
+
}
|
|
122
|
+
function resolveConfigModuleExport(loadedModule) {
|
|
123
|
+
if (loadedModule && typeof loadedModule === "object" && "default" in loadedModule) {
|
|
124
|
+
return loadedModule.default;
|
|
125
|
+
}
|
|
126
|
+
return loadedModule;
|
|
127
|
+
}
|
|
128
|
+
function resolveConfigValue(value) {
|
|
129
|
+
if (value === void 0 || value === null) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
if (typeof value === "function") {
|
|
133
|
+
return value();
|
|
134
|
+
}
|
|
135
|
+
if (typeof value !== "object") {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Invalid m4trix eval config export. Expected an object or defineConfig(() => config)."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
function loadRunnerConfigFile(cwd = process.cwd()) {
|
|
143
|
+
const configPath = path.resolve(cwd, CONFIG_FILE_NAME);
|
|
144
|
+
if (!fs.existsSync(configPath)) {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
const loader = getJitiLoader();
|
|
148
|
+
const loaded = loader(configPath);
|
|
149
|
+
const exportedValue = resolveConfigModuleExport(loaded);
|
|
150
|
+
const config = resolveConfigValue(exportedValue);
|
|
151
|
+
return toRunnerConfigOverrides(config);
|
|
36
152
|
}
|
|
37
153
|
var jitiLoader;
|
|
38
154
|
function toId(prefix, filePath, name) {
|
|
@@ -85,12 +201,12 @@ function hasOneSuffix(filePath, suffixes) {
|
|
|
85
201
|
async function loadModuleExports(filePath) {
|
|
86
202
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
|
|
87
203
|
if (!jitiLoader) {
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
if (!
|
|
204
|
+
const jitiModule2 = await import('jiti');
|
|
205
|
+
const createJiti2 = jitiModule2.createJiti ?? jitiModule2.default;
|
|
206
|
+
if (!createJiti2) {
|
|
91
207
|
throw new Error("Failed to initialize jiti TypeScript loader");
|
|
92
208
|
}
|
|
93
|
-
jitiLoader =
|
|
209
|
+
jitiLoader = createJiti2((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('out.js', document.baseURI).href)), {
|
|
94
210
|
interopDefault: true,
|
|
95
211
|
moduleCache: true
|
|
96
212
|
});
|
|
@@ -498,8 +614,18 @@ function createNameMatcher(pattern) {
|
|
|
498
614
|
}
|
|
499
615
|
return (value) => value.toLowerCase() === normalizedPattern.toLowerCase();
|
|
500
616
|
}
|
|
617
|
+
function mergeRunnerOverrides(base, next) {
|
|
618
|
+
if (!base) {
|
|
619
|
+
return next;
|
|
620
|
+
}
|
|
621
|
+
{
|
|
622
|
+
return base;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
501
625
|
function createRunner(overrides) {
|
|
502
|
-
|
|
626
|
+
const fileOverrides = loadRunnerConfigFile();
|
|
627
|
+
const merged = mergeRunnerOverrides(fileOverrides, overrides);
|
|
628
|
+
return new EffectRunner(withRunnerConfig(merged));
|
|
503
629
|
}
|
|
504
630
|
var EffectRunner = class {
|
|
505
631
|
constructor(config) {
|
|
@@ -902,7 +1028,7 @@ async function runSimpleEvalCommand(runner, datasetName, evaluatorPattern) {
|
|
|
902
1028
|
);
|
|
903
1029
|
}
|
|
904
1030
|
let spinnerTimer;
|
|
905
|
-
const done = new Promise((
|
|
1031
|
+
const done = new Promise((resolve4) => {
|
|
906
1032
|
const unsubscribe = runner.subscribeRunEvents((event) => {
|
|
907
1033
|
if (event.type === "TestCaseProgress") {
|
|
908
1034
|
completedCount = event.completedTestCases;
|
|
@@ -952,7 +1078,7 @@ async function runSimpleEvalCommand(runner, datasetName, evaluatorPattern) {
|
|
|
952
1078
|
runFinished = true;
|
|
953
1079
|
clearLine();
|
|
954
1080
|
unsubscribe();
|
|
955
|
-
|
|
1081
|
+
resolve4(event);
|
|
956
1082
|
}
|
|
957
1083
|
});
|
|
958
1084
|
});
|