From 18a92d9a4e356129d48de33a828f077df7863c4f Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 22 Jan 2025 15:54:34 +0000 Subject: [PATCH] feat(mistral-ai): add app with create chat completion action --- .../actions/create-chat-completion/index.js | 157 ++++++++++++++++++ .../src/apps/mistral-ai/actions/index.js | 3 + .../src/apps/mistral-ai/assets/favicon.svg | 32 ++++ .../backend/src/apps/mistral-ai/auth/index.js | 34 ++++ .../apps/mistral-ai/auth/is-still-verified.js | 6 + .../mistral-ai/auth/verify-credentials.js | 5 + .../apps/mistral-ai/common/add-auth-header.js | 9 + .../src/apps/mistral-ai/dynamic-data/index.js | 3 + .../dynamic-data/list-models/index.js | 17 ++ packages/backend/src/apps/mistral-ai/index.js | 20 +++ .../src/models/__snapshots__/app.test.js.snap | 1 + packages/docs/pages/.vitepress/config.js | 9 + .../docs/pages/apps/mistral-ai/actions.md | 12 ++ .../docs/pages/apps/mistral-ai/connection.md | 8 + packages/docs/pages/guide/available-apps.md | 1 + .../docs/pages/public/favicons/mistral-ai.svg | 32 ++++ 16 files changed, 349 insertions(+) create mode 100644 packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js create mode 100644 packages/backend/src/apps/mistral-ai/actions/index.js create mode 100644 packages/backend/src/apps/mistral-ai/assets/favicon.svg create mode 100644 packages/backend/src/apps/mistral-ai/auth/index.js create mode 100644 packages/backend/src/apps/mistral-ai/auth/is-still-verified.js create mode 100644 packages/backend/src/apps/mistral-ai/auth/verify-credentials.js create mode 100644 packages/backend/src/apps/mistral-ai/common/add-auth-header.js create mode 100644 packages/backend/src/apps/mistral-ai/dynamic-data/index.js create mode 100644 packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js create mode 100644 packages/backend/src/apps/mistral-ai/index.js create mode 100644 packages/docs/pages/apps/mistral-ai/actions.md create mode 100644 packages/docs/pages/apps/mistral-ai/connection.md create mode 100644 packages/docs/pages/public/favicons/mistral-ai.svg diff --git a/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js b/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js new file mode 100644 index 00000000..8e983103 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js @@ -0,0 +1,157 @@ +import defineAction from '../../../../helpers/define-action.js'; + +const castFloatOrUndefined = (value) => { + return value === '' ? undefined : parseFloat(value); +}; + +export default defineAction({ + name: 'Create chat completion', + key: 'createChatCompletion', + description: 'Creates a chat completion.', + arguments: [ + { + label: 'Model', + key: 'model', + type: 'dropdown', + required: true, + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listModels', + }, + ], + }, + }, + { + label: 'Messages', + key: 'messages', + type: 'dynamic', + required: true, + description: + 'The prompt(s) to generate completions for, encoded as a list of dict with role and content.', + value: [{ role: 'system', body: '' }], + fields: [ + { + label: 'Role', + key: 'role', + type: 'dropdown', + required: true, + options: [ + { + label: 'System', + value: 'system', + }, + { + label: 'Assistant', + value: 'assistant', + }, + { + label: 'User', + value: 'user', + }, + ], + }, + { + label: 'Content', + key: 'content', + type: 'string', + required: true, + variables: true, + }, + ], + }, + { + label: 'Temperature', + key: 'temperature', + type: 'string', + required: false, + variables: true, + description: + 'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.', + }, + { + label: 'Maximum tokens', + key: 'maxTokens', + type: 'string', + required: false, + variables: true, + description: `The maximum number of tokens to generate in the completion. The token count of your prompt plus max_tokens cannot exceed the model's context length.`, + }, + { + label: 'Stop sequences', + key: 'stopSequences', + type: 'dynamic', + required: false, + variables: true, + description: 'Stop generation if one of these tokens is detected', + fields: [ + { + label: 'Stop sequence', + key: 'stopSequence', + type: 'string', + required: false, + variables: true, + }, + ], + }, + { + label: 'Top P', + key: 'topP', + type: 'string', + required: false, + variables: true, + description: + 'Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.', + }, + { + label: 'Frequency Penalty', + key: 'frequencyPenalty', + type: 'string', + required: false, + variables: true, + description: `Frequency_penalty penalizes the repetition of words based on their frequency in the generated text. A higher frequency penalty discourages the model from repeating words that have already appeared frequently in the output, promoting diversity and reducing repetition.`, + }, + { + label: 'Presence Penalty', + key: 'presencePenalty', + type: 'string', + required: false, + variables: true, + description: `Presence penalty determines how much the model penalizes the repetition of words or phrases. A higher presence penalty encourages the model to use a wider variety of words and phrases, making the output more diverse and creative.`, + }, + ], + + async run($) { + const nonEmptyStopSequences = $.step.parameters.stopSequences + .filter(({ stopSequence }) => stopSequence) + .map(({ stopSequence }) => stopSequence); + + const messages = $.step.parameters.messages.map((message) => ({ + role: message.role, + content: message.content, + })); + + const payload = { + model: $.step.parameters.model, + messages, + stop: nonEmptyStopSequences, + temperature: castFloatOrUndefined($.step.parameters.temperature), + max_tokens: castFloatOrUndefined($.step.parameters.maxTokens), + top_p: castFloatOrUndefined($.step.parameters.topP), + frequency_penalty: castFloatOrUndefined( + $.step.parameters.frequencyPenalty + ), + presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty), + }; + + const { data } = await $.http.post('/v1/chat/completions', payload); + + $.setActionItem({ + raw: data, + }); + }, +}); diff --git a/packages/backend/src/apps/mistral-ai/actions/index.js b/packages/backend/src/apps/mistral-ai/actions/index.js new file mode 100644 index 00000000..cc0e0532 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/actions/index.js @@ -0,0 +1,3 @@ +import createChatCompletion from './create-chat-completion/index.js'; + +export default [createChatCompletion]; diff --git a/packages/backend/src/apps/mistral-ai/assets/favicon.svg b/packages/backend/src/apps/mistral-ai/assets/favicon.svg new file mode 100644 index 00000000..3f583306 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/assets/favicon.svg @@ -0,0 +1,32 @@ + + + Mistral AI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/backend/src/apps/mistral-ai/auth/index.js b/packages/backend/src/apps/mistral-ai/auth/index.js new file mode 100644 index 00000000..429699eb --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/auth/index.js @@ -0,0 +1,34 @@ +import verifyCredentials from './verify-credentials.js'; +import isStillVerified from './is-still-verified.js'; + +export default { + fields: [ + { + key: 'screenName', + label: 'Screen Name', + type: 'string', + required: true, + readOnly: false, + value: null, + placeholder: null, + description: + 'Screen name of your connection to be used on Automatisch UI.', + clickToCopy: false, + }, + { + key: 'apiKey', + label: 'API Key', + type: 'string', + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'Mistral AI API key of your account.', + docUrl: 'https://automatisch.io/docs/mistral-ai#api-key', + clickToCopy: false, + }, + ], + + verifyCredentials, + isStillVerified, +}; diff --git a/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js b/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js new file mode 100644 index 00000000..3e6c9095 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js @@ -0,0 +1,6 @@ +const isStillVerified = async ($) => { + await $.http.get('/v1/models'); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js b/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js new file mode 100644 index 00000000..7f43f884 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js @@ -0,0 +1,5 @@ +const verifyCredentials = async ($) => { + await $.http.get('/v1/models'); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/mistral-ai/common/add-auth-header.js b/packages/backend/src/apps/mistral-ai/common/add-auth-header.js new file mode 100644 index 00000000..f9f5acba --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/common/add-auth-header.js @@ -0,0 +1,9 @@ +const addAuthHeader = ($, requestConfig) => { + if ($.auth.data?.apiKey) { + requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/mistral-ai/dynamic-data/index.js b/packages/backend/src/apps/mistral-ai/dynamic-data/index.js new file mode 100644 index 00000000..6db48046 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/dynamic-data/index.js @@ -0,0 +1,3 @@ +import listModels from './list-models/index.js'; + +export default [listModels]; diff --git a/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js b/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js new file mode 100644 index 00000000..a8e81538 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js @@ -0,0 +1,17 @@ +export default { + name: 'List models', + key: 'listModels', + + async run($) { + const response = await $.http.get('/v1/models'); + + const models = response.data.data.map((model) => { + return { + value: model.id, + name: model.id, + }; + }); + + return { data: models }; + }, +}; diff --git a/packages/backend/src/apps/mistral-ai/index.js b/packages/backend/src/apps/mistral-ai/index.js new file mode 100644 index 00000000..08f9e4b5 --- /dev/null +++ b/packages/backend/src/apps/mistral-ai/index.js @@ -0,0 +1,20 @@ +import defineApp from '../../helpers/define-app.js'; +import addAuthHeader from './common/add-auth-header.js'; +import auth from './auth/index.js'; +import actions from './actions/index.js'; +import dynamicData from './dynamic-data/index.js'; + +export default defineApp({ + name: 'Mistral AI', + key: 'mistral-ai', + baseUrl: 'https://mistral.ai', + apiBaseUrl: 'https://api.mistral.ai', + iconUrl: '{BASE_URL}/apps/mistral-ai/assets/favicon.svg', + authDocUrl: '{DOCS_URL}/apps/mistral-ai/connection', + primaryColor: '#000000', + supportsConnections: true, + beforeRequest: [addAuthHeader], + auth, + actions, + dynamicData, +}); diff --git a/packages/backend/src/models/__snapshots__/app.test.js.snap b/packages/backend/src/models/__snapshots__/app.test.js.snap index 38324be3..346d917e 100644 --- a/packages/backend/src/models/__snapshots__/app.test.js.snap +++ b/packages/backend/src/models/__snapshots__/app.test.js.snap @@ -37,6 +37,7 @@ exports[`App model > list should have list of applications keys 1`] = ` "mailerlite", "mattermost", "miro", + "mistral-ai", "notion", "ntfy", "odoo", diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 0fd2511b..dc16c3cd 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -313,6 +313,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/miro/connection' }, ], }, + { + text: 'Mistral AI', + collapsible: true, + collapsed: true, + items: [ + { text: 'Actions', link: '/apps/mistral-ai/actions' }, + { text: 'Connection', link: '/apps/mistral-ai/connection' }, + ], + }, { text: 'Notion', collapsible: true, diff --git a/packages/docs/pages/apps/mistral-ai/actions.md b/packages/docs/pages/apps/mistral-ai/actions.md new file mode 100644 index 00000000..82f3207e --- /dev/null +++ b/packages/docs/pages/apps/mistral-ai/actions.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/mistral-ai.svg +items: + - name: Create chat completion + desc: Creates a chat completion. +--- + + + + diff --git a/packages/docs/pages/apps/mistral-ai/connection.md b/packages/docs/pages/apps/mistral-ai/connection.md new file mode 100644 index 00000000..41201303 --- /dev/null +++ b/packages/docs/pages/apps/mistral-ai/connection.md @@ -0,0 +1,8 @@ +# Mistral AI + +1. Go to [Your API keys page](https://console.mistral.ai/api-keys/) on Mistral AI. +2. Create a new API key. +3. Paste the key into the `API Key` field in Automatisch. +4. Write any screen name to be displayed in Automatisch. +5. Click `Save`. +6. Start using Mistral AI integration with Automatisch! diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 4be6447b..1e1d517b 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -31,6 +31,7 @@ The following integrations are currently supported by Automatisch. - [MailerLite](/apps/mailerlite/triggers) - [Mattermost](/apps/mattermost/actions) - [Miro](/apps/miro/actions) +- [Mistral AI](/apps/mistral-ai/actions) - [Notion](/apps/notion/triggers) - [Ntfy](/apps/ntfy/actions) - [Odoo](/apps/odoo/actions) diff --git a/packages/docs/pages/public/favicons/mistral-ai.svg b/packages/docs/pages/public/favicons/mistral-ai.svg new file mode 100644 index 00000000..3f583306 --- /dev/null +++ b/packages/docs/pages/public/favicons/mistral-ai.svg @@ -0,0 +1,32 @@ + + + Mistral AI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file