@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 +3 -2
- package/.github/workflows/build_publish.yaml +15 -5
- package/README.md +33 -8
- package/examples/chat-react/package-lock.json +9 -7
- package/examples/chat-react/package.json +0 -5
- package/examples/package-lock.json +1 -0
- package/package.json +13 -3
- package/src/client.d.ts +13 -1
- package/src/client.js +42 -18
- package/tests/client.test.js +179 -0
- package/tests/utils.js +257 -0
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
|
-
|
|
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:
|
|
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
|
-
#
|
|
41
|
+
# Eslint
|
|
38
42
|
- name: ESlint check
|
|
39
43
|
run: |
|
|
40
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
88
|
+
### API key setup
|
|
80
89
|
|
|
81
90
|
Running the examples requires a Mistral AI API key.
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
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=[
|
|
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
|
|
112
|
+
You can then run the examples without appending the API key:
|
|
95
113
|
|
|
96
114
|
```bash
|
|
97
|
-
|
|
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": "
|
|
16027
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-
|
|
16028
|
-
"integrity": "sha512-
|
|
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": ">=
|
|
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": "
|
|
28468
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-
|
|
28469
|
-
"integrity": "sha512-
|
|
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": {
|
package/package.json
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mistralai/mistralai",
|
|
3
|
-
"version": "0.0.
|
|
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/
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
+
}
|