promptfoo 0.103.3 → 0.103.5
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 +10 -1
- package/dist/package.json +13 -13
- package/dist/src/app/assets/index-BR1tgrAf.css +1 -0
- package/dist/src/app/assets/{index-XXoiz61D.js → index-CmPQAxfe.js} +276 -276
- package/dist/src/app/assets/{index.es-DTKpmNcZ.js → index.es-DfqJ7zdu.js} +1 -1
- package/dist/src/app/assets/{sync-ClbHj3jr.js → sync-C-aW1Mpw.js} +1 -1
- package/dist/src/app/index.html +2 -2
- package/dist/src/assertions/index.d.ts +3 -2
- package/dist/src/assertions/index.d.ts.map +1 -1
- package/dist/src/assertions/index.js +21 -6
- package/dist/src/assertions/index.js.map +1 -1
- package/dist/src/assertions/utils.d.ts +6 -2
- package/dist/src/assertions/utils.d.ts.map +1 -1
- package/dist/src/commands/eval/filterErrorTests.d.ts +5 -0
- package/dist/src/commands/eval/filterErrorTests.d.ts.map +1 -0
- package/dist/src/commands/eval/filterErrorTests.js +19 -0
- package/dist/src/commands/eval/filterErrorTests.js.map +1 -0
- package/dist/src/commands/eval/filterTests.d.ts +1 -0
- package/dist/src/commands/eval/filterTests.d.ts.map +1 -1
- package/dist/src/commands/eval/filterTests.js +4 -0
- package/dist/src/commands/eval/filterTests.js.map +1 -1
- package/dist/src/commands/eval.d.ts.map +1 -1
- package/dist/src/commands/eval.js +1 -0
- package/dist/src/commands/eval.js.map +1 -1
- package/dist/src/database/tables.d.ts +51 -12
- package/dist/src/database/tables.d.ts.map +1 -1
- package/dist/src/envars.d.ts +1 -0
- package/dist/src/envars.d.ts.map +1 -1
- package/dist/src/envars.js.map +1 -1
- package/dist/src/evaluator.d.ts.map +1 -1
- package/dist/src/evaluator.js +1 -0
- package/dist/src/evaluator.js.map +1 -1
- package/dist/src/fetch.d.ts.map +1 -1
- package/dist/src/fetch.js +20 -3
- package/dist/src/fetch.js.map +1 -1
- package/dist/src/models/evalResult.d.ts.map +1 -1
- package/dist/src/models/evalResult.js +9 -1
- package/dist/src/models/evalResult.js.map +1 -1
- package/dist/src/providers/browser.js +1 -1
- package/dist/src/providers/browser.js.map +1 -1
- package/dist/src/providers/defaults.d.ts +1 -0
- package/dist/src/providers/defaults.d.ts.map +1 -1
- package/dist/src/providers/defaults.js +11 -0
- package/dist/src/providers/defaults.js.map +1 -1
- package/dist/src/providers/http.d.ts.map +1 -1
- package/dist/src/providers/http.js +39 -63
- package/dist/src/providers/http.js.map +1 -1
- package/dist/src/providers/llama.d.ts.map +1 -1
- package/dist/src/providers/llama.js +8 -1
- package/dist/src/providers/llama.js.map +1 -1
- package/dist/src/providers/openai.d.ts.map +1 -1
- package/dist/src/providers/openai.js +6 -13
- package/dist/src/providers/openai.js.map +1 -1
- package/dist/src/providers/watsonx.d.ts.map +1 -1
- package/dist/src/providers/watsonx.js +9 -0
- package/dist/src/providers/watsonx.js.map +1 -1
- package/dist/src/providers.d.ts.map +1 -1
- package/dist/src/providers.js +15 -0
- package/dist/src/providers.js.map +1 -1
- package/dist/src/redteam/commands/generate.d.ts.map +1 -1
- package/dist/src/redteam/commands/generate.js +4 -0
- package/dist/src/redteam/commands/generate.js.map +1 -1
- package/dist/src/redteam/constants.d.ts +4 -2
- package/dist/src/redteam/constants.d.ts.map +1 -1
- package/dist/src/redteam/constants.js +11 -7
- package/dist/src/redteam/constants.js.map +1 -1
- package/dist/src/redteam/plugins/base.d.ts.map +1 -1
- package/dist/src/redteam/plugins/base.js +3 -0
- package/dist/src/redteam/plugins/base.js.map +1 -1
- package/dist/src/redteam/plugins/cyberseceval.d.ts.map +1 -1
- package/dist/src/redteam/plugins/cyberseceval.js +13 -3
- package/dist/src/redteam/plugins/cyberseceval.js.map +1 -1
- package/dist/src/redteam/providers/crescendo/index.d.ts +1 -0
- package/dist/src/redteam/providers/crescendo/index.d.ts.map +1 -1
- package/dist/src/redteam/providers/crescendo/index.js +58 -3
- package/dist/src/redteam/providers/crescendo/index.js.map +1 -1
- package/dist/src/redteam/providers/iterative.d.ts.map +1 -1
- package/dist/src/redteam/providers/iterative.js +59 -5
- package/dist/src/redteam/providers/iterative.js.map +1 -1
- package/dist/src/redteam/providers/iterativeImage.d.ts +6 -2
- package/dist/src/redteam/providers/iterativeImage.d.ts.map +1 -1
- package/dist/src/redteam/providers/iterativeImage.js +322 -131
- package/dist/src/redteam/providers/iterativeImage.js.map +1 -1
- package/dist/src/redteam/providers/iterativeTree.d.ts +37 -26
- package/dist/src/redteam/providers/iterativeTree.d.ts.map +1 -1
- package/dist/src/redteam/providers/iterativeTree.js +193 -85
- package/dist/src/redteam/providers/iterativeTree.js.map +1 -1
- package/dist/src/redteam/shared.d.ts.map +1 -1
- package/dist/src/redteam/shared.js +4 -1
- package/dist/src/redteam/shared.js.map +1 -1
- package/dist/src/server/routes/providers.js +11 -6
- package/dist/src/server/routes/providers.js.map +1 -1
- package/dist/src/types/env.d.ts +3 -0
- package/dist/src/types/env.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +1376 -351
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +4 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/providers.d.ts +22 -0
- package/dist/src/types/providers.d.ts.map +1 -1
- package/dist/src/types/providers.js.map +1 -1
- package/dist/src/util/config/manage.d.ts +1 -1
- package/dist/src/util/config/manage.d.ts.map +1 -1
- package/dist/src/util/config/manage.js.map +1 -1
- package/dist/src/util/convertEvalResultsToTable.d.ts.map +1 -1
- package/dist/src/util/convertEvalResultsToTable.js +14 -0
- package/dist/src/util/convertEvalResultsToTable.js.map +1 -1
- package/dist/src/util/index.d.ts +12 -4
- package/dist/src/util/index.d.ts.map +1 -1
- package/dist/src/validators/providers.d.ts +71 -2
- package/dist/src/validators/providers.d.ts.map +1 -1
- package/dist/src/validators/providers.js +3 -0
- package/dist/src/validators/providers.js.map +1 -1
- package/dist/src/validators/redteam.d.ts +24 -0
- package/dist/src/validators/redteam.d.ts.map +1 -1
- package/dist/test/assertions/index.test.js +26 -475
- package/dist/test/assertions/index.test.js.map +1 -1
- package/dist/test/assertions/javascript.test.d.ts +2 -0
- package/dist/test/assertions/javascript.test.d.ts.map +1 -0
- package/dist/test/assertions/javascript.test.js +679 -0
- package/dist/test/assertions/javascript.test.js.map +1 -0
- package/dist/test/assertions/python.test.d.ts +2 -0
- package/dist/test/assertions/python.test.d.ts.map +1 -0
- package/dist/test/assertions/python.test.js +377 -0
- package/dist/test/assertions/python.test.js.map +1 -0
- package/dist/test/cache.test.js +297 -100
- package/dist/test/cache.test.js.map +1 -1
- package/dist/test/commands/eval/filterErrorTests.test.d.ts +2 -0
- package/dist/test/commands/eval/filterErrorTests.test.d.ts.map +1 -0
- package/dist/test/commands/eval/filterErrorTests.test.js +110 -0
- package/dist/test/commands/eval/filterErrorTests.test.js.map +1 -0
- package/dist/test/evaluator.test.js +10 -0
- package/dist/test/evaluator.test.js.map +1 -1
- package/dist/test/factories/evalFactory.d.ts +39 -8
- package/dist/test/factories/evalFactory.d.ts.map +1 -1
- package/dist/test/fetch.test.js +147 -19
- package/dist/test/fetch.test.js.map +1 -1
- package/dist/test/models/eval.test.js +12 -0
- package/dist/test/models/eval.test.js.map +1 -1
- package/dist/test/providers/defaults.test.d.ts +2 -0
- package/dist/test/providers/defaults.test.d.ts.map +1 -0
- package/dist/test/providers/defaults.test.js +77 -0
- package/dist/test/providers/defaults.test.js.map +1 -0
- package/dist/test/providers/http.test.js +65 -9
- package/dist/test/providers/http.test.js.map +1 -1
- package/dist/test/providers/index.test.js +6 -3
- package/dist/test/providers/index.test.js.map +1 -1
- package/dist/test/providers/mistral.test.js +28 -19
- package/dist/test/providers/mistral.test.js.map +1 -1
- package/dist/test/providers/watsonx.test.js +58 -0
- package/dist/test/providers/watsonx.test.js.map +1 -1
- package/dist/test/redteam/providers/iterativeTree.test.js +329 -98
- package/dist/test/redteam/providers/iterativeTree.test.js.map +1 -1
- package/dist/test/server/providers.test.js +4 -4
- package/dist/test/server/providers.test.js.map +1 -1
- package/dist/test/util/config/main.test.js +3 -0
- package/dist/test/util/config/main.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/dist/src/app/assets/index-DdUNCsxz.css +0 -1
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const assertions_1 = require("../../src/assertions");
|
|
38
|
+
const esm_1 = require("../../src/esm");
|
|
39
|
+
const openai_1 = require("../../src/providers/openai");
|
|
40
|
+
const packageParser_1 = require("../../src/providers/packageParser");
|
|
41
|
+
jest.mock('../../src/redteam/remoteGeneration', () => ({
|
|
42
|
+
shouldGenerateRemote: jest.fn().mockReturnValue(false),
|
|
43
|
+
}));
|
|
44
|
+
jest.mock('proxy-agent', () => ({
|
|
45
|
+
ProxyAgent: jest.fn().mockImplementation(() => ({})),
|
|
46
|
+
}));
|
|
47
|
+
jest.mock('node:module', () => {
|
|
48
|
+
const mockRequire = {
|
|
49
|
+
resolve: jest.fn(),
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
createRequire: jest.fn().mockReturnValue(mockRequire),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
jest.mock('../../src/fetch', () => {
|
|
56
|
+
const actual = jest.requireActual('../../src/fetch');
|
|
57
|
+
return {
|
|
58
|
+
...actual,
|
|
59
|
+
fetchWithRetries: jest.fn(actual.fetchWithRetries),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
jest.mock('glob', () => ({
|
|
63
|
+
globSync: jest.fn(),
|
|
64
|
+
}));
|
|
65
|
+
jest.mock('fs', () => ({
|
|
66
|
+
readFileSync: jest.fn(),
|
|
67
|
+
promises: {
|
|
68
|
+
readFile: jest.fn(),
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
jest.mock('../../src/esm', () => ({
|
|
72
|
+
importModule: jest.fn().mockImplementation((path, functionName) => {
|
|
73
|
+
// Make sure both parameters are captured in the mock call
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
}),
|
|
76
|
+
__esModule: true,
|
|
77
|
+
}));
|
|
78
|
+
jest.mock('../../src/database', () => ({
|
|
79
|
+
getDb: jest.fn(),
|
|
80
|
+
}));
|
|
81
|
+
jest.mock('path', () => {
|
|
82
|
+
const actualPath = jest.requireActual('path');
|
|
83
|
+
return {
|
|
84
|
+
...actualPath,
|
|
85
|
+
resolve: jest.fn((basePath, filePath) => actualPath.join(basePath, filePath)),
|
|
86
|
+
extname: jest.fn((filePath) => actualPath.extname(filePath)),
|
|
87
|
+
join: actualPath.join,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
jest.mock('../../src/cliState', () => ({
|
|
91
|
+
basePath: '/base/path',
|
|
92
|
+
}));
|
|
93
|
+
jest.mock('../../src/matchers', () => {
|
|
94
|
+
const actual = jest.requireActual('../../src/matchers');
|
|
95
|
+
return {
|
|
96
|
+
...actual,
|
|
97
|
+
matchesContextRelevance: jest
|
|
98
|
+
.fn()
|
|
99
|
+
.mockResolvedValue({ pass: true, score: 1, reason: 'Mocked reason' }),
|
|
100
|
+
matchesContextFaithfulness: jest
|
|
101
|
+
.fn()
|
|
102
|
+
.mockResolvedValue({ pass: true, score: 1, reason: 'Mocked reason' }),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
// Add this mock for packageParser
|
|
106
|
+
jest.mock('../../src/providers/packageParser', () => {
|
|
107
|
+
const mockIsPackagePath = jest.fn();
|
|
108
|
+
const mockLoadFromPackage = jest.fn();
|
|
109
|
+
return {
|
|
110
|
+
isPackagePath: mockIsPackagePath,
|
|
111
|
+
loadFromPackage: mockLoadFromPackage,
|
|
112
|
+
__esModule: true, // This is important for proper mocking
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
const javascriptStringAssertion = {
|
|
116
|
+
type: 'javascript',
|
|
117
|
+
value: 'output === "Expected output"',
|
|
118
|
+
};
|
|
119
|
+
const javascriptMultilineStringAssertion = {
|
|
120
|
+
type: 'javascript',
|
|
121
|
+
value: `
|
|
122
|
+
if (output === "Expected output") {
|
|
123
|
+
return {
|
|
124
|
+
pass: true,
|
|
125
|
+
score: 0.5,
|
|
126
|
+
reason: 'Assertion passed',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
pass: false,
|
|
131
|
+
score: 0,
|
|
132
|
+
reason: 'Assertion failed',
|
|
133
|
+
};`,
|
|
134
|
+
};
|
|
135
|
+
const javascriptStringAssertionWithNumber = {
|
|
136
|
+
type: 'javascript',
|
|
137
|
+
value: 'output.length * 10',
|
|
138
|
+
};
|
|
139
|
+
const javascriptBooleanAssertionWithConfig = {
|
|
140
|
+
type: 'javascript',
|
|
141
|
+
value: 'output.length <= context.config.maximumOutputSize',
|
|
142
|
+
config: {
|
|
143
|
+
maximumOutputSize: 20,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const javascriptStringAssertionWithNumberAndThreshold = {
|
|
147
|
+
type: 'javascript',
|
|
148
|
+
value: 'output.length * 10',
|
|
149
|
+
threshold: 0.5,
|
|
150
|
+
};
|
|
151
|
+
const javascriptFunctionAssertion = {
|
|
152
|
+
type: 'javascript',
|
|
153
|
+
value: async (output) => ({
|
|
154
|
+
pass: true,
|
|
155
|
+
score: 0.5,
|
|
156
|
+
reason: 'Assertion passed',
|
|
157
|
+
assertion: null,
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
const javascriptFunctionFailAssertion = {
|
|
161
|
+
type: 'javascript',
|
|
162
|
+
value: async (output) => ({
|
|
163
|
+
pass: false,
|
|
164
|
+
score: 0.5,
|
|
165
|
+
reason: 'Assertion failed',
|
|
166
|
+
assertion: null,
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
describe('JavaScript file references', () => {
|
|
170
|
+
beforeEach(() => {
|
|
171
|
+
jest.clearAllMocks();
|
|
172
|
+
// Reset all mocks before each test
|
|
173
|
+
jest.mocked(esm_1.importModule).mockReset();
|
|
174
|
+
jest.mocked(path.resolve).mockReset();
|
|
175
|
+
jest.mocked(packageParser_1.isPackagePath).mockReset();
|
|
176
|
+
jest.mocked(packageParser_1.loadFromPackage).mockReset();
|
|
177
|
+
});
|
|
178
|
+
it('should handle JavaScript file reference with function name', async () => {
|
|
179
|
+
const assertion = {
|
|
180
|
+
type: 'javascript',
|
|
181
|
+
value: 'file:///path/to/assert.js:customFunction',
|
|
182
|
+
};
|
|
183
|
+
const mockFn = jest.fn((output) => true);
|
|
184
|
+
jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
|
|
185
|
+
jest.mocked(path.extname).mockReturnValue('.js');
|
|
186
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
|
|
187
|
+
// Mock importModule to return the mock function
|
|
188
|
+
jest.mocked(esm_1.importModule).mockImplementationOnce((path, functionName) => {
|
|
189
|
+
return Promise.resolve({
|
|
190
|
+
customFunction: mockFn,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
const output = 'Expected output';
|
|
194
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
195
|
+
const providerResponse = { output };
|
|
196
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
197
|
+
prompt: 'Some prompt',
|
|
198
|
+
provider,
|
|
199
|
+
assertion,
|
|
200
|
+
test: {},
|
|
201
|
+
providerResponse,
|
|
202
|
+
});
|
|
203
|
+
// Verify the mock was called with both parameters
|
|
204
|
+
expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', 'customFunction');
|
|
205
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
206
|
+
prompt: 'Some prompt',
|
|
207
|
+
vars: {},
|
|
208
|
+
test: {},
|
|
209
|
+
provider,
|
|
210
|
+
providerResponse,
|
|
211
|
+
});
|
|
212
|
+
expect(result).toMatchObject({
|
|
213
|
+
pass: true,
|
|
214
|
+
reason: 'Assertion passed',
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
it('should handle default export when no function name specified', async () => {
|
|
218
|
+
const assertion = {
|
|
219
|
+
type: 'javascript',
|
|
220
|
+
value: 'file:///path/to/assert.js',
|
|
221
|
+
};
|
|
222
|
+
const mockFn = jest.fn((output) => true);
|
|
223
|
+
jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
|
|
224
|
+
jest.mocked(path.extname).mockReturnValue('.js');
|
|
225
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
|
|
226
|
+
// Mock importModule to return the mock function
|
|
227
|
+
jest.mocked(esm_1.importModule).mockImplementationOnce((path, functionName) => {
|
|
228
|
+
return Promise.resolve(mockFn);
|
|
229
|
+
});
|
|
230
|
+
const output = 'Expected output';
|
|
231
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
232
|
+
const providerResponse = { output };
|
|
233
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
234
|
+
prompt: 'Some prompt',
|
|
235
|
+
provider,
|
|
236
|
+
assertion,
|
|
237
|
+
test: {},
|
|
238
|
+
providerResponse,
|
|
239
|
+
});
|
|
240
|
+
expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', undefined);
|
|
241
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
242
|
+
prompt: 'Some prompt',
|
|
243
|
+
vars: {},
|
|
244
|
+
test: {},
|
|
245
|
+
provider,
|
|
246
|
+
providerResponse,
|
|
247
|
+
});
|
|
248
|
+
expect(result).toMatchObject({
|
|
249
|
+
pass: true,
|
|
250
|
+
reason: 'Assertion passed',
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
it('should handle default export object with function', async () => {
|
|
254
|
+
const assertion = {
|
|
255
|
+
type: 'javascript',
|
|
256
|
+
value: 'file:///path/to/assert.js',
|
|
257
|
+
};
|
|
258
|
+
const mockFn = jest.fn((output) => true);
|
|
259
|
+
jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
|
|
260
|
+
jest.mocked(path.extname).mockReturnValue('.js');
|
|
261
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
|
|
262
|
+
// Mock importModule to handle both parameters
|
|
263
|
+
const mockImportModule = jest.mocked(esm_1.importModule);
|
|
264
|
+
mockImportModule.mockImplementationOnce((path, functionName) => {
|
|
265
|
+
// Return the mock function in a default export object
|
|
266
|
+
return Promise.resolve({ default: mockFn });
|
|
267
|
+
});
|
|
268
|
+
const output = 'Expected output';
|
|
269
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
270
|
+
const providerResponse = { output };
|
|
271
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
272
|
+
prompt: 'Some prompt',
|
|
273
|
+
provider,
|
|
274
|
+
assertion,
|
|
275
|
+
test: {},
|
|
276
|
+
providerResponse,
|
|
277
|
+
});
|
|
278
|
+
expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', undefined);
|
|
279
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
280
|
+
prompt: 'Some prompt',
|
|
281
|
+
vars: {},
|
|
282
|
+
test: {},
|
|
283
|
+
provider,
|
|
284
|
+
providerResponse,
|
|
285
|
+
});
|
|
286
|
+
expect(result).toMatchObject({
|
|
287
|
+
pass: true,
|
|
288
|
+
reason: 'Assertion passed',
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
it('should pass when the javascript assertion passes', async () => {
|
|
292
|
+
const output = 'Expected output';
|
|
293
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
294
|
+
prompt: 'Some prompt',
|
|
295
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
296
|
+
assertion: javascriptStringAssertion,
|
|
297
|
+
test: {},
|
|
298
|
+
providerResponse: { output },
|
|
299
|
+
});
|
|
300
|
+
expect(result).toMatchObject({
|
|
301
|
+
pass: true,
|
|
302
|
+
reason: 'Assertion passed',
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
it('should pass a score through when the javascript returns a number', async () => {
|
|
306
|
+
const output = 'Expected output';
|
|
307
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
308
|
+
prompt: 'Some prompt',
|
|
309
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
310
|
+
assertion: javascriptStringAssertionWithNumber,
|
|
311
|
+
test: {},
|
|
312
|
+
providerResponse: { output },
|
|
313
|
+
});
|
|
314
|
+
expect(result).toMatchObject({
|
|
315
|
+
pass: true,
|
|
316
|
+
score: output.length * 10,
|
|
317
|
+
reason: 'Assertion passed',
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
it('should pass when javascript returns an output string that is smaller than the maximum size threshold', async () => {
|
|
321
|
+
const output = 'Expected output';
|
|
322
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
323
|
+
prompt: 'Some prompt',
|
|
324
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
325
|
+
assertion: javascriptBooleanAssertionWithConfig,
|
|
326
|
+
test: {},
|
|
327
|
+
providerResponse: { output },
|
|
328
|
+
});
|
|
329
|
+
expect(result).toMatchObject({
|
|
330
|
+
pass: true,
|
|
331
|
+
score: 1.0,
|
|
332
|
+
reason: 'Assertion passed',
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
it('should fail when javascript returns an output string that is larger than the maximum size threshold', async () => {
|
|
336
|
+
const output = 'Expected output with some extra characters';
|
|
337
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
338
|
+
prompt: 'Some prompt',
|
|
339
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
340
|
+
assertion: javascriptBooleanAssertionWithConfig,
|
|
341
|
+
test: {},
|
|
342
|
+
providerResponse: { output },
|
|
343
|
+
});
|
|
344
|
+
expect(result).toMatchObject({
|
|
345
|
+
pass: false,
|
|
346
|
+
score: 0,
|
|
347
|
+
reason: expect.stringContaining('Custom function returned false'),
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
it('should pass when javascript returns a number above threshold', async () => {
|
|
351
|
+
const output = 'Expected output';
|
|
352
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
353
|
+
prompt: 'Some prompt',
|
|
354
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
355
|
+
assertion: javascriptStringAssertionWithNumberAndThreshold,
|
|
356
|
+
test: {},
|
|
357
|
+
providerResponse: { output },
|
|
358
|
+
});
|
|
359
|
+
expect(result).toMatchObject({
|
|
360
|
+
pass: true,
|
|
361
|
+
score: output.length * 10,
|
|
362
|
+
reason: 'Assertion passed',
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
it('should fail when javascript returns a number below threshold', async () => {
|
|
366
|
+
const output = '';
|
|
367
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
368
|
+
prompt: 'Some prompt',
|
|
369
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
370
|
+
assertion: javascriptStringAssertionWithNumberAndThreshold,
|
|
371
|
+
test: {},
|
|
372
|
+
providerResponse: { output },
|
|
373
|
+
});
|
|
374
|
+
expect(result).toMatchObject({
|
|
375
|
+
pass: false,
|
|
376
|
+
score: output.length * 10,
|
|
377
|
+
reason: expect.stringContaining('Custom function returned false'),
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
it('should set score when javascript returns false', async () => {
|
|
381
|
+
const output = 'Test output';
|
|
382
|
+
const assertion = {
|
|
383
|
+
type: 'javascript',
|
|
384
|
+
value: 'output.length < 1',
|
|
385
|
+
};
|
|
386
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
387
|
+
prompt: 'Some prompt',
|
|
388
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
389
|
+
assertion,
|
|
390
|
+
test: {},
|
|
391
|
+
providerResponse: { output },
|
|
392
|
+
});
|
|
393
|
+
expect(result).toMatchObject({
|
|
394
|
+
pass: false,
|
|
395
|
+
score: 0,
|
|
396
|
+
reason: expect.stringContaining('Custom function returned false'),
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
it('should fail when the javascript assertion fails', async () => {
|
|
400
|
+
const output = 'Different output';
|
|
401
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
402
|
+
prompt: 'Some prompt',
|
|
403
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
404
|
+
assertion: javascriptStringAssertion,
|
|
405
|
+
test: {},
|
|
406
|
+
providerResponse: { output },
|
|
407
|
+
});
|
|
408
|
+
expect(result).toMatchObject({
|
|
409
|
+
pass: false,
|
|
410
|
+
reason: 'Custom function returned false\noutput === "Expected output"',
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
it('should pass when javascript function assertion passes - with vars', async () => {
|
|
414
|
+
const output = 'Expected output';
|
|
415
|
+
const javascriptStringAssertionWithVars = {
|
|
416
|
+
type: 'javascript',
|
|
417
|
+
value: 'output === "Expected output" && context.vars.foo === "bar"',
|
|
418
|
+
};
|
|
419
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
420
|
+
prompt: 'Some prompt',
|
|
421
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
422
|
+
assertion: javascriptStringAssertionWithVars,
|
|
423
|
+
test: { vars: { foo: 'bar' } },
|
|
424
|
+
providerResponse: { output },
|
|
425
|
+
});
|
|
426
|
+
expect(result).toMatchObject({
|
|
427
|
+
pass: true,
|
|
428
|
+
reason: 'Assertion passed',
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
it('should fail when the javascript does not match vars', async () => {
|
|
432
|
+
const output = 'Expected output';
|
|
433
|
+
const javascriptStringAssertionWithVars = {
|
|
434
|
+
type: 'javascript',
|
|
435
|
+
value: 'output === "Expected output" && context.vars.foo === "something else"',
|
|
436
|
+
};
|
|
437
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
438
|
+
prompt: 'Some prompt',
|
|
439
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
440
|
+
assertion: javascriptStringAssertionWithVars,
|
|
441
|
+
test: { vars: { foo: 'bar' } },
|
|
442
|
+
providerResponse: { output },
|
|
443
|
+
});
|
|
444
|
+
expect(result).toMatchObject({
|
|
445
|
+
pass: false,
|
|
446
|
+
reason: 'Custom function returned false\noutput === "Expected output" && context.vars.foo === "something else"',
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
it('should pass when the function returns pass', async () => {
|
|
450
|
+
const output = 'Expected output';
|
|
451
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
452
|
+
prompt: 'Some prompt',
|
|
453
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
454
|
+
assertion: javascriptFunctionAssertion,
|
|
455
|
+
test: {},
|
|
456
|
+
providerResponse: { output },
|
|
457
|
+
});
|
|
458
|
+
expect(result).toMatchObject({
|
|
459
|
+
pass: true,
|
|
460
|
+
score: 0.5,
|
|
461
|
+
reason: 'Assertion passed',
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
it('should fail when the function returns fail', async () => {
|
|
465
|
+
const output = 'Expected output';
|
|
466
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
467
|
+
prompt: 'Some prompt',
|
|
468
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
469
|
+
assertion: javascriptFunctionFailAssertion,
|
|
470
|
+
test: {},
|
|
471
|
+
providerResponse: { output },
|
|
472
|
+
});
|
|
473
|
+
expect(result).toMatchObject({
|
|
474
|
+
pass: false,
|
|
475
|
+
score: 0.5,
|
|
476
|
+
reason: 'Assertion failed',
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
it('should pass when the multiline javascript assertion passes', async () => {
|
|
480
|
+
const output = 'Expected output';
|
|
481
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
482
|
+
prompt: 'Some prompt',
|
|
483
|
+
assertion: javascriptMultilineStringAssertion,
|
|
484
|
+
test: {},
|
|
485
|
+
providerResponse: { output },
|
|
486
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
487
|
+
});
|
|
488
|
+
expect(result).toMatchObject({
|
|
489
|
+
pass: true,
|
|
490
|
+
reason: 'Assertion passed',
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
it('should pass when the multiline javascript assertion fails', async () => {
|
|
494
|
+
const output = 'Not the expected output';
|
|
495
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
496
|
+
prompt: 'Some prompt',
|
|
497
|
+
assertion: javascriptMultilineStringAssertion,
|
|
498
|
+
test: {},
|
|
499
|
+
providerResponse: { output },
|
|
500
|
+
provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
|
|
501
|
+
});
|
|
502
|
+
expect(result).toMatchObject({
|
|
503
|
+
pass: false,
|
|
504
|
+
reason: 'Assertion failed',
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
it.each([
|
|
508
|
+
[
|
|
509
|
+
'boolean',
|
|
510
|
+
jest.fn((output) => output === 'Expected output'),
|
|
511
|
+
true,
|
|
512
|
+
'Assertion passed',
|
|
513
|
+
],
|
|
514
|
+
['number', jest.fn((output) => output.length), true, 'Assertion passed'],
|
|
515
|
+
[
|
|
516
|
+
'GradingResult',
|
|
517
|
+
jest.fn((output) => ({ pass: true, score: 1, reason: 'Custom reason' })),
|
|
518
|
+
true,
|
|
519
|
+
'Custom reason',
|
|
520
|
+
],
|
|
521
|
+
[
|
|
522
|
+
'boolean',
|
|
523
|
+
jest.fn((output) => output !== 'Expected output'),
|
|
524
|
+
false,
|
|
525
|
+
'Custom function returned false',
|
|
526
|
+
],
|
|
527
|
+
['number', jest.fn((output) => 0), false, 'Custom function returned false'],
|
|
528
|
+
[
|
|
529
|
+
'GradingResult',
|
|
530
|
+
jest.fn((output) => ({ pass: false, score: 0.1, reason: 'Custom reason' })),
|
|
531
|
+
false,
|
|
532
|
+
'Custom reason',
|
|
533
|
+
],
|
|
534
|
+
[
|
|
535
|
+
'boolean Promise',
|
|
536
|
+
jest.fn((output) => Promise.resolve(true)),
|
|
537
|
+
true,
|
|
538
|
+
'Assertion passed',
|
|
539
|
+
],
|
|
540
|
+
])('should pass when the file:// assertion with .js file returns a %s', async (type, mockFn, expectedPass, expectedReason) => {
|
|
541
|
+
const output = 'Expected output';
|
|
542
|
+
// Mock path.resolve to return a valid path
|
|
543
|
+
jest.mocked(path.resolve).mockReturnValue('/mocked/path/to/assert.js');
|
|
544
|
+
jest.mocked(path.extname).mockReturnValue('.js');
|
|
545
|
+
// Mock isPackagePath to return false for file:// paths
|
|
546
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
|
|
547
|
+
// Mock importModule to handle both path and functionName
|
|
548
|
+
const mockImportModule = jest.mocked(esm_1.importModule);
|
|
549
|
+
mockImportModule.mockImplementation((path, functionName) => {
|
|
550
|
+
// Make sure both parameters are captured in the mock
|
|
551
|
+
mockImportModule.mock.calls.push([path, functionName]);
|
|
552
|
+
return Promise.resolve(mockFn);
|
|
553
|
+
});
|
|
554
|
+
const fileAssertion = {
|
|
555
|
+
type: 'javascript',
|
|
556
|
+
value: 'file:///path/to/assert.js',
|
|
557
|
+
};
|
|
558
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
559
|
+
const providerResponse = { output };
|
|
560
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
561
|
+
prompt: 'Some prompt',
|
|
562
|
+
provider,
|
|
563
|
+
assertion: fileAssertion,
|
|
564
|
+
test: {},
|
|
565
|
+
providerResponse,
|
|
566
|
+
});
|
|
567
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
568
|
+
prompt: 'Some prompt',
|
|
569
|
+
vars: {},
|
|
570
|
+
test: {},
|
|
571
|
+
provider,
|
|
572
|
+
providerResponse,
|
|
573
|
+
});
|
|
574
|
+
expect(result).toMatchObject({
|
|
575
|
+
pass: expectedPass,
|
|
576
|
+
reason: expect.stringContaining(expectedReason),
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
it.each([
|
|
580
|
+
[
|
|
581
|
+
'boolean',
|
|
582
|
+
jest.fn((output) => output === 'Expected output'),
|
|
583
|
+
true,
|
|
584
|
+
'Assertion passed',
|
|
585
|
+
],
|
|
586
|
+
['number', jest.fn((output) => output.length), true, 'Assertion passed'],
|
|
587
|
+
[
|
|
588
|
+
'GradingResult',
|
|
589
|
+
jest.fn((output) => ({ pass: true, score: 1, reason: 'Custom reason' })),
|
|
590
|
+
true,
|
|
591
|
+
'Custom reason',
|
|
592
|
+
],
|
|
593
|
+
[
|
|
594
|
+
'boolean',
|
|
595
|
+
jest.fn((output) => output !== 'Expected output'),
|
|
596
|
+
false,
|
|
597
|
+
'Custom function returned false',
|
|
598
|
+
],
|
|
599
|
+
['number', jest.fn((output) => 0), false, 'Custom function returned false'],
|
|
600
|
+
[
|
|
601
|
+
'GradingResult',
|
|
602
|
+
jest.fn((output) => ({ pass: false, score: 0.1, reason: 'Custom reason' })),
|
|
603
|
+
false,
|
|
604
|
+
'Custom reason',
|
|
605
|
+
],
|
|
606
|
+
[
|
|
607
|
+
'boolean Promise',
|
|
608
|
+
jest.fn((output) => Promise.resolve(true)),
|
|
609
|
+
true,
|
|
610
|
+
'Assertion passed',
|
|
611
|
+
],
|
|
612
|
+
])('should pass when assertion is a package path', async (type, mockFn, expectedPass, expectedReason) => {
|
|
613
|
+
const output = 'Expected output';
|
|
614
|
+
// Mock isPackagePath to return true for package paths
|
|
615
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(true);
|
|
616
|
+
// Mock loadFromPackage to return the mockFn
|
|
617
|
+
jest.mocked(packageParser_1.loadFromPackage).mockResolvedValue(mockFn);
|
|
618
|
+
const packageAssertion = {
|
|
619
|
+
type: 'javascript',
|
|
620
|
+
value: 'package:@promptfoo/fake:assertionFunction',
|
|
621
|
+
};
|
|
622
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
623
|
+
const providerResponse = { output };
|
|
624
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
625
|
+
prompt: 'Some prompt',
|
|
626
|
+
provider,
|
|
627
|
+
assertion: packageAssertion,
|
|
628
|
+
test: {},
|
|
629
|
+
providerResponse,
|
|
630
|
+
});
|
|
631
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
632
|
+
prompt: 'Some prompt',
|
|
633
|
+
vars: {},
|
|
634
|
+
test: {},
|
|
635
|
+
provider,
|
|
636
|
+
providerResponse,
|
|
637
|
+
});
|
|
638
|
+
expect(result).toMatchObject({
|
|
639
|
+
pass: expectedPass,
|
|
640
|
+
reason: expect.stringContaining(expectedReason),
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
it('should resolve js paths relative to the configuration file', async () => {
|
|
644
|
+
const output = 'Expected output';
|
|
645
|
+
const mockFn = jest.fn((output) => output === 'Expected output');
|
|
646
|
+
// Mock path.resolve to return a valid path
|
|
647
|
+
jest.mocked(path.resolve).mockReturnValue('/base/path/path/to/assert.js');
|
|
648
|
+
jest.mocked(path.extname).mockReturnValue('.js');
|
|
649
|
+
// Mock isPackagePath to return false
|
|
650
|
+
jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
|
|
651
|
+
// Mock importModule to return the mockFn
|
|
652
|
+
jest.mocked(esm_1.importModule).mockResolvedValue(mockFn);
|
|
653
|
+
const fileAssertion = {
|
|
654
|
+
type: 'javascript',
|
|
655
|
+
value: 'file://./path/to/assert.js',
|
|
656
|
+
};
|
|
657
|
+
const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
|
|
658
|
+
const providerResponse = { output };
|
|
659
|
+
const result = await (0, assertions_1.runAssertion)({
|
|
660
|
+
prompt: 'Some prompt',
|
|
661
|
+
provider,
|
|
662
|
+
assertion: fileAssertion,
|
|
663
|
+
test: {},
|
|
664
|
+
providerResponse,
|
|
665
|
+
});
|
|
666
|
+
expect(mockFn).toHaveBeenCalledWith(output, {
|
|
667
|
+
prompt: 'Some prompt',
|
|
668
|
+
vars: {},
|
|
669
|
+
test: {},
|
|
670
|
+
provider,
|
|
671
|
+
providerResponse,
|
|
672
|
+
});
|
|
673
|
+
expect(result).toMatchObject({
|
|
674
|
+
pass: true,
|
|
675
|
+
reason: 'Assertion passed',
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
//# sourceMappingURL=javascript.test.js.map
|