promptfoo 0.2.2 → 0.4.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 +55 -17
- package/dist/evaluator.d.ts +1 -1
- package/dist/evaluator.d.ts.map +1 -1
- package/dist/evaluator.js +213 -150
- package/dist/evaluator.js.map +1 -1
- package/dist/main.js +51 -10
- package/dist/main.js.map +1 -1
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +21 -0
- package/dist/prompts.js.map +1 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +11 -5
- package/dist/providers.js.map +1 -1
- package/dist/tableOutput.html +5 -8
- package/dist/types.d.ts +27 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +50 -1
- package/dist/util.js.map +1 -1
- package/dist/web/client/assets/index-710f1308.css +1 -0
- package/dist/web/client/assets/index-900b20c0.js +172 -0
- package/dist/web/client/favicon.ico +0 -0
- package/dist/web/client/index.html +15 -0
- package/dist/web/client/logo.svg +30 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +74 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +14 -3
- package/src/evaluator.ts +271 -174
- package/src/main.ts +52 -10
- package/src/prompts.ts +20 -0
- package/src/providers.ts +33 -15
- package/src/tableOutput.html +5 -8
- package/src/types.ts +32 -7
- package/src/util.ts +60 -1
- package/src/web/client/.eslintrc.cjs +14 -0
- package/src/web/client/index.html +13 -0
- package/src/web/client/package.json +37 -0
- package/src/web/client/public/favicon.ico +0 -0
- package/src/web/client/public/logo.svg +30 -0
- package/src/web/client/src/App.css +0 -0
- package/src/web/client/src/App.tsx +43 -0
- package/src/web/client/src/Logo.css +13 -0
- package/src/web/client/src/Logo.tsx +11 -0
- package/src/web/client/src/NavBar.css +3 -0
- package/src/web/client/src/NavBar.tsx +11 -0
- package/src/web/client/src/ResultsTable.css +133 -0
- package/src/web/client/src/ResultsTable.tsx +261 -0
- package/src/web/client/src/ResultsView.tsx +110 -0
- package/src/web/client/src/index.css +35 -0
- package/src/web/client/src/main.tsx +10 -0
- package/src/web/client/src/store.ts +13 -0
- package/src/web/client/src/types.ts +14 -0
- package/src/web/client/src/vite-env.d.ts +1 -0
- package/src/web/client/tsconfig.json +24 -0
- package/src/web/client/tsconfig.node.json +10 -0
- package/src/web/client/vite.config.ts +7 -0
- package/src/web/server.ts +96 -0
package/README.md
CHANGED
|
@@ -13,7 +13,18 @@ With promptfoo, you can:
|
|
|
13
13
|
- Use as a command line tool, or integrate into your workflow as a library
|
|
14
14
|
- Use OpenAI API models (built-in support), or integrate custom API providers for any LLM API
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
**» [View docs on website](https://promptfoo.dev/docs/intro) «**
|
|
17
|
+
|
|
18
|
+
promptfoo works by producing matrix views that allow you to quickly review prompt outputs across many inputs. The goal: tune prompts systematically across all relevant test cases, instead of testing prompts one-off.
|
|
19
|
+
|
|
20
|
+
Here's an example of a side-by-side comparison of multiple prompts and inputs. You can manually review outputs, or set up "expectations" that automatically flag bad outputs.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
It works on the command line too.
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Usage (command line & web viewer)
|
|
17
28
|
|
|
18
29
|
To get started, run the following command:
|
|
19
30
|
|
|
@@ -32,15 +43,22 @@ npx promptfoo eval
|
|
|
32
43
|
If you're looking to customize your usage, you have the full set of parameters at your disposal:
|
|
33
44
|
|
|
34
45
|
```bash
|
|
35
|
-
npx promptfoo eval -p <prompt_paths...> -o <output_path> -r <providers> [-v <vars_path>] [-j <max_concurrency] [-c <config_path>]
|
|
46
|
+
npx promptfoo eval -p <prompt_paths...> -o <output_path> -r <providers> [-v <vars_path>] [-j <max_concurrency] [-c <config_path>] [--grader <grading_provider>]
|
|
36
47
|
```
|
|
37
48
|
|
|
38
49
|
- `<prompt_paths...>`: Paths to prompt file(s)
|
|
39
50
|
- `<output_path>`: Path to output CSV, JSON, YAML, or HTML file. Defaults to terminal output
|
|
40
51
|
- `<providers>`: One or more of: `openai:<model_name>`, or filesystem path to custom API caller module
|
|
41
52
|
- `<vars_path>` (optional): Path to CSV, JSON, or YAML file with prompt variables
|
|
42
|
-
- `<max_concurrency>` (optional): Number of simultaneous API requests. Defaults to
|
|
53
|
+
- `<max_concurrency>` (optional): Number of simultaneous API requests. Defaults to 4
|
|
43
54
|
- `<config_path>` (optional): Path to configuration file
|
|
55
|
+
- `<grading_provider>`: A provider that handles the grading process, if you are using [LLM grading](#expected-outputs)
|
|
56
|
+
|
|
57
|
+
After running an eval, you may optionally use the `view` command to open the web viewer:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
npx promptfoo view
|
|
61
|
+
```
|
|
44
62
|
|
|
45
63
|
### Examples
|
|
46
64
|
|
|
@@ -52,8 +70,6 @@ In this example, we evaluate whether adding adjectives to the personality of an
|
|
|
52
70
|
npx promptfoo eval -p prompts.txt -v vars.csv -r openai:gpt-3.5-turbo
|
|
53
71
|
```
|
|
54
72
|
|
|
55
|
-

|
|
56
|
-
|
|
57
73
|
<!--
|
|
58
74
|
<img width="1362" alt="Side-by-side evaluation of LLM prompt quality, terminal output" src="https://user-images.githubusercontent.com/310310/235329207-e8c22459-5f51-4fee-9714-1b602ac3d7ca.png">
|
|
59
75
|
|
|
@@ -64,7 +80,9 @@ This command will evaluate the prompts in `prompts.txt`, substituing the variabl
|
|
|
64
80
|
|
|
65
81
|
Have a look at the setup and full output [here](https://github.com/typpo/promptfoo/tree/main/examples/assistant-cli).
|
|
66
82
|
|
|
67
|
-
You can
|
|
83
|
+
You can also output a nice [spreadsheet](https://docs.google.com/spreadsheets/d/1nanoj3_TniWrDl1Sj-qYqIMD6jwm5FBy15xPFdUTsmI/edit?usp=sharing), [JSON](https://github.com/typpo/promptfoo/blob/main/examples/simple-cli/output.json), YAML, or an HTML file:
|
|
84
|
+
|
|
85
|
+

|
|
68
86
|
|
|
69
87
|
#### Model quality
|
|
70
88
|
|
|
@@ -164,9 +182,9 @@ You can use [Nunjucks](https://mozilla.github.io/nunjucks/) templating syntax to
|
|
|
164
182
|
Example of a single prompt file with multiple prompts (`prompts.txt`):
|
|
165
183
|
|
|
166
184
|
```
|
|
167
|
-
Translate the following text to French: "{{text}}"
|
|
185
|
+
Translate the following text to French: "{{name}}: {{text}}"
|
|
168
186
|
---
|
|
169
|
-
Translate the following text to German: "{{text}}"
|
|
187
|
+
Translate the following text to German: "{{name}}: {{text}}"
|
|
170
188
|
```
|
|
171
189
|
|
|
172
190
|
Example of multiple prompt files:
|
|
@@ -174,13 +192,13 @@ Example of multiple prompt files:
|
|
|
174
192
|
- `prompt1.txt`:
|
|
175
193
|
|
|
176
194
|
```
|
|
177
|
-
Translate the following text to French: "{{text}}"
|
|
195
|
+
Translate the following text to French: "{{name}}: {{text}}"
|
|
178
196
|
```
|
|
179
197
|
|
|
180
198
|
- `prompt2.txt`:
|
|
181
199
|
|
|
182
200
|
```
|
|
183
|
-
Translate the following text to German: "{{text}}"
|
|
201
|
+
Translate the following text to German: "{{name}}: {{text}}"
|
|
184
202
|
```
|
|
185
203
|
|
|
186
204
|
### Vars File
|
|
@@ -192,24 +210,27 @@ Vars are substituted by [Nunjucks](https://mozilla.github.io/nunjucks/) templati
|
|
|
192
210
|
Example of a vars file (`vars.csv`):
|
|
193
211
|
|
|
194
212
|
```
|
|
195
|
-
text
|
|
196
|
-
"Hello, world!"
|
|
197
|
-
"Goodbye, everyone!"
|
|
213
|
+
"name","text"
|
|
214
|
+
"Bob","Hello, world!"
|
|
215
|
+
"Joe","Goodbye, everyone!"
|
|
198
216
|
```
|
|
199
217
|
|
|
200
218
|
Example of a vars file (`vars.json`):
|
|
201
219
|
|
|
202
220
|
```json
|
|
203
|
-
[
|
|
221
|
+
[
|
|
222
|
+
{ "name": "Bob", "text": "Hello, world!" },
|
|
223
|
+
{ "name": "Joe", "text": "Goodbye, everyone!" }
|
|
224
|
+
]
|
|
204
225
|
```
|
|
205
226
|
|
|
206
|
-
### Expected
|
|
227
|
+
### Expected Outputs
|
|
207
228
|
|
|
208
|
-
You can specify an expected value for each test case to evaluate the success or failure of the model's output. To do this, add a special field called `__expected` in the `vars` file. The `__expected` field supports
|
|
229
|
+
You can specify an expected value for each test case to evaluate the success or failure of the model's output. To do this, add a special field called `__expected` in the `vars` file. The `__expected` field supports these types of value comparisons:
|
|
209
230
|
|
|
210
231
|
1. If the expected value starts with `eval:`, it will evaluate the contents as the body of a JavaScript function defined like: `function(output) { <eval> }`. The function should return a boolean value, where `true` indicates success and `false` indicates failure.
|
|
211
232
|
|
|
212
|
-
2. If the expected value starts with `grade:`, it will
|
|
233
|
+
2. If the expected value starts with `grade:`, it will ask an LLM to evaluate whether the output meets the condition. For example: `grade: don't mention being an AI`. This option requires a provider name to be supplied to promptfoo via the `--grader` argument: `promptfoo --grader openai:gpt-4 ...`.
|
|
213
234
|
|
|
214
235
|
3. Otherwise, it attempts an exact string match comparison between the expected value and the model's output.
|
|
215
236
|
|
|
@@ -219,6 +240,7 @@ Example of a vars file with the `__expected` field (`vars.csv`):
|
|
|
219
240
|
text,__expected
|
|
220
241
|
"Hello, world!","Bonjour le monde"
|
|
221
242
|
"Goodbye, everyone!","eval:return output.includes('Au revoir');"
|
|
243
|
+
"I am a pineapple","grade:doesn't reference any fruits besides pineapple"
|
|
222
244
|
```
|
|
223
245
|
|
|
224
246
|
Example of a vars file with the `__expected` field (`vars.json`):
|
|
@@ -227,6 +249,7 @@ Example of a vars file with the `__expected` field (`vars.json`):
|
|
|
227
249
|
[
|
|
228
250
|
{ "text": "Hello, world!", "__expected": "Bonjour le monde" },
|
|
229
251
|
{ "text": "Goodbye, everyone!", "__expected": "eval:output.includes('Au revoir');" }
|
|
252
|
+
{ "text": "I am a pineapple", "__expected": "grade:doesn't reference any fruits besides pineapple" }
|
|
230
253
|
]
|
|
231
254
|
```
|
|
232
255
|
|
|
@@ -273,6 +296,18 @@ npm install
|
|
|
273
296
|
npm link
|
|
274
297
|
```
|
|
275
298
|
|
|
299
|
+
4. Build:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm run build
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
5. Make the entrypoint executable:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
chmod +x dist/main.js
|
|
309
|
+
```
|
|
310
|
+
|
|
276
311
|
### Example
|
|
277
312
|
|
|
278
313
|
```bash
|
|
@@ -297,6 +332,9 @@ Other OpenAI-related environment variables are supported:
|
|
|
297
332
|
|
|
298
333
|
- `OPENAI_TEMPERATURE` - temperature model parameter, defaults to 0
|
|
299
334
|
- `OPENAI_MAX_TOKENS` - max_tokens model parameter, defaults to 1024
|
|
335
|
+
- `OPENAI_STOP` - stopwords in JSON format, defaults to []
|
|
336
|
+
- `OPENAI_API_HOST` - override the hostname for the API request. Useful for proxies like Helicone.
|
|
337
|
+
- `REQUEST_TIMEOUT_MS` - maximum request time, in milliseconds (defaults to 60000)
|
|
300
338
|
|
|
301
339
|
The OpenAI provider supports the following model formats:
|
|
302
340
|
|
package/dist/evaluator.d.ts
CHANGED
package/dist/evaluator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../src/evaluator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../src/evaluator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,eAAe,EAGf,eAAe,EAIhB,MAAM,YAAY,CAAC;AAoSpB,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,4BAGhD"}
|
package/dist/evaluator.js
CHANGED
|
@@ -1,175 +1,238 @@
|
|
|
1
1
|
import async from 'async';
|
|
2
2
|
import nunjucks from 'nunjucks';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { DEFAULT_GRADING_PROMPT } from './prompts.js';
|
|
4
|
+
const DEFAULT_MAX_CONCURRENCY = 4;
|
|
5
|
+
class Evaluator {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.stats = {
|
|
9
|
+
successes: 0,
|
|
10
|
+
failures: 0,
|
|
11
|
+
tokenUsage: {
|
|
12
|
+
total: 0,
|
|
13
|
+
prompt: 0,
|
|
14
|
+
completion: 0,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
async gradeOutput(expected, output) {
|
|
19
|
+
const { grading } = this.options;
|
|
20
|
+
if (!grading) {
|
|
21
|
+
throw new Error('Cannot grade output without grading config. Specify --grader option or grading config.');
|
|
22
|
+
}
|
|
23
|
+
const prompt = nunjucks.renderString(grading.prompt || DEFAULT_GRADING_PROMPT, {
|
|
24
|
+
content: output,
|
|
25
|
+
rubric: expected,
|
|
26
|
+
});
|
|
27
|
+
const resp = await grading.provider.callApi(prompt);
|
|
28
|
+
if (resp.error || !resp.output) {
|
|
29
|
+
return {
|
|
30
|
+
pass: false,
|
|
31
|
+
reason: resp.error || 'No output',
|
|
32
|
+
tokensUsed: {
|
|
33
|
+
total: resp.tokenUsage?.total || 0,
|
|
34
|
+
prompt: resp.tokenUsage?.prompt || 0,
|
|
35
|
+
completion: resp.tokenUsage?.completion || 0,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(resp.output);
|
|
41
|
+
parsed.tokensUsed = {
|
|
42
|
+
total: resp.tokenUsage?.total || 0,
|
|
43
|
+
prompt: resp.tokenUsage?.prompt || 0,
|
|
44
|
+
completion: resp.tokenUsage?.completion || 0,
|
|
45
|
+
};
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return {
|
|
50
|
+
pass: false,
|
|
51
|
+
reason: `Output is not valid JSON: ${resp.output}`,
|
|
52
|
+
tokensUsed: {
|
|
53
|
+
total: resp.tokenUsage?.total || 0,
|
|
54
|
+
prompt: resp.tokenUsage?.prompt || 0,
|
|
55
|
+
completion: resp.tokenUsage?.completion || 0,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
16
59
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const promptDisplay = includeProviderId ? `[${provider.id()}] ${prompt}` : prompt;
|
|
23
|
-
const setup = {
|
|
24
|
-
prompt: {
|
|
25
|
-
raw: renderedPrompt,
|
|
26
|
-
display: promptDisplay,
|
|
27
|
-
},
|
|
28
|
-
vars,
|
|
29
|
-
};
|
|
30
|
-
try {
|
|
31
|
-
const response = await provider.callApi(renderedPrompt);
|
|
32
|
-
const ret = {
|
|
33
|
-
...setup,
|
|
34
|
-
response,
|
|
35
|
-
success: false,
|
|
36
|
-
};
|
|
37
|
-
if (response.error) {
|
|
38
|
-
ret.error = response.error;
|
|
60
|
+
async checkExpectedValue(expected, output) {
|
|
61
|
+
if (expected.startsWith('eval:')) {
|
|
62
|
+
const evalBody = expected.slice(5);
|
|
63
|
+
const evalFunction = new Function('output', `return ${evalBody}`);
|
|
64
|
+
return { pass: evalFunction(output) };
|
|
39
65
|
}
|
|
40
|
-
else if (
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
ret.success = matchesExpected;
|
|
66
|
+
else if (expected.startsWith('grade:')) {
|
|
67
|
+
const gradingResult = await this.gradeOutput(expected.slice(6), output);
|
|
68
|
+
return {
|
|
69
|
+
pass: gradingResult.pass,
|
|
70
|
+
reason: gradingResult.reason,
|
|
71
|
+
};
|
|
48
72
|
}
|
|
49
73
|
else {
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
const pass = expected === output;
|
|
75
|
+
return {
|
|
76
|
+
pass,
|
|
77
|
+
reason: pass ? undefined : `Expected: ${expected}, Output: ${output}`,
|
|
78
|
+
};
|
|
52
79
|
}
|
|
53
|
-
return ret;
|
|
54
80
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
async runEval({ provider, prompt, vars, includeProviderId, }) {
|
|
82
|
+
vars = vars || {};
|
|
83
|
+
const renderedPrompt = nunjucks.renderString(prompt, vars);
|
|
84
|
+
// Note that we're using original prompt, not renderedPrompt
|
|
85
|
+
const promptDisplay = includeProviderId ? `[${provider.id()}] ${prompt}` : prompt;
|
|
86
|
+
const setup = {
|
|
87
|
+
prompt: {
|
|
88
|
+
raw: renderedPrompt,
|
|
89
|
+
display: promptDisplay,
|
|
90
|
+
},
|
|
91
|
+
vars,
|
|
60
92
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
try {
|
|
94
|
+
const response = await provider.callApi(renderedPrompt);
|
|
95
|
+
const ret = {
|
|
96
|
+
...setup,
|
|
97
|
+
response,
|
|
98
|
+
success: false,
|
|
99
|
+
};
|
|
100
|
+
if (response.error) {
|
|
101
|
+
ret.error = response.error;
|
|
102
|
+
}
|
|
103
|
+
else if (response.output) {
|
|
104
|
+
const checkResult = vars.__expected
|
|
105
|
+
? await this.checkExpectedValue(vars.__expected, response.output)
|
|
106
|
+
: { pass: true };
|
|
107
|
+
if (!checkResult.pass) {
|
|
108
|
+
ret.error = checkResult.reason || `Expected: ${vars.__expected}`;
|
|
109
|
+
}
|
|
110
|
+
ret.success = checkResult.pass;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
ret.success = false;
|
|
114
|
+
ret.error = 'No output';
|
|
115
|
+
}
|
|
116
|
+
// Update token usage stats
|
|
117
|
+
this.stats.tokenUsage.total += response.tokenUsage?.total || 0;
|
|
118
|
+
this.stats.tokenUsage.prompt += response.tokenUsage?.prompt || 0;
|
|
119
|
+
this.stats.tokenUsage.completion += response.tokenUsage?.completion || 0;
|
|
120
|
+
if (ret.success) {
|
|
121
|
+
this.stats.successes++;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.stats.failures++;
|
|
125
|
+
}
|
|
126
|
+
return ret;
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return {
|
|
130
|
+
...setup,
|
|
131
|
+
error: String(err),
|
|
132
|
+
success: false,
|
|
133
|
+
};
|
|
72
134
|
}
|
|
73
135
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
delete ret.__expected;
|
|
78
|
-
return ret;
|
|
79
|
-
});
|
|
80
|
-
const isTest = vars[0].__expected;
|
|
81
|
-
const table = [
|
|
82
|
-
isTest
|
|
83
|
-
? [
|
|
84
|
-
'RESULT',
|
|
85
|
-
[...prompts.map((p) => p.display), ...Object.keys(varsWithExpectedKeyRemoved[0])],
|
|
86
|
-
].flat()
|
|
87
|
-
: [...prompts.map((p) => p.display), ...Object.keys(varsWithExpectedKeyRemoved[0])],
|
|
88
|
-
];
|
|
89
|
-
const stats = {
|
|
90
|
-
successes: 0,
|
|
91
|
-
failures: 0,
|
|
92
|
-
tokenUsage: {
|
|
93
|
-
total: 0,
|
|
94
|
-
prompt: 0,
|
|
95
|
-
completion: 0,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
let progressbar;
|
|
99
|
-
if (options.showProgressBar) {
|
|
100
|
-
const totalNumRuns = options.prompts.length * options.providers.length * (options.vars?.length || 1);
|
|
101
|
-
const cliProgress = await import('cli-progress');
|
|
102
|
-
progressbar = new cliProgress.SingleBar({
|
|
103
|
-
format: 'Eval: [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} | {provider} "{prompt}" {vars}',
|
|
104
|
-
}, cliProgress.Presets.shades_classic);
|
|
105
|
-
progressbar.start(totalNumRuns, 0, {
|
|
106
|
-
provider: '',
|
|
107
|
-
prompt: '',
|
|
108
|
-
vars: '',
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
const runEvalOptions = [];
|
|
112
|
-
for (const row of vars) {
|
|
136
|
+
async evaluate() {
|
|
137
|
+
const options = this.options;
|
|
138
|
+
const prompts = [];
|
|
113
139
|
for (const promptContent of options.prompts) {
|
|
114
140
|
for (const provider of options.providers) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
includeProviderId: options.providers.length > 1,
|
|
141
|
+
const display = options.providers.length > 1 ? `[${provider.id()}] ${promptContent}` : promptContent;
|
|
142
|
+
prompts.push({
|
|
143
|
+
raw: promptContent,
|
|
144
|
+
display,
|
|
120
145
|
});
|
|
121
146
|
}
|
|
122
147
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
const vars = options.vars && options.vars.length > 0 ? options.vars : [{}];
|
|
149
|
+
const varsWithExpectedKeyRemoved = vars.map((v) => {
|
|
150
|
+
const ret = { ...v };
|
|
151
|
+
delete ret.__expected;
|
|
152
|
+
return ret;
|
|
153
|
+
});
|
|
154
|
+
const isTest = vars[0].__expected;
|
|
155
|
+
const table = {
|
|
156
|
+
head: {
|
|
157
|
+
prompts: prompts.map((p) => p.display),
|
|
158
|
+
vars: Object.keys(varsWithExpectedKeyRemoved[0]),
|
|
159
|
+
},
|
|
160
|
+
body: [],
|
|
161
|
+
};
|
|
162
|
+
let progressbar;
|
|
163
|
+
if (options.showProgressBar) {
|
|
164
|
+
const totalNumRuns = options.prompts.length * options.providers.length * (options.vars?.length || 1);
|
|
165
|
+
const cliProgress = await import('cli-progress');
|
|
166
|
+
progressbar = new cliProgress.SingleBar({
|
|
167
|
+
format: 'Eval: [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} | {provider} "{prompt}" {vars}',
|
|
168
|
+
}, cliProgress.Presets.shades_classic);
|
|
169
|
+
progressbar.start(totalNumRuns, 0, {
|
|
170
|
+
provider: '',
|
|
171
|
+
prompt: '',
|
|
172
|
+
vars: '',
|
|
173
|
+
});
|
|
130
174
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
175
|
+
const runEvalOptions = [];
|
|
176
|
+
let rowIndex = 0;
|
|
177
|
+
for (const row of vars) {
|
|
178
|
+
let colIndex = 0;
|
|
179
|
+
for (const promptContent of options.prompts) {
|
|
180
|
+
for (const provider of options.providers) {
|
|
181
|
+
runEvalOptions.push({
|
|
182
|
+
provider,
|
|
183
|
+
prompt: promptContent,
|
|
184
|
+
vars: row,
|
|
185
|
+
includeProviderId: options.providers.length > 1,
|
|
186
|
+
rowIndex,
|
|
187
|
+
colIndex,
|
|
188
|
+
});
|
|
189
|
+
colIndex++;
|
|
190
|
+
}
|
|
137
191
|
}
|
|
138
|
-
|
|
139
|
-
stats.tokenUsage.prompt += row.response?.tokenUsage?.prompt || 0;
|
|
140
|
-
stats.tokenUsage.completion += row.response?.tokenUsage?.completion || 0;
|
|
192
|
+
rowIndex++;
|
|
141
193
|
}
|
|
194
|
+
const results = [];
|
|
195
|
+
await async.forEachOfLimit(runEvalOptions, options.maxConcurrency || DEFAULT_MAX_CONCURRENCY, async (options, index) => {
|
|
196
|
+
const row = await this.runEval(options);
|
|
197
|
+
results.push(row);
|
|
198
|
+
if (progressbar) {
|
|
199
|
+
progressbar.increment({
|
|
200
|
+
provider: options.provider.id(),
|
|
201
|
+
prompt: options.prompt.slice(0, 10),
|
|
202
|
+
vars: Object.entries(options.vars || {})
|
|
203
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
204
|
+
.join(' ')
|
|
205
|
+
.slice(0, 10),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// Bookkeeping for table
|
|
209
|
+
if (typeof index !== 'number') {
|
|
210
|
+
throw new Error('Expected index to be a number');
|
|
211
|
+
}
|
|
212
|
+
const resultText = isTest
|
|
213
|
+
? row.success
|
|
214
|
+
? `[PASS] ${row.response?.output || row.error || ''}`
|
|
215
|
+
: `[FAIL] ${row.error}\n---\n${row.response?.output || row.error || ''}`
|
|
216
|
+
: row.response?.output || row.error || '';
|
|
217
|
+
// TODO(ian): Provide full context in table cells, and have the caller
|
|
218
|
+
// construct the table contents itself.
|
|
219
|
+
const { rowIndex, colIndex } = options;
|
|
220
|
+
if (!table.body[rowIndex]) {
|
|
221
|
+
table.body[rowIndex] = {
|
|
222
|
+
outputs: [],
|
|
223
|
+
vars: Object.values(options.vars || {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
table.body[rowIndex].outputs[colIndex] = resultText;
|
|
227
|
+
});
|
|
142
228
|
if (progressbar) {
|
|
143
|
-
progressbar.
|
|
144
|
-
provider: options.provider.id(),
|
|
145
|
-
prompt: options.prompt.slice(0, 10),
|
|
146
|
-
vars: Object.entries(options.vars || {})
|
|
147
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
148
|
-
.join(' ')
|
|
149
|
-
.slice(0, 10),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Bookkeeping for table
|
|
153
|
-
if (typeof index !== 'number') {
|
|
154
|
-
throw new Error('Expected index to be a number');
|
|
229
|
+
progressbar.stop();
|
|
155
230
|
}
|
|
156
|
-
|
|
157
|
-
combinedOutputs[combinedOutputIndex].push(row.response?.output || row.error || '');
|
|
158
|
-
});
|
|
159
|
-
if (progressbar) {
|
|
160
|
-
progressbar.stop();
|
|
161
|
-
}
|
|
162
|
-
// TODO(ian): Display errors in table UI.
|
|
163
|
-
if (isTest) {
|
|
164
|
-
table.push(...combinedOutputs.map((output, index) => [
|
|
165
|
-
results[index].success ? 'PASS' : `FAIL: ${results[index].error}`,
|
|
166
|
-
...output,
|
|
167
|
-
...Object.values(varsWithExpectedKeyRemoved[index]),
|
|
168
|
-
]));
|
|
231
|
+
return { version: 1, results, stats: this.stats, table };
|
|
169
232
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return
|
|
233
|
+
}
|
|
234
|
+
export function evaluate(options) {
|
|
235
|
+
const ev = new Evaluator(options);
|
|
236
|
+
return ev.evaluate();
|
|
174
237
|
}
|
|
175
238
|
//# sourceMappingURL=evaluator.js.map
|
package/dist/evaluator.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../src/evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../src/evaluator.ts"],"names":[],"mappings":"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"}
|