From 74faf139644047340ab60bac308fc8b5141cf4d8 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Wed, 1 May 2024 10:19:37 +0700 Subject: [PATCH 1/6] feat: add signal support and fix timeout, closes #5 --- src/client.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index 6828820..5f96692 100644 --- a/src/client.js +++ b/src/client.js @@ -69,9 +69,10 @@ class MistralClient { * @param {*} method * @param {*} path * @param {*} request + * @param {*} signal * @return {Promise<*>} */ - _request = async function(method, path, request) { + _request = async function(method, path, request, signal) { const url = `${this.endpoint}/${path}`; const options = { method: method, @@ -82,7 +83,7 @@ class MistralClient { 'Authorization': `Bearer ${this.apiKey}`, }, body: method !== 'get' ? JSON.stringify(request) : null, - timeout: this.timeout * 1000, + signal: signal ?? AbortSignal.timeout(this.timeout * 1000), }; for (let attempts = 0; attempts < this.maxRetries; attempts++) { @@ -221,6 +222,8 @@ class MistralClient { * @param {*} safePrompt whether to use safe mode, e.g. true * @param {*} toolChoice the tool to use, e.g. 'auto' * @param {*} responseFormat the format of the response, e.g. 'json_format' + * @param {*} [signal=AbortSignal.timeout(...)] - + * An optional AbortSignal instance to control the operation. * @return {Promise} */ chat = async function({ @@ -233,6 +236,7 @@ class MistralClient { randomSeed, safeMode, safePrompt, + signal, toolChoice, responseFormat, }) { @@ -254,6 +258,7 @@ class MistralClient { 'post', 'v1/chat/completions', request, + signal, ); return response; }; @@ -272,6 +277,8 @@ class MistralClient { * @param {*} safePrompt whether to use safe mode, e.g. true * @param {*} toolChoice the tool to use, e.g. 'auto' * @param {*} responseFormat the format of the response, e.g. 'json_format' + * @param {*} [signal=AbortSignal.timeout(...)] - + * An optional AbortSignal instance to control the operation. * @return {Promise} */ chatStream = async function* ({ @@ -284,6 +291,7 @@ class MistralClient { randomSeed, safeMode, safePrompt, + signal, toolChoice, responseFormat, }) { @@ -305,6 +313,7 @@ class MistralClient { 'post', 'v1/chat/completions', request, + signal, ); let buffer = ''; From de969ef2ee87cbf9be0f6b985630f0ef06797bad Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Thu, 2 May 2024 11:44:58 +0700 Subject: [PATCH 2/6] feat: add combineSignals --- src/client.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 5f96692..9f56ef2 100644 --- a/src/client.js +++ b/src/client.js @@ -23,6 +23,29 @@ class MistralAPIError extends Error { } }; +/** + * @param {Array} signals to merge + * @return {AbortSignal} signal which will abort when any of signals abort + */ +function combineSignals(signals) { + const controller = new AbortController(); + signals.forEach((signal) => { + if (!signal) { + return; + } + + signal.addEventListener('abort', () => { + controller.abort(signal.reason); + }, {once: true}); + + if (signal.aborted) { + controller.abort(signal.reason); + } + }); + + return controller.signal; +} + /** * MistralClient * @return {MistralClient} @@ -83,7 +106,8 @@ class MistralClient { 'Authorization': `Bearer ${this.apiKey}`, }, body: method !== 'get' ? JSON.stringify(request) : null, - signal: signal ?? AbortSignal.timeout(this.timeout * 1000), + signal: combineSignals( + [AbortSignal.timeout(this.timeout * 1000), signal]), }; for (let attempts = 0; attempts < this.maxRetries; attempts++) { From f7ae679863daf8ab24f6132d165e2e038e6f4bb9 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Thu, 2 May 2024 11:52:44 +0700 Subject: [PATCH 3/6] feat: update signal docs --- src/client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index 9f56ef2..8d82730 100644 --- a/src/client.js +++ b/src/client.js @@ -246,8 +246,8 @@ class MistralClient { * @param {*} safePrompt whether to use safe mode, e.g. true * @param {*} toolChoice the tool to use, e.g. 'auto' * @param {*} responseFormat the format of the response, e.g. 'json_format' - * @param {*} [signal=AbortSignal.timeout(...)] - - * An optional AbortSignal instance to control the operation. + * @param {*} [signal] - optional AbortSignal instance to control request + * The signal will be combined with default timeout signal * @return {Promise} */ chat = async function({ @@ -301,8 +301,8 @@ class MistralClient { * @param {*} safePrompt whether to use safe mode, e.g. true * @param {*} toolChoice the tool to use, e.g. 'auto' * @param {*} responseFormat the format of the response, e.g. 'json_format' - * @param {*} [signal=AbortSignal.timeout(...)] - - * An optional AbortSignal instance to control the operation. + * @param {*} [signal] - optional AbortSignal instance to control request + * The signal will be combined with default timeout signal * @return {Promise} */ chatStream = async function* ({ From 4b70df4b030afb2e4ce11872e69d6e75ebd7cc49 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Mon, 6 May 2024 08:28:34 +0700 Subject: [PATCH 4/6] refactor: move signal from data to options --- src/client.js | 82 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/client.js b/src/client.js index 8d82730..514a8df 100644 --- a/src/client.js +++ b/src/client.js @@ -233,21 +233,30 @@ class MistralClient { }; /** - * A chat endpoint without streaming - * @param {*} model the name of the model to chat with, e.g. mistral-tiny - * @param {*} messages an array of messages to chat with, e.g. - * [{role: 'user', content: 'What is the best French cheese?'}] - * @param {*} tools a list of tools to use. - * @param {*} temperature the temperature to use for sampling, e.g. 0.5 - * @param {*} maxTokens the maximum number of tokens to generate, e.g. 100 - * @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9 - * @param {*} randomSeed the random seed to use for sampling, e.g. 42 - * @param {*} safeMode deprecated use safePrompt instead - * @param {*} safePrompt whether to use safe mode, e.g. true - * @param {*} toolChoice the tool to use, e.g. 'auto' - * @param {*} responseFormat the format of the response, e.g. 'json_format' - * @param {*} [signal] - optional AbortSignal instance to control request - * The signal will be combined with default timeout signal + * A chat endpoint without streaming. + * + * @param {Object} data - The main chat configuration. + * @param {*} data.model - the name of the model to chat with, + * e.g. mistral-tiny + * @param {*} data.messages - an array of messages to chat with, e.g. + * [{role: 'user', content: 'What is the best + * French cheese?'}] + * @param {*} data.tools - a list of tools to use. + * @param {*} data.temperature - the temperature to use for sampling, e.g. 0.5 + * @param {*} data.maxTokens - the maximum number of tokens to generate, + * e.g. 100 + * @param {*} data.topP - the cumulative probability of tokens to generate, + * e.g. 0.9 + * @param {*} data.randomSeed - the random seed to use for sampling, e.g. 42 + * @param {*} data.safeMode - deprecated use safePrompt instead + * @param {*} data.safePrompt - whether to use safe mode, e.g. true + * @param {*} data.toolChoice - the tool to use, e.g. 'auto' + * @param {*} data.responseFormat - the format of the response, + * e.g. 'json_format' + * @param {Object} options - Additional operational options. + * @param {*} [options.signal] - optional AbortSignal instance to control + * request The signal will be combined with + * default timeout signal * @return {Promise} */ chat = async function({ @@ -260,10 +269,9 @@ class MistralClient { randomSeed, safeMode, safePrompt, - signal, toolChoice, responseFormat, - }) { + }, {signal}) { const request = this._makeChatCompletionRequest( model, messages, @@ -289,20 +297,29 @@ class MistralClient { /** * A chat endpoint that streams responses. - * @param {*} model the name of the model to chat with, e.g. mistral-tiny - * @param {*} messages an array of messages to chat with, e.g. - * [{role: 'user', content: 'What is the best French cheese?'}] - * @param {*} tools a list of tools to use. - * @param {*} temperature the temperature to use for sampling, e.g. 0.5 - * @param {*} maxTokens the maximum number of tokens to generate, e.g. 100 - * @param {*} topP the cumulative probability of tokens to generate, e.g. 0.9 - * @param {*} randomSeed the random seed to use for sampling, e.g. 42 - * @param {*} safeMode deprecated use safePrompt instead - * @param {*} safePrompt whether to use safe mode, e.g. true - * @param {*} toolChoice the tool to use, e.g. 'auto' - * @param {*} responseFormat the format of the response, e.g. 'json_format' - * @param {*} [signal] - optional AbortSignal instance to control request - * The signal will be combined with default timeout signal + * + * @param {Object} data - The main chat configuration. + * @param {*} data.model - the name of the model to chat with, + * e.g. mistral-tiny + * @param {*} data.messages - an array of messages to chat with, e.g. + * [{role: 'user', content: 'What is the best + * French cheese?'}] + * @param {*} data.tools - a list of tools to use. + * @param {*} data.temperature - the temperature to use for sampling, e.g. 0.5 + * @param {*} data.maxTokens - the maximum number of tokens to generate, + * e.g. 100 + * @param {*} data.topP - the cumulative probability of tokens to generate, + * e.g. 0.9 + * @param {*} data.randomSeed - the random seed to use for sampling, e.g. 42 + * @param {*} data.safeMode - deprecated use safePrompt instead + * @param {*} data.safePrompt - whether to use safe mode, e.g. true + * @param {*} data.toolChoice - the tool to use, e.g. 'auto' + * @param {*} data.responseFormat - the format of the response, + * e.g. 'json_format' + * @param {Object} options - Additional operational options. + * @param {*} [options.signal] - optional AbortSignal instance to control + * request The signal will be combined with + * default timeout signal * @return {Promise} */ chatStream = async function* ({ @@ -315,10 +332,9 @@ class MistralClient { randomSeed, safeMode, safePrompt, - signal, toolChoice, responseFormat, - }) { + }, {signal}) { const request = this._makeChatCompletionRequest( model, messages, From c9afae5bd7280be0b8242421021e39d8b452637a Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Mon, 6 May 2024 08:31:02 +0700 Subject: [PATCH 5/6] fix: set default option --- src/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index 514a8df..d429d54 100644 --- a/src/client.js +++ b/src/client.js @@ -271,7 +271,7 @@ class MistralClient { safePrompt, toolChoice, responseFormat, - }, {signal}) { + }, {signal} = {}) { const request = this._makeChatCompletionRequest( model, messages, @@ -334,7 +334,7 @@ class MistralClient { safePrompt, toolChoice, responseFormat, - }, {signal}) { + }, {signal} = {}) { const request = this._makeChatCompletionRequest( model, messages, From 3ce76a0d2712a497075e7ba0e85acd45e1124630 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Mon, 6 May 2024 08:47:28 +0700 Subject: [PATCH 6/6] feat: add node 22 to CI --- .github/workflows/build_publish.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_publish.yaml b/.github/workflows/build_publish.yaml index a6f9a52..002812c 100644 --- a/.github/workflows/build_publish.yaml +++ b/.github/workflows/build_publish.yaml @@ -3,7 +3,7 @@ name: Build and Publish on: push: branches: ["main"] - + # We only deploy on tags and main branch tags: # Only run on tags that match the following regex @@ -14,13 +14,13 @@ on: pull_request: jobs: - + lint_and_test: runs-on: ubuntu-latest strategy: matrix: - node-version: [18, 20] + node-version: [18, 20, 22] steps: # Checkout the repository @@ -72,4 +72,3 @@ jobs: sed -i 's/VERSION = '\''0.0.1'\''/VERSION = '\''${{ github.ref_name }}'\''/g' src/client.js npm publish - \ No newline at end of file