openai-api-mock 0.1.6 → 0.1.8

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/main.js CHANGED
@@ -2,33 +2,36 @@ const nock = require('nock');
2
2
  const { getChatResponce } = require('./src/chat.js');
3
3
  const { getImageResponce } = require('./src/image.js');
4
4
 
5
+ const OPEN_AI_BASE_URL = 'https://api.openai.com';
6
+ const CHAT_COMPLETIONS_ENDPOINT = '/v1/chat/completions';
7
+ const IMAGE_GENERATIONS_ENDPOINT = '/v1/images/generations';
8
+
5
9
  function mockOpenAIResponse(force = false) {
6
- // Define the OpenAI endpoints
7
- const openaiCompletionEndpoint = 'https://api.openai.com/v1/chat/completions';
8
- const imageEndpoint = 'https://api.openai.com/v1/images/generations';
9
10
  var env = process.env.NODE_ENV || 'development';
10
-
11
11
  // Intercept the HTTP call and return the mock response
12
12
  if (env === 'development' || force) {
13
- nock(openaiCompletionEndpoint)
14
- .post('')
13
+ nock(OPEN_AI_BASE_URL)
14
+ .post(CHAT_COMPLETIONS_ENDPOINT)
15
15
  .reply(function (uri, requestBody) {
16
16
  return [200, getChatResponce(requestBody)];
17
17
  });
18
18
 
19
- nock(imageEndpoint)
20
- .post('')
19
+ nock(OPEN_AI_BASE_URL)
20
+ .post(IMAGE_GENERATIONS_ENDPOINT)
21
21
  .reply(function (uri, requestBody) {
22
22
  return [200, getImageResponce(requestBody)];
23
23
  });
24
24
 
25
25
  // Mocking only the chat completion endpoint, not blocking other requests
26
- nock.emitter.on('no match', function (req) {
27
- nock.enableNetConnect(req);
28
- });
26
+ nock.enableNetConnect(host => host !== "api.openai.com");
29
27
  }
30
28
  }
31
29
 
30
+ function stopMocking() {
31
+ nock.cleanAll();
32
+ }
33
+
32
34
  module.exports = {
33
- mockOpenAIResponse
34
- };
35
+ mockOpenAIResponse,
36
+ stopMocking,
37
+ };
package/main.test.js ADDED
@@ -0,0 +1,180 @@
1
+ const { mockOpenAIResponse, stopMocking } = require('./main');
2
+
3
+ const OpenAI = require('openai');
4
+ const openai = new OpenAI({ apiKey: "OPENAI_API_KEY" });
5
+
6
+ describe('Mock OpenAI Chat & Image generation API', () => {
7
+ beforeEach(() => {
8
+ mockOpenAIResponse(true);
9
+ });
10
+ afterEach(() => {
11
+ stopMocking();
12
+ });
13
+ it('should mock the chat completion', async () => {
14
+ try {
15
+ const response = await openai.chat.completions.create({
16
+ model: "gpt-3.5",
17
+ messages: [
18
+ { role: 'system', content: "You'r an expert chef" },
19
+ { role: 'user', content: "Suggest at least 5 recipes" },
20
+ ]
21
+ });
22
+ expect(response.choices[0]).toHaveProperty('finish_reason', 'stop');
23
+ expect(response.model).toEqual('gpt-3.5-mock');
24
+ } catch (error) {
25
+ throw new Error(error);
26
+ }
27
+ });
28
+
29
+ it('should mock the chat completion with function call', async () => {
30
+ try {
31
+ const response = await openai.chat.completions.create({
32
+ model: "gpt-3.5",
33
+ messages: [
34
+ { role: 'system', content: "You'r an expert chef" },
35
+ { role: 'user', content: "Suggest at least 5 recipes" },
36
+ ],
37
+ functions: [
38
+ {
39
+ name: 'get_recipes',
40
+ description: 'Suggest at least 5 recipes based on the ingredients you have',
41
+ parameters: {
42
+ type: "object",
43
+ properties: {
44
+ recipes: {
45
+ type: "array",
46
+ description: "The recipes that can be made with the ingredients",
47
+ items: {
48
+ type: 'object',
49
+ properties: {
50
+ name: {
51
+ type: 'string',
52
+ description: 'The name of the recipe',
53
+ },
54
+ ingredients: {
55
+ type: 'array',
56
+ description: 'The ingredients needed for the recipe',
57
+ items: {
58
+ type: 'string',
59
+ description: 'The name of the ingredient',
60
+ },
61
+ },
62
+ instructions: {
63
+ type: 'string',
64
+ description: 'Detailed instructions on how to make the recipe',
65
+ },
66
+ serving: {
67
+ type: 'string',
68
+ description: 'The number of people the recipe serves',
69
+ },
70
+ }
71
+ }
72
+ },
73
+ }
74
+ },
75
+ required: ["recipes"],
76
+ },
77
+ ],
78
+ function_call: { name: 'get_recipes' },
79
+ });
80
+ expect(response.model).toEqual('gpt-3.5-mock');
81
+ expect(response.choices[0]).toHaveProperty('finish_reason', 'function_call');
82
+ expect(response.choices[0].message.function_call).toHaveProperty('name', 'get_recipes');
83
+ expect(response.choices[0].message.function_call).toHaveProperty('arguments');
84
+ expect(JSON.parse(response.choices[0].message.function_call.arguments)).toHaveProperty('recipes');
85
+
86
+ } catch (error) {
87
+ throw new Error(error);
88
+ }
89
+ });
90
+
91
+
92
+ it('should mock the chat completion with function call (tools)', async () => {
93
+ const response = await openai.chat.completions.create({
94
+ model: "gpt-3.5",
95
+ messages: [
96
+ { role: 'system', content: "You'r an expert chef" },
97
+ { role: 'user', content: "Suggest at least 5 recipes" },
98
+ ],
99
+ tools: [
100
+ {
101
+ type: "function",
102
+ function: {
103
+ name: 'get_recipes',
104
+ description: 'Suggest at least 5 recipes based on the ingredients you have',
105
+ parameters: {
106
+ type: "object",
107
+ properties: {
108
+ recipes: {
109
+ type: "array",
110
+ description: "The recipes that can be made with the ingredients",
111
+ items: {
112
+ type: 'object',
113
+ properties: {
114
+ name: {
115
+ type: 'string',
116
+ description: 'The name of the recipe',
117
+ },
118
+ ingredients: {
119
+ type: 'array',
120
+ description: 'The ingredients needed for the recipe',
121
+ items: {
122
+ type: 'string',
123
+ description: 'The name of the ingredient',
124
+ },
125
+ },
126
+ instructions: {
127
+ type: 'string',
128
+ description: 'Detailed instructions on how to make the recipe',
129
+ },
130
+ serving: {
131
+ type: 'string',
132
+ description: 'The number of people the recipe serves',
133
+ },
134
+ }
135
+ }
136
+ },
137
+ }
138
+ },
139
+ required: ["recipes"],
140
+ }
141
+
142
+ }],
143
+ tool_choice: { name: 'get_recipes' },
144
+ });
145
+ expect(response.model).toEqual('gpt-3.5-mock');
146
+ expect(response.choices[0]).toHaveProperty('finish_reason', 'tool_calls');
147
+ expect(response.choices[0].message.tool_calls[0].function).toHaveProperty('name', 'get_recipes');
148
+ expect(response.choices[0].message.tool_calls[0].function).toHaveProperty('arguments');
149
+ expect(JSON.parse(response.choices[0].message.tool_calls[0].function.arguments)).toHaveProperty('recipes');
150
+ });
151
+
152
+
153
+ it('should mock image generation', async () => {
154
+ try {
155
+ const response = await openai.images.generate({
156
+ model: "dall-e-3",
157
+ prompt: "Kurt Cobain",
158
+ n: 1,
159
+ size: "1024x1024",
160
+ quality: "hd",
161
+ });
162
+
163
+ expect(response.data[0]).toHaveProperty('revised_prompt');
164
+ expect(response.data[0]).toHaveProperty('url');
165
+ } catch (error) {
166
+ throw new Error(error);
167
+ }
168
+
169
+ });
170
+
171
+ it('should allow other requests', async () => {
172
+ try {
173
+ const response = await fetch('https://jsonplaceholder.typicode.com/todos');
174
+ const data = await response.json();
175
+ expect(data[0]).toHaveProperty('completed');
176
+ } catch (error) {
177
+ throw new Error(error);
178
+ }
179
+ })
180
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openai-api-mock",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A Node.js module for mocking OpenAI API responses in a development environment",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,13 +11,22 @@
11
11
  "mock",
12
12
  "api",
13
13
  "development",
14
- "testing"
14
+ "testing",
15
+ "chatgpt",
16
+ "interceptor"
15
17
  ],
18
+ "scripts": {
19
+ "test": "jest"
20
+ },
16
21
  "author": "Chiheb Nabil",
17
22
  "license": "MIT",
18
23
  "main": "main.js",
19
24
  "dependencies": {
20
25
  "@faker-js/faker": "^8.4.0",
21
26
  "nock": "^13.5.1"
27
+ },
28
+ "devDependencies": {
29
+ "jest": "^29.7.0",
30
+ "openai": "^4.26.0"
22
31
  }
23
32
  }
package/readme.md CHANGED
@@ -32,6 +32,42 @@ mockOpenAIResponse(force = true);
32
32
  The force parameter is a boolean that determines whether the mock response should be used regardless of the environment.
33
33
  If force is true, the mock response will be used regardless of the environment. If force is false or not provided, the mock response will only be used if the <code>NODE_ENV</code> environment variable is set to 'development'.
34
34
 
35
+ ### Example responces
36
+
37
+ ```js
38
+ // Call the mockOpenAIResponse function once to set up the mock
39
+ mockOpenAIResponse()
40
+
41
+ // Now, when you call the OpenAI API, it will return a mock response
42
+ const response = await openai.chat.completions.create({
43
+ model: "gpt-3.5",
44
+ messages: [
45
+ { role: 'system', content: "You'r an expert chef" },
46
+ { role: 'user', content: "Suggest at least 5 recipes" },
47
+ ]
48
+ });
49
+ ```
50
+ In this example, the `response` constant will contain mock data, simulating a response from the OpenAI API:
51
+
52
+ ```json
53
+ {
54
+ choices: [
55
+ {
56
+ finish_reason: 'stop',
57
+ index: 0,
58
+ message: [Object],
59
+ logprobs: null
60
+ }
61
+ ],
62
+ created: 1707040459,
63
+ id: 'chatcmpl-tggOnwW8Lp2XiwQ8dmHHAcNYJ8CfzR',
64
+ model: 'gpt-3.5-mock',
65
+ object: 'chat.completion',
66
+ usage: { completion_tokens: 17, prompt_tokens: 57, total_tokens: 74 }
67
+ }
68
+ ```
69
+
70
+
35
71
  ## Intercepted URLs
36
72
 
37
73
  This module uses the `nock` library to intercept HTTP calls to the following OpenAI API endpoints:
package/src/chat.js CHANGED
@@ -3,7 +3,7 @@ const { faker } = require('@faker-js/faker');
3
3
  function getChatResponce(requestBody) {
4
4
  const created = Math.floor(Date.now() / 1000);
5
5
 
6
- if (!requestBody.functions) {
6
+ if (!requestBody.functions && !requestBody.tools) {
7
7
  return createDefaultResponse(created);
8
8
  }
9
9
 
@@ -36,6 +36,10 @@ function createDefaultResponse(created) {
36
36
  }
37
37
 
38
38
  function createFunctionCallingResponse(requestBody, created) {
39
+ const isTool = Boolean(requestBody.tools);
40
+ const functionOrToolCallObject = isTool ? [createToolCallObject(requestBody)] : createFunctionCallObject(requestBody);
41
+ const functionOrToolCall = isTool ? 'tool_calls' : 'function_call';
42
+
39
43
  const functionCallingResponse = {
40
44
  id: `chatcmpl-${faker.string.alphanumeric(30)}`,
41
45
  object: 'chat.completion',
@@ -47,9 +51,9 @@ function createFunctionCallingResponse(requestBody, created) {
47
51
  message: {
48
52
  role: 'assistant',
49
53
  content: null,
50
- function_call: createFunctionCallObject(requestBody),
54
+ [functionOrToolCall]: functionOrToolCallObject,
51
55
  },
52
- finish_reason: 'function_call',
56
+ finish_reason: functionOrToolCall,
53
57
  },
54
58
  ],
55
59
  usage: {
@@ -62,6 +66,16 @@ function createFunctionCallingResponse(requestBody, created) {
62
66
  return functionCallingResponse;
63
67
  }
64
68
 
69
+ function createToolCallObject(requestBody) {
70
+ return {
71
+ id: `call-${faker.string.alphanumeric(30)}`,
72
+ type: "function",
73
+ "function": {
74
+ name: `${requestBody.tools[0].function.name}`,
75
+ arguments: `${generateToolCallArguments(requestBody)}`
76
+ }
77
+ };
78
+ }
65
79
  function createFunctionCallObject(requestBody) {
66
80
  return {
67
81
  name: `${requestBody.functions[0].name}`,
@@ -69,6 +83,17 @@ function createFunctionCallObject(requestBody) {
69
83
  };
70
84
  }
71
85
 
86
+ function generateToolCallArguments(requestBody) {
87
+ const { parameters } = requestBody.tools[0].function;
88
+ const argumentsObject = {};
89
+
90
+ Object.entries(parameters.properties).forEach(([paramName, paramDetails]) => {
91
+ argumentsObject[paramName] = generateFakeData(paramDetails.type, paramDetails);
92
+ });
93
+
94
+ return JSON.stringify(argumentsObject, null, 2);
95
+ }
96
+
72
97
  function generateFakeData(type, properties) {
73
98
  switch (type) {
74
99
  case 'string':
@@ -119,4 +144,4 @@ function generateFunctionCallArguments(requestBody) {
119
144
 
120
145
  module.exports = {
121
146
  getChatResponce,
122
- };
147
+ };
package/src/image.js CHANGED
@@ -19,8 +19,6 @@ function getImageResponce(requestBody) {
19
19
  }
20
20
 
21
21
 
22
-
23
-
24
22
  module.exports = {
25
23
  getImageResponce,
26
- };
24
+ };