@mistralai/mistralai 0.0.8 → 0.0.10

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/.eslintrc.yml CHANGED
@@ -2,11 +2,12 @@ env:
2
2
  browser: true
3
3
  es2021: true
4
4
  extends: google
5
- ignorePatterns:
5
+ ignorePatterns:
6
6
  - examples/chat-react/
7
7
  parserOptions:
8
8
  ecmaVersion: latest
9
9
  sourceType: module
10
- rules:
10
+ rules:
11
11
  indent: ["error", 2]
12
12
  space-before-function-paren: ["error", "never"]
13
+ quotes: ["error", "single"]
@@ -15,9 +15,13 @@ on:
15
15
 
16
16
  jobs:
17
17
 
18
- lint:
18
+ lint_and_test:
19
19
  runs-on: ubuntu-latest
20
20
 
21
+ strategy:
22
+ matrix:
23
+ node-version: [18, 20]
24
+
21
25
  steps:
22
26
  # Checkout the repository
23
27
  - name: Checkout
@@ -27,20 +31,25 @@ jobs:
27
31
  - name: set node version
28
32
  uses: actions/setup-node@v4
29
33
  with:
30
- node-version: 18
34
+ node-version: ${{ matrix.node-version }}
31
35
 
32
36
  # Install Build stuff
33
37
  - name: Install Dependencies
34
38
  run: |
35
39
  npm install
36
40
 
37
- # Ruff
41
+ # Eslint
38
42
  - name: ESlint check
39
43
  run: |
40
- ./node_modules/.bin/eslint .
44
+ npm run lint
45
+
46
+ # Run tests
47
+ - name: Run tests
48
+ run: |
49
+ npm run test
41
50
 
42
51
  publish:
43
- needs: lint
52
+ needs: lint_and_test
44
53
  runs-on: ubuntu-latest
45
54
  if: startsWith(github.ref, 'refs/tags')
46
55
 
@@ -60,6 +69,7 @@ jobs:
60
69
  run: |
61
70
  echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
62
71
  npm version ${{ github.ref_name }}
72
+ sed -i 's/VERSION = '\''0.0.1'\''/VERSION = '\''${{ github.ref_name }}'\''/g' src/client.js
63
73
  npm publish
64
74
 
65
75
 
package/README.md CHANGED
@@ -11,16 +11,19 @@ You can install the library in your project using:
11
11
  `npm install @mistralai/mistralai`
12
12
 
13
13
  ## Usage
14
+
14
15
  ### Set up
16
+
15
17
  ```typescript
16
18
  import MistralClient from '@mistralai/mistralai';
17
19
 
18
- const apiKey = "Your API key";
20
+ const apiKey = process.env.MISTRAL_API_KEY || 'your_api_key';
19
21
 
20
22
  const client = new MistralClient(apiKey);
21
23
  ```
22
24
 
23
25
  ### List models
26
+
24
27
  ```typescript
25
28
  const listModelsResponse = await client.listModels();
26
29
  const listModels = listModelsResponse.data;
@@ -30,6 +33,7 @@ listModels.forEach((model) => {
30
33
  ```
31
34
 
32
35
  ### Chat with streaming
36
+
33
37
  ```typescript
34
38
  const chatStreamResponse = await client.chatStream({
35
39
  model: 'mistral-tiny',
@@ -44,7 +48,9 @@ for await (const chunk of chatStreamResponse) {
44
48
  }
45
49
  }
46
50
  ```
51
+
47
52
  ### Chat without streaming
53
+
48
54
  ```typescript
49
55
  const chatResponse = await client.chat({
50
56
  model: 'mistral-tiny',
@@ -53,7 +59,9 @@ const chatResponse = await client.chat({
53
59
 
54
60
  console.log('Chat:', chatResponse.choices[0].message.content);
55
61
  ```
56
- ###Embeddings
62
+
63
+ ### Embeddings
64
+
57
65
  ```typescript
58
66
  const input = [];
59
67
  for (let i = 0; i < 1; i++) {
@@ -67,6 +75,7 @@ const embeddingsBatchResponse = await client.embeddings({
67
75
 
68
76
  console.log('Embeddings Batch:', embeddingsBatchResponse.data);
69
77
  ```
78
+
70
79
  ## Run examples
71
80
 
72
81
  You can run the examples in the examples directory by installing them locally:
@@ -76,23 +85,39 @@ cd examples
76
85
  npm install .
77
86
  ```
78
87
 
79
- ### API Key Setup
88
+ ### API key setup
80
89
 
81
90
  Running the examples requires a Mistral AI API key.
82
91
 
83
- 1. Get your own Mistral API Key: <https://docs.mistral.ai/#api-access>
84
- 2. Set your Mistral API Key as an environment variable. You only need to do this once.
92
+ Get your own Mistral API Key: <https://docs.mistral.ai/#api-access>
93
+
94
+ ### Run the examples
95
+
96
+ ```bash
97
+ MISTRAL_API_KEY='your_api_key' node chat_with_streaming.js
98
+ ```
99
+
100
+ ### Persisting the API key in environment
101
+
102
+ Set your Mistral API Key as an environment variable. You only need to do this once.
85
103
 
86
104
  ```bash
87
105
  # set Mistral API Key (using zsh for example)
88
- $ echo 'export MISTRAL_API_KEY=[your_key_here]' >> ~/.zshenv
106
+ $ echo 'export MISTRAL_API_KEY=[your_api_key]' >> ~/.zshenv
89
107
 
90
108
  # reload the environment (or just quit and open a new terminal)
91
109
  $ source ~/.zshenv
92
110
  ```
93
111
 
94
- You can then run the examples using node:
112
+ You can then run the examples without appending the API key:
95
113
 
96
114
  ```bash
97
- MISTRAL_API_KEY=XXXX node chat_with_streaming.js
115
+ node chat_with_streaming.js
116
+ ```
117
+ After the env variable setup the client will find the `MISTRAL_API_KEY` by itself
118
+
119
+ ```typescript
120
+ import MistralClient from '@mistralai/mistralai';
121
+
122
+ const client = new MistralClient();
98
123
  ```
@@ -26,6 +26,7 @@
26
26
  "devDependencies": {
27
27
  "eslint": "^8.55.0",
28
28
  "eslint-config-google": "^0.14.0",
29
+ "jest": "^29.7.0",
29
30
  "prettier": "2.8.8"
30
31
  }
31
32
  },
@@ -16023,16 +16024,16 @@
16023
16024
  }
16024
16025
  },
16025
16026
  "node_modules/typescript": {
16026
- "version": "5.3.3",
16027
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
16028
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
16027
+ "version": "4.9.5",
16028
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
16029
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
16029
16030
  "peer": true,
16030
16031
  "bin": {
16031
16032
  "tsc": "bin/tsc",
16032
16033
  "tsserver": "bin/tsserver"
16033
16034
  },
16034
16035
  "engines": {
16035
- "node": ">=14.17"
16036
+ "node": ">=4.2.0"
16036
16037
  }
16037
16038
  },
16038
16039
  "node_modules/unbox-primitive": {
@@ -19300,6 +19301,7 @@
19300
19301
  "requires": {
19301
19302
  "eslint": "^8.55.0",
19302
19303
  "eslint-config-google": "^0.14.0",
19304
+ "jest": "^29.7.0",
19303
19305
  "node-fetch": "^2.6.7",
19304
19306
  "prettier": "2.8.8"
19305
19307
  }
@@ -28464,9 +28466,9 @@
28464
28466
  }
28465
28467
  },
28466
28468
  "typescript": {
28467
- "version": "5.3.3",
28468
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
28469
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
28469
+ "version": "4.9.5",
28470
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
28471
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
28470
28472
  "peer": true
28471
28473
  },
28472
28474
  "unbox-primitive": {
@@ -13,11 +13,6 @@
13
13
  "scripts": {
14
14
  "start": "react-scripts start"
15
15
  },
16
- // "eslintConfig": {
17
- // "extends": [
18
- // "react-app"
19
- // ]
20
- // },
21
16
  "browserslist": {
22
17
  "production": [
23
18
  ">0.2%",
@@ -21,6 +21,7 @@
21
21
  "devDependencies": {
22
22
  "eslint": "^8.55.0",
23
23
  "eslint-config-google": "^0.14.0",
24
+ "jest": "^29.7.0",
24
25
  "prettier": "2.8.8"
25
26
  }
26
27
  },
package/package.json CHANGED
@@ -1,22 +1,32 @@
1
1
  {
2
2
  "name": "@mistralai/mistralai",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "",
5
5
  "author": "bam4d@mistral.ai",
6
6
  "license": "ISC",
7
7
  "type": "module",
8
8
  "main": "src/client.js",
9
+ "scripts": {
10
+ "lint": "./node_modules/.bin/eslint .",
11
+ "test": "node --experimental-vm-modules node_modules/.bin/jest"
12
+ },
13
+ "jest": {
14
+ "testPathIgnorePatterns": [
15
+ "examples"
16
+ ]
17
+ },
9
18
  "repository": {
10
19
  "type": "git",
11
20
  "url": "https://github.com/mistralai/client-js"
12
21
  },
13
- "types": "src/mistralai.d.ts",
22
+ "types": "src/client.d.ts",
14
23
  "dependencies": {
15
24
  "node-fetch": "^2.6.7"
16
25
  },
17
26
  "devDependencies": {
18
27
  "eslint": "^8.55.0",
19
28
  "eslint-config-google": "^0.14.0",
20
- "prettier": "2.8.8"
29
+ "prettier": "2.8.8",
30
+ "jest": "^29.7.0"
21
31
  }
22
32
  }
package/src/client.d.ts CHANGED
@@ -101,7 +101,11 @@ declare module '@mistralai/mistralai' {
101
101
  topP?: number,
102
102
  randomSeed?: number,
103
103
  stream?: boolean,
104
- safeMode?: boolean
104
+ /**
105
+ * @deprecated use safePrompt instead
106
+ */
107
+ safeMode?: boolean,
108
+ safePrompt?: boolean
105
109
  ): object;
106
110
 
107
111
  listModels(): Promise<ListModelsResponse>;
@@ -113,7 +117,11 @@ declare module '@mistralai/mistralai' {
113
117
  maxTokens?: number;
114
118
  topP?: number;
115
119
  randomSeed?: number;
120
+ /**
121
+ * @deprecated use safePrompt instead
122
+ */
116
123
  safeMode?: boolean;
124
+ safePrompt?: boolean;
117
125
  }): Promise<ChatCompletionResponse>;
118
126
 
119
127
  chatStream(options: {
@@ -123,7 +131,11 @@ declare module '@mistralai/mistralai' {
123
131
  maxTokens?: number;
124
132
  topP?: number;
125
133
  randomSeed?: number;
134
+ /**
135
+ * @deprecated use safePrompt instead
136
+ */
126
137
  safeMode?: boolean;
138
+ safePrompt?: boolean;
127
139
  }): AsyncGenerator<ChatCompletionResponseChunk, void, unknown>;
128
140
 
129
141
  embeddings(options: {
package/src/client.js CHANGED
@@ -1,16 +1,26 @@
1
- let fetch;
2
1
  let isNode = false;
3
- if (typeof globalThis.fetch === 'undefined') {
4
- fetch = (await import('node-fetch')).default;
5
- isNode = true;
6
- } else {
7
- fetch = globalThis.fetch;
8
- }
9
-
10
2
 
3
+ const VERSION = '0.0.3';
11
4
  const RETRY_STATUS_CODES = [429, 500, 502, 503, 504];
12
5
  const ENDPOINT = 'https://api.mistral.ai';
13
6
 
7
+ /**
8
+ * Initialize fetch
9
+ * @return {Promise<void>}
10
+ */
11
+ async function initializeFetch() {
12
+ if (typeof window === 'undefined' ||
13
+ typeof globalThis.fetch === 'undefined') {
14
+ const nodeFetch = await import('node-fetch');
15
+ fetch = nodeFetch.default;
16
+ isNode = true;
17
+ } else {
18
+ fetch = globalThis.fetch;
19
+ }
20
+ }
21
+
22
+ initializeFetch();
23
+
14
24
  /**
15
25
  * MistralAPIError
16
26
  * @return {MistralAPIError}
@@ -65,6 +75,8 @@ class MistralClient {
65
75
  const options = {
66
76
  method: method,
67
77
  headers: {
78
+ 'User-Agent': `mistral-client-js/${VERSION}`,
79
+ 'Accept': request?.stream ? 'text/event-stream' : 'application/json',
68
80
  'Content-Type': 'application/json',
69
81
  'Authorization': `Bearer ${this.apiKey}`,
70
82
  },
@@ -85,14 +97,13 @@ class MistralClient {
85
97
  // Chrome does not support async iterators yet, so polyfill it
86
98
  const asyncIterator = async function* () {
87
99
  try {
88
- const decoder = new TextDecoder();
89
100
  while (true) {
90
101
  // Read from the stream
91
102
  const {done, value} = await reader.read();
92
103
  // Exit if we're done
93
104
  if (done) return;
94
105
  // Else yield the chunk
95
- yield decoder.decode(value, {stream: true});
106
+ yield value;
96
107
  }
97
108
  } finally {
98
109
  reader.releaseLock();
@@ -104,14 +115,19 @@ class MistralClient {
104
115
  }
105
116
  return await response.json();
106
117
  } else if (RETRY_STATUS_CODES.includes(response.status)) {
107
- console.debug(`Retrying request, attempt: ${attempts + 1}`);
118
+ console.debug(
119
+ `Retrying request on response status: ${response.status}`,
120
+ `Response: ${await response.text()}`,
121
+ `Attempt: ${attempts + 1}`,
122
+ );
108
123
  // eslint-disable-next-line max-len
109
124
  await new Promise((resolve) =>
110
125
  setTimeout(resolve, Math.pow(2, (attempts + 1)) * 500),
111
126
  );
112
127
  } else {
113
128
  throw new MistralAPIError(
114
- `HTTP error! status: ${response.status}`,
129
+ `HTTP error! status: ${response.status} ` +
130
+ `Response: \n${await response.text()}`,
115
131
  );
116
132
  }
117
133
  } catch (error) {
@@ -138,7 +154,8 @@ class MistralClient {
138
154
  * @param {*} topP
139
155
  * @param {*} randomSeed
140
156
  * @param {*} stream
141
- * @param {*} safeMode
157
+ * @param {*} safeMode deprecated use safePrompt instead
158
+ * @param {*} safePrompt
142
159
  * @return {Promise<Object>}
143
160
  */
144
161
  _makeChatCompletionRequest = function(
@@ -150,6 +167,7 @@ class MistralClient {
150
167
  randomSeed,
151
168
  stream,
152
169
  safeMode,
170
+ safePrompt,
153
171
  ) {
154
172
  return {
155
173
  model: model,
@@ -159,7 +177,7 @@ class MistralClient {
159
177
  top_p: topP ?? undefined,
160
178
  random_seed: randomSeed ?? undefined,
161
179
  stream: stream ?? undefined,
162
- safe_prompt: safeMode ?? undefined,
180
+ safe_prompt: (safeMode || safePrompt) ?? undefined,
163
181
  };
164
182
  };
165
183
 
@@ -181,7 +199,8 @@ class MistralClient {
181
199
  * @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
182
200
  * @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
183
201
  * @param {*} randomSeed the random seed to use for sampling, e.g. 42
184
- * @param {*} safeMode whether to use safe mode, e.g. true
202
+ * @param {*} safeMode deprecated use safePrompt instead
203
+ * @param {*} safePrompt whether to use safe mode, e.g. true
185
204
  * @return {Promise<Object>}
186
205
  */
187
206
  chat = async function({
@@ -192,6 +211,7 @@ class MistralClient {
192
211
  topP,
193
212
  randomSeed,
194
213
  safeMode,
214
+ safePrompt,
195
215
  }) {
196
216
  const request = this._makeChatCompletionRequest(
197
217
  model,
@@ -202,6 +222,7 @@ class MistralClient {
202
222
  randomSeed,
203
223
  false,
204
224
  safeMode,
225
+ safePrompt,
205
226
  );
206
227
  const response = await this._request(
207
228
  'post',
@@ -220,7 +241,8 @@ class MistralClient {
220
241
  * @param {*} maxTokens the maximum number of tokens to generate, e.g. 100
221
242
  * @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9
222
243
  * @param {*} randomSeed the random seed to use for sampling, e.g. 42
223
- * @param {*} safeMode whether to use safe mode, e.g. true
244
+ * @param {*} safeMode deprecated use safePrompt instead
245
+ * @param {*} safePrompt whether to use safe mode, e.g. true
224
246
  * @return {Promise<Object>}
225
247
  */
226
248
  chatStream = async function* ({
@@ -231,6 +253,7 @@ class MistralClient {
231
253
  topP,
232
254
  randomSeed,
233
255
  safeMode,
256
+ safePrompt,
234
257
  }) {
235
258
  const request = this._makeChatCompletionRequest(
236
259
  model,
@@ -241,6 +264,7 @@ class MistralClient {
241
264
  randomSeed,
242
265
  true,
243
266
  safeMode,
267
+ safePrompt,
244
268
  );
245
269
  const response = await this._request(
246
270
  'post',
@@ -249,9 +273,9 @@ class MistralClient {
249
273
  );
250
274
 
251
275
  let buffer = '';
252
-
276
+ const decoder = new TextDecoder();
253
277
  for await (const chunk of response) {
254
- buffer += chunk;
278
+ buffer += decoder.decode(chunk, {stream: true});
255
279
  let firstNewline;
256
280
  while ((firstNewline = buffer.indexOf('\n')) !== -1) {
257
281
  const chunkLine = buffer.substring(0, firstNewline);
@@ -0,0 +1,179 @@
1
+ import MistralClient from '../src/client';
2
+ import {
3
+ mockListModels,
4
+ mockFetch,
5
+ mockChatResponseStreamingPayload,
6
+ mockEmbeddingRequest,
7
+ mockEmbeddingResponsePayload,
8
+ mockChatResponsePayload,
9
+ mockFetchStream,
10
+ } from './utils';
11
+
12
+ // Test the list models endpoint
13
+ describe('Mistral Client', () => {
14
+ let client;
15
+ beforeEach(() => {
16
+ client = new MistralClient();
17
+ });
18
+
19
+ describe('chat()', () => {
20
+ it('should return a chat response object', async() => {
21
+ // Mock the fetch function
22
+ const mockResponse = mockChatResponsePayload();
23
+ globalThis.fetch = mockFetch(200, mockResponse);
24
+
25
+ const response = await client.chat({
26
+ model: 'mistral-small',
27
+ messages: [
28
+ {
29
+ role: 'user',
30
+ content: 'What is the best French cheese?',
31
+ },
32
+ ],
33
+ });
34
+ expect(response).toEqual(mockResponse);
35
+ });
36
+
37
+ it('should return a chat response object if safeMode is set', async() => {
38
+ // Mock the fetch function
39
+ const mockResponse = mockChatResponsePayload();
40
+ globalThis.fetch = mockFetch(200, mockResponse);
41
+
42
+ const response = await client.chat({
43
+ model: 'mistral-small',
44
+ messages: [
45
+ {
46
+ role: 'user',
47
+ content: 'What is the best French cheese?',
48
+ },
49
+ ],
50
+ safeMode: true,
51
+ });
52
+ expect(response).toEqual(mockResponse);
53
+ });
54
+
55
+ it('should return a chat response object if safePrompt is set', async() => {
56
+ // Mock the fetch function
57
+ const mockResponse = mockChatResponsePayload();
58
+ globalThis.fetch = mockFetch(200, mockResponse);
59
+
60
+ const response = await client.chat({
61
+ model: 'mistral-small',
62
+ messages: [
63
+ {
64
+ role: 'user',
65
+ content: 'What is the best French cheese?',
66
+ },
67
+ ],
68
+ safePrompt: true,
69
+ });
70
+ expect(response).toEqual(mockResponse);
71
+ });
72
+ });
73
+
74
+ describe('chatStream()', () => {
75
+ it('should return parsed, streamed response', async() => {
76
+ // Mock the fetch function
77
+ const mockResponse = mockChatResponseStreamingPayload();
78
+ globalThis.fetch = mockFetchStream(200, mockResponse);
79
+
80
+ const response = await client.chatStream({
81
+ model: 'mistral-small',
82
+ messages: [
83
+ {
84
+ role: 'user',
85
+ content: 'What is the best French cheese?',
86
+ },
87
+ ],
88
+ });
89
+
90
+ const parsedResponse = [];
91
+ for await (const r of response) {
92
+ parsedResponse.push(r);
93
+ }
94
+
95
+ expect(parsedResponse.length).toEqual(11);
96
+ });
97
+
98
+ it('should return parsed, streamed response with safeMode', async() => {
99
+ // Mock the fetch function
100
+ const mockResponse = mockChatResponseStreamingPayload();
101
+ globalThis.fetch = mockFetchStream(200, mockResponse);
102
+
103
+ const response = await client.chatStream({
104
+ model: 'mistral-small',
105
+ messages: [
106
+ {
107
+ role: 'user',
108
+ content: 'What is the best French cheese?',
109
+ },
110
+ ],
111
+ safeMode: true,
112
+ });
113
+
114
+ const parsedResponse = [];
115
+ for await (const r of response) {
116
+ parsedResponse.push(r);
117
+ }
118
+
119
+ expect(parsedResponse.length).toEqual(11);
120
+ });
121
+
122
+ it('should return parsed, streamed response with safePrompt', async() => {
123
+ // Mock the fetch function
124
+ const mockResponse = mockChatResponseStreamingPayload();
125
+ globalThis.fetch = mockFetchStream(200, mockResponse);
126
+
127
+ const response = await client.chatStream({
128
+ model: 'mistral-small',
129
+ messages: [
130
+ {
131
+ role: 'user',
132
+ content: 'What is the best French cheese?',
133
+ },
134
+ ],
135
+ safePrompt: true,
136
+ });
137
+
138
+ const parsedResponse = [];
139
+ for await (const r of response) {
140
+ parsedResponse.push(r);
141
+ }
142
+
143
+ expect(parsedResponse.length).toEqual(11);
144
+ });
145
+ });
146
+
147
+ describe('embeddings()', () => {
148
+ it('should return embeddings', async() => {
149
+ // Mock the fetch function
150
+ const mockResponse = mockEmbeddingResponsePayload();
151
+ globalThis.fetch = mockFetch(200, mockResponse);
152
+
153
+ const response = await client.embeddings(mockEmbeddingRequest);
154
+ expect(response).toEqual(mockResponse);
155
+ });
156
+ });
157
+
158
+ describe('embeddings() batched', () => {
159
+ it('should return batched embeddings', async() => {
160
+ // Mock the fetch function
161
+ const mockResponse = mockEmbeddingResponsePayload(10);
162
+ globalThis.fetch = mockFetch(200, mockResponse);
163
+
164
+ const response = await client.embeddings(mockEmbeddingRequest);
165
+ expect(response).toEqual(mockResponse);
166
+ });
167
+ });
168
+
169
+ describe('listModels()', () => {
170
+ it('should return a list of models', async() => {
171
+ // Mock the fetch function
172
+ const mockResponse = mockListModels();
173
+ globalThis.fetch = mockFetch(200, mockResponse);
174
+
175
+ const response = await client.listModels();
176
+ expect(response).toEqual(mockResponse);
177
+ });
178
+ });
179
+ });
package/tests/utils.js ADDED
@@ -0,0 +1,257 @@
1
+ import jest from 'jest-mock';
2
+
3
+ /**
4
+ * Mock the fetch function
5
+ * @param {*} status
6
+ * @param {*} payload
7
+ * @return {Object}
8
+ */
9
+ export function mockFetch(status, payload) {
10
+ return jest.fn(() =>
11
+ Promise.resolve({
12
+ json: () => Promise.resolve(payload),
13
+ text: () => Promise.resolve(JSON.stringify(payload)),
14
+ status,
15
+ ok: status >= 200 && status < 300,
16
+ }),
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Mock fetch stream
22
+ * @param {*} status
23
+ * @param {*} payload
24
+ * @return {Object}
25
+ */
26
+ export function mockFetchStream(status, payload) {
27
+ const asyncIterator = async function* () {
28
+ while (true) {
29
+ // Read from the stream
30
+ const value = payload.shift();
31
+ // Exit if we're done
32
+ if (!value) return;
33
+ // Else yield the chunk
34
+ yield value;
35
+ }
36
+ };
37
+
38
+ return jest.fn(() =>
39
+ Promise.resolve({
40
+ // body is a ReadableStream of the objects in payload list
41
+ body: asyncIterator(),
42
+ status,
43
+ ok: status >= 200 && status < 300,
44
+ }),
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Mock models list
50
+ * @return {Object}
51
+ */
52
+ export function mockListModels() {
53
+ return {
54
+ object: 'list',
55
+ data: [
56
+ {
57
+ id: 'mistral-medium',
58
+ object: 'model',
59
+ created: 1703186988,
60
+ owned_by: 'mistralai',
61
+ root: null,
62
+ parent: null,
63
+ permission: [
64
+ {
65
+ id: 'modelperm-15bebaf316264adb84b891bf06a84933',
66
+ object: 'model_permission',
67
+ created: 1703186988,
68
+ allow_create_engine: false,
69
+ allow_sampling: true,
70
+ allow_logprobs: false,
71
+ allow_search_indices: false,
72
+ allow_view: true,
73
+ allow_fine_tuning: false,
74
+ organization: '*',
75
+ group: null,
76
+ is_blocking: false,
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ id: 'mistral-small',
82
+ object: 'model',
83
+ created: 1703186988,
84
+ owned_by: 'mistralai',
85
+ root: null,
86
+ parent: null,
87
+ permission: [
88
+ {
89
+ id: 'modelperm-d0dced5c703242fa862f4ca3f241c00e',
90
+ object: 'model_permission',
91
+ created: 1703186988,
92
+ allow_create_engine: false,
93
+ allow_sampling: true,
94
+ allow_logprobs: false,
95
+ allow_search_indices: false,
96
+ allow_view: true,
97
+ allow_fine_tuning: false,
98
+ organization: '*',
99
+ group: null,
100
+ is_blocking: false,
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ id: 'mistral-tiny',
106
+ object: 'model',
107
+ created: 1703186988,
108
+ owned_by: 'mistralai',
109
+ root: null,
110
+ parent: null,
111
+ permission: [
112
+ {
113
+ id: 'modelperm-0e64e727c3a94f17b29f8895d4be2910',
114
+ object: 'model_permission',
115
+ created: 1703186988,
116
+ allow_create_engine: false,
117
+ allow_sampling: true,
118
+ allow_logprobs: false,
119
+ allow_search_indices: false,
120
+ allow_view: true,
121
+ allow_fine_tuning: false,
122
+ organization: '*',
123
+ group: null,
124
+ is_blocking: false,
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ id: 'mistral-embed',
130
+ object: 'model',
131
+ created: 1703186988,
132
+ owned_by: 'mistralai',
133
+ root: null,
134
+ parent: null,
135
+ permission: [
136
+ {
137
+ id: 'modelperm-ebdff9046f524e628059447b5932e3ad',
138
+ object: 'model_permission',
139
+ created: 1703186988,
140
+ allow_create_engine: false,
141
+ allow_sampling: true,
142
+ allow_logprobs: false,
143
+ allow_search_indices: false,
144
+ allow_view: true,
145
+ allow_fine_tuning: false,
146
+ organization: '*',
147
+ group: null,
148
+ is_blocking: false,
149
+ },
150
+ ],
151
+ },
152
+ ],
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Mock chat completion object
158
+ * @return {Object}
159
+ */
160
+ export function mockChatResponsePayload() {
161
+ return {
162
+ id: 'chat-98c8c60e3fbf4fc49658eddaf447357c',
163
+ object: 'chat.completion',
164
+ created: 1703165682,
165
+ choices: [
166
+ {
167
+ finish_reason: 'stop',
168
+ message: {
169
+ role: 'assistant',
170
+ content: 'What is the best French cheese?',
171
+ },
172
+ index: 0,
173
+ },
174
+ ],
175
+ model: 'mistral-small',
176
+ usage: {prompt_tokens: 90, total_tokens: 90, completion_tokens: 0},
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Mock chat completion stream
182
+ * @return {Object}
183
+ */
184
+ export function mockChatResponseStreamingPayload() {
185
+ const encoder = new TextEncoder();
186
+ const firstMessage =
187
+ [encoder.encode('data: ' +
188
+ JSON.stringify({
189
+ id: 'cmpl-8cd9019d21ba490aa6b9740f5d0a883e',
190
+ model: 'mistral-small',
191
+ choices: [
192
+ {
193
+ index: 0,
194
+ delta: {role: 'assistant'},
195
+ finish_reason: null,
196
+ },
197
+ ],
198
+ }) +
199
+ '\n\n')];
200
+ const lastMessage = [encoder.encode('data: [DONE]\n\n')];
201
+
202
+ const dataMessages = [];
203
+ for (let i = 0; i < 10; i++) {
204
+ dataMessages.push(encoder.encode(
205
+ 'data: ' +
206
+ JSON.stringify({
207
+ id: 'cmpl-8cd9019d21ba490aa6b9740f5d0a883e',
208
+ object: 'chat.completion.chunk',
209
+ created: 1703168544,
210
+ model: 'mistral-small',
211
+ choices: [
212
+ {
213
+ index: i,
214
+ delta: {content: `stream response ${i}`},
215
+ finish_reason: null,
216
+ },
217
+ ],
218
+ }) +
219
+ '\n\n'),
220
+ );
221
+ }
222
+
223
+ return firstMessage.concat(dataMessages).concat(lastMessage);
224
+ }
225
+
226
+ /**
227
+ * Mock embeddings response
228
+ * @param {number} batchSize
229
+ * @return {Object}
230
+ */
231
+ export function mockEmbeddingResponsePayload(batchSize = 1) {
232
+ return {
233
+ id: 'embd-98c8c60e3fbf4fc49658eddaf447357c',
234
+ object: 'list',
235
+ data:
236
+ [
237
+ {
238
+ object: 'embedding',
239
+ embedding: [-0.018585205078125, 0.027099609375, 0.02587890625],
240
+ index: 0,
241
+ },
242
+ ] * batchSize,
243
+ model: 'mistral-embed',
244
+ usage: {prompt_tokens: 90, total_tokens: 90, completion_tokens: 0},
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Mock embeddings request payload
250
+ * @return {Object}
251
+ */
252
+ export function mockEmbeddingRequest() {
253
+ return {
254
+ model: 'mistral-embed',
255
+ input: 'embed',
256
+ };
257
+ }