openai-api-mock 0.1.33 → 0.2.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 +106 -4
- package/dist/index.cjs +163 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +163 -7
- package/dist/index.js.map +1 -1
- package/index.d.ts +24 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -14,6 +14,16 @@ The module supports the following OpenAI API endpoints:
|
|
|
14
14
|
|
|
15
15
|
> This module is powering the sandbox mode for [Aipify](https://aipify.co).
|
|
16
16
|
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Usage](#usage)
|
|
21
|
+
- [Consistent Outputs for Testing](#consistent-outputs-for-testing)
|
|
22
|
+
- [Intercepted URLs](#intercepted-urls)
|
|
23
|
+
- [TypeScript Support](#typescript-support)
|
|
24
|
+
- [Dependencies](#dependencies)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
17
27
|
## Installation
|
|
18
28
|
|
|
19
29
|
You can install this module using npm as a dev dependency :
|
|
@@ -47,7 +57,9 @@ mockOpenAIResponse(true);
|
|
|
47
57
|
mockOpenAIResponse(false, {
|
|
48
58
|
includeErrors: true, // Simulate random API errors
|
|
49
59
|
latency: 1000, // Add 1 second delay to responses
|
|
50
|
-
logRequests: true
|
|
60
|
+
logRequests: true, // Log incoming requests to console
|
|
61
|
+
seed: 12345, // Seed for consistent/deterministic responses
|
|
62
|
+
useFixedResponses: true // Use predefined fixed response templates
|
|
51
63
|
});
|
|
52
64
|
```
|
|
53
65
|
|
|
@@ -57,6 +69,8 @@ The function accepts two parameters:
|
|
|
57
69
|
- `includeErrors` (boolean): When true, randomly simulates API errors
|
|
58
70
|
- `latency` (number): Adds artificial delay to responses in milliseconds
|
|
59
71
|
- `logRequests` (boolean): Logs incoming requests to console for debugging
|
|
72
|
+
- `seed` (number|string): Seed value for consistent/deterministic responses using faker.js
|
|
73
|
+
- `useFixedResponses` (boolean): Use predefined fixed response templates for completely consistent responses
|
|
60
74
|
|
|
61
75
|
The function returns an object with control methods:
|
|
62
76
|
```js
|
|
@@ -68,6 +82,16 @@ console.log(mock.isActive);
|
|
|
68
82
|
// Stop all mocks
|
|
69
83
|
mock.stopMocking();
|
|
70
84
|
|
|
85
|
+
// Seed management for consistent outputs
|
|
86
|
+
mock.setSeed(12345); // Set a new seed for deterministic responses
|
|
87
|
+
mock.resetSeed(); // Reset to random responses
|
|
88
|
+
|
|
89
|
+
// Template management
|
|
90
|
+
const templates = mock.getResponseTemplates(); // Get available templates
|
|
91
|
+
const customTemplate = mock.createResponseTemplate('SIMPLE_CHAT', {
|
|
92
|
+
choices: [{ message: { content: 'Custom response' } }]
|
|
93
|
+
});
|
|
94
|
+
|
|
71
95
|
// Add custom endpoint mock (uses api.openai.com as base url)
|
|
72
96
|
mock.addCustomEndpoint('POST', '/v1/custom', (uri, body) => {
|
|
73
97
|
return [200, { custom: 'response' }];
|
|
@@ -129,6 +153,74 @@ for await (const part of response) {
|
|
|
129
153
|
}
|
|
130
154
|
```
|
|
131
155
|
|
|
156
|
+
## Consistent Outputs for Testing
|
|
157
|
+
|
|
158
|
+
The library provides several mechanisms to achieve consistent, deterministic outputs for reliable testing:
|
|
159
|
+
|
|
160
|
+
### Seed-based Consistency
|
|
161
|
+
|
|
162
|
+
Use seeds to ensure reproducible responses across test runs:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
// Set up mock with a fixed seed
|
|
166
|
+
const mock = mockOpenAIResponse(true, { seed: 12345 });
|
|
167
|
+
|
|
168
|
+
// Multiple calls will return identical responses
|
|
169
|
+
const response1 = await openai.chat.completions.create({
|
|
170
|
+
model: 'gpt-3.5-turbo',
|
|
171
|
+
messages: [{ role: 'user', content: 'Hello' }]
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const response2 = await openai.chat.completions.create({
|
|
175
|
+
model: 'gpt-3.5-turbo',
|
|
176
|
+
messages: [{ role: 'user', content: 'Hello' }]
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// response1 and response2 will be identical
|
|
180
|
+
console.log(JSON.stringify(response1) === JSON.stringify(response2)); // true
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Fixed Response Templates
|
|
184
|
+
|
|
185
|
+
For maximum consistency, use predefined response templates:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
// Enable fixed responses
|
|
189
|
+
const mock = mockOpenAIResponse(true, { useFixedResponses: true });
|
|
190
|
+
|
|
191
|
+
const response = await openai.chat.completions.create({
|
|
192
|
+
model: 'gpt-3.5-turbo',
|
|
193
|
+
messages: [{ role: 'user', content: 'Any message' }]
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Will always return the same fixed response
|
|
197
|
+
console.log(response.choices[0].message.content);
|
|
198
|
+
// "This is a consistent test response."
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Runtime Seed Management
|
|
202
|
+
|
|
203
|
+
Change seeds during runtime for different test scenarios:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
const mock = mockOpenAIResponse(true);
|
|
207
|
+
|
|
208
|
+
// Test scenario A
|
|
209
|
+
mock.setSeed(12345);
|
|
210
|
+
const responseA = await openai.chat.completions.create({...});
|
|
211
|
+
|
|
212
|
+
// Test scenario B
|
|
213
|
+
mock.setSeed(54321);
|
|
214
|
+
const responseB = await openai.chat.completions.create({...});
|
|
215
|
+
|
|
216
|
+
// Reset to random behavior
|
|
217
|
+
mock.resetSeed();
|
|
218
|
+
const responseRandom = await openai.chat.completions.create({...});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For comprehensive examples and best practices, see [CONSISTENCY_EXAMPLES.md](./CONSISTENCY_EXAMPLES.md).
|
|
222
|
+
```
|
|
223
|
+
|
|
132
224
|
## Intercepted URLs
|
|
133
225
|
|
|
134
226
|
This module uses the `nock` library to intercept HTTP calls to the following OpenAI API endpoints:
|
|
@@ -146,9 +238,11 @@ import { mockOpenAIResponse, MockOptions } from 'openai-api-mock';
|
|
|
146
238
|
|
|
147
239
|
// Configure with TypeScript types
|
|
148
240
|
const options: MockOptions = {
|
|
149
|
-
includeErrors: true,
|
|
150
|
-
latency: 1000,
|
|
151
|
-
logRequests: true
|
|
241
|
+
includeErrors: true, // Optional: simulate random API errors
|
|
242
|
+
latency: 1000, // Optional: add 1 second delay
|
|
243
|
+
logRequests: true, // Optional: log requests to console
|
|
244
|
+
seed: 12345, // Optional: seed for consistent responses
|
|
245
|
+
useFixedResponses: true // Optional: use fixed response templates
|
|
152
246
|
};
|
|
153
247
|
|
|
154
248
|
const mock = mockOpenAIResponse(true, options);
|
|
@@ -156,6 +250,14 @@ const mock = mockOpenAIResponse(true, options);
|
|
|
156
250
|
// TypeScript provides full type checking and autocompletion
|
|
157
251
|
console.log(mock.isActive); // boolean
|
|
158
252
|
mock.stopMocking(); // function
|
|
253
|
+
mock.setSeed(54321); // function with type checking
|
|
254
|
+
mock.resetSeed(); // function
|
|
255
|
+
|
|
256
|
+
// Template methods with type safety
|
|
257
|
+
const templates = mock.getResponseTemplates(); // Record<string, any>
|
|
258
|
+
const customTemplate = mock.createResponseTemplate('SIMPLE_CHAT', {
|
|
259
|
+
choices: [{ message: { content: 'Custom content' } }]
|
|
260
|
+
});
|
|
159
261
|
|
|
160
262
|
// Custom endpoints with type safety
|
|
161
263
|
mock.addCustomEndpoint('POST', '/v1/custom', (uri, body) => {
|
package/dist/index.cjs
CHANGED
|
@@ -2188,16 +2188,129 @@ function getImageResponce(requestBody) {
|
|
|
2188
2188
|
data
|
|
2189
2189
|
};
|
|
2190
2190
|
}
|
|
2191
|
+
const RESPONSE_TEMPLATES = {
|
|
2192
|
+
SIMPLE_CHAT: {
|
|
2193
|
+
choices: [
|
|
2194
|
+
{
|
|
2195
|
+
finish_reason: "stop",
|
|
2196
|
+
index: 0,
|
|
2197
|
+
message: {
|
|
2198
|
+
content: "This is a consistent test response.",
|
|
2199
|
+
role: "assistant"
|
|
2200
|
+
},
|
|
2201
|
+
logprobs: null
|
|
2202
|
+
}
|
|
2203
|
+
],
|
|
2204
|
+
created: 1640995200,
|
|
2205
|
+
id: "chatcmpl-test123456789",
|
|
2206
|
+
model: "gpt-3.5-mock",
|
|
2207
|
+
object: "chat.completion",
|
|
2208
|
+
usage: {
|
|
2209
|
+
completion_tokens: 10,
|
|
2210
|
+
prompt_tokens: 20,
|
|
2211
|
+
total_tokens: 30
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
FUNCTION_CALL: {
|
|
2215
|
+
id: "chatcmpl-test123456789",
|
|
2216
|
+
object: "chat.completion",
|
|
2217
|
+
created: 1640995200,
|
|
2218
|
+
model: "gpt-3.5-mock",
|
|
2219
|
+
choices: [
|
|
2220
|
+
{
|
|
2221
|
+
index: 0,
|
|
2222
|
+
message: {
|
|
2223
|
+
role: "assistant",
|
|
2224
|
+
content: null,
|
|
2225
|
+
function_call: {
|
|
2226
|
+
name: "test_function",
|
|
2227
|
+
arguments: '{"param1": "test_value", "param2": 42}'
|
|
2228
|
+
}
|
|
2229
|
+
},
|
|
2230
|
+
finish_reason: "function_call"
|
|
2231
|
+
}
|
|
2232
|
+
],
|
|
2233
|
+
usage: {
|
|
2234
|
+
prompt_tokens: 81,
|
|
2235
|
+
completion_tokens: 19,
|
|
2236
|
+
total_tokens: 100
|
|
2237
|
+
}
|
|
2238
|
+
},
|
|
2239
|
+
TOOL_CALL: {
|
|
2240
|
+
id: "chatcmpl-test123456789",
|
|
2241
|
+
object: "chat.completion",
|
|
2242
|
+
created: 1640995200,
|
|
2243
|
+
model: "gpt-3.5-mock",
|
|
2244
|
+
choices: [
|
|
2245
|
+
{
|
|
2246
|
+
index: 0,
|
|
2247
|
+
message: {
|
|
2248
|
+
role: "assistant",
|
|
2249
|
+
content: null,
|
|
2250
|
+
tool_calls: [
|
|
2251
|
+
{
|
|
2252
|
+
id: "call_test123456789",
|
|
2253
|
+
type: "function",
|
|
2254
|
+
function: {
|
|
2255
|
+
name: "test_function",
|
|
2256
|
+
arguments: '{"param1": "test_value", "param2": 42}'
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
]
|
|
2260
|
+
},
|
|
2261
|
+
finish_reason: "tool_calls"
|
|
2262
|
+
}
|
|
2263
|
+
],
|
|
2264
|
+
usage: {
|
|
2265
|
+
prompt_tokens: 81,
|
|
2266
|
+
completion_tokens: 19,
|
|
2267
|
+
total_tokens: 100
|
|
2268
|
+
}
|
|
2269
|
+
},
|
|
2270
|
+
IMAGE_GENERATION: {
|
|
2271
|
+
created: 1640995200,
|
|
2272
|
+
data: [
|
|
2273
|
+
{
|
|
2274
|
+
url: "https://example.com/test-image.png"
|
|
2275
|
+
}
|
|
2276
|
+
]
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
function createResponseTemplate(templateType, overrides = {}) {
|
|
2280
|
+
const template = RESPONSE_TEMPLATES[templateType];
|
|
2281
|
+
if (!template) {
|
|
2282
|
+
throw new Error(`Unknown template type: ${templateType}`);
|
|
2283
|
+
}
|
|
2284
|
+
return deepMerge(template, overrides);
|
|
2285
|
+
}
|
|
2286
|
+
function deepMerge(target, source) {
|
|
2287
|
+
const result = { ...target };
|
|
2288
|
+
for (const key in source) {
|
|
2289
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
2290
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
2291
|
+
} else {
|
|
2292
|
+
result[key] = source[key];
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
return result;
|
|
2296
|
+
}
|
|
2191
2297
|
const OPEN_AI_BASE_URL = "https://api.openai.com";
|
|
2192
2298
|
const CHAT_COMPLETIONS_ENDPOINT = "/v1/chat/completions";
|
|
2193
2299
|
const IMAGE_GENERATIONS_ENDPOINT = "/v1/images/generations";
|
|
2194
|
-
const customScopes = [];
|
|
2195
2300
|
function mockOpenAIResponse(force = false, options = {}) {
|
|
2196
2301
|
const {
|
|
2197
2302
|
includeErrors = false,
|
|
2198
2303
|
latency = 0,
|
|
2199
|
-
logRequests = false
|
|
2304
|
+
logRequests = false,
|
|
2305
|
+
seed = null,
|
|
2306
|
+
useFixedResponses = false
|
|
2200
2307
|
} = options;
|
|
2308
|
+
if (seed !== null) {
|
|
2309
|
+
f.seed(seed);
|
|
2310
|
+
if (logRequests) {
|
|
2311
|
+
console.log(`[openai-api-mock] Using seed for consistent outputs: ${seed}`);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2201
2314
|
const env = process.env.NODE_ENV || "development";
|
|
2202
2315
|
if (env !== "development" && !force) {
|
|
2203
2316
|
return { isActive: false, stopMocking };
|
|
@@ -2218,12 +2331,21 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
2218
2331
|
const stream = createChatStream(requestBody);
|
|
2219
2332
|
return [200, stream];
|
|
2220
2333
|
}
|
|
2334
|
+
if (useFixedResponses) {
|
|
2335
|
+
if (requestBody.tools) {
|
|
2336
|
+
return [200, createResponseTemplate("TOOL_CALL")];
|
|
2337
|
+
} else if (requestBody.functions) {
|
|
2338
|
+
return [200, createResponseTemplate("FUNCTION_CALL")];
|
|
2339
|
+
} else {
|
|
2340
|
+
return [200, createResponseTemplate("SIMPLE_CHAT")];
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2221
2343
|
return [200, getChatResponce(requestBody)];
|
|
2222
2344
|
} catch (error) {
|
|
2223
2345
|
console.error("[openai-api-mock] Error processing chat request:", error);
|
|
2224
2346
|
return [500, { error: { message: "Internal server error in mock" } }];
|
|
2225
2347
|
}
|
|
2226
|
-
});
|
|
2348
|
+
}).persist();
|
|
2227
2349
|
nock(OPEN_AI_BASE_URL).post(IMAGE_GENERATIONS_ENDPOINT).delay(latency).reply(function(uri, requestBody) {
|
|
2228
2350
|
if (logRequests) {
|
|
2229
2351
|
console.log(`[openai-api-mock] Image request:`, JSON.stringify(requestBody, null, 2));
|
|
@@ -2235,16 +2357,52 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
2235
2357
|
if (includeErrors && Math.random() < 0.05) {
|
|
2236
2358
|
return [400, { error: { message: "Your request was rejected as a result of our safety system." } }];
|
|
2237
2359
|
}
|
|
2360
|
+
if (useFixedResponses) {
|
|
2361
|
+
return [200, createResponseTemplate("IMAGE_GENERATION")];
|
|
2362
|
+
}
|
|
2238
2363
|
return [200, getImageResponce(requestBody)];
|
|
2239
2364
|
} catch (error) {
|
|
2240
2365
|
console.error("[openai-api-mock] Error processing image request:", error);
|
|
2241
2366
|
return [500, { error: { message: "Internal server error in mock" } }];
|
|
2242
2367
|
}
|
|
2243
|
-
});
|
|
2368
|
+
}).persist();
|
|
2244
2369
|
nock.enableNetConnect((host) => host !== "api.openai.com");
|
|
2245
2370
|
return {
|
|
2246
2371
|
isActive: true,
|
|
2247
2372
|
stopMocking,
|
|
2373
|
+
/**
|
|
2374
|
+
* Resets the faker seed to ensure consistent outputs for subsequent requests
|
|
2375
|
+
* @param {number|string} newSeed - New seed value to use
|
|
2376
|
+
*/
|
|
2377
|
+
setSeed(newSeed) {
|
|
2378
|
+
f.seed(newSeed);
|
|
2379
|
+
if (logRequests) {
|
|
2380
|
+
console.log(`[openai-api-mock] Seed updated to: ${newSeed}`);
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
/**
|
|
2384
|
+
* Resets faker to use random values (removes deterministic behavior)
|
|
2385
|
+
*/
|
|
2386
|
+
resetSeed() {
|
|
2387
|
+
f.seed();
|
|
2388
|
+
if (logRequests) {
|
|
2389
|
+
console.log(`[openai-api-mock] Seed reset - using random values`);
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
/**
|
|
2393
|
+
* Get available response templates
|
|
2394
|
+
* @returns {Object} Available response templates
|
|
2395
|
+
*/
|
|
2396
|
+
getResponseTemplates() {
|
|
2397
|
+
return RESPONSE_TEMPLATES;
|
|
2398
|
+
},
|
|
2399
|
+
/**
|
|
2400
|
+
* Create a custom response template
|
|
2401
|
+
* @param {string} templateType - Type of template to use
|
|
2402
|
+
* @param {Object} overrides - Values to override in the template
|
|
2403
|
+
* @returns {Object} Response template with overrides applied
|
|
2404
|
+
*/
|
|
2405
|
+
createResponseTemplate,
|
|
2248
2406
|
/**
|
|
2249
2407
|
* Adds a custom endpoint mock
|
|
2250
2408
|
* @param {string} method - HTTP method (e.g., 'GET', 'POST')
|
|
@@ -2253,14 +2411,12 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
2253
2411
|
*/
|
|
2254
2412
|
addCustomEndpoint(method, path, handler) {
|
|
2255
2413
|
const methodLower = method.toLowerCase();
|
|
2256
|
-
|
|
2257
|
-
customScopes.push(scope);
|
|
2414
|
+
nock(OPEN_AI_BASE_URL)[methodLower](path).reply(handler).persist();
|
|
2258
2415
|
}
|
|
2259
2416
|
};
|
|
2260
2417
|
}
|
|
2261
2418
|
function stopMocking() {
|
|
2262
2419
|
nock.cleanAll();
|
|
2263
|
-
customScopes.forEach((scope) => scope.persist(false));
|
|
2264
2420
|
}
|
|
2265
2421
|
exports.mockOpenAIResponse = mockOpenAIResponse;
|
|
2266
2422
|
exports.stopMocking = stopMocking;
|