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 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 // Log incoming requests to console
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, // Optional: simulate random API errors
150
- latency: 1000, // Optional: add 1 second delay
151
- logRequests: true // Optional: log requests to console
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
- const scope = nock(OPEN_AI_BASE_URL)[methodLower](path).reply(handler).persist();
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;