From 99d4bacd8ab61cadce756b770947dd18678e6df7 Mon Sep 17 00:00:00 2001 From: Eduard-Constantin Ibinceanu Date: Wed, 11 Dec 2024 12:42:49 +0200 Subject: [PATCH] IBM Watsonx LLM node (#3674) * Add node icon * Add node --- .../nodes/llms/IBMWatsonx/IBMWatsonx.ts | 231 ++++++++++++++++++ .../components/nodes/llms/IBMWatsonx/ibm.png | Bin 0 -> 24591 bytes 2 files changed, 231 insertions(+) create mode 100644 packages/components/nodes/llms/IBMWatsonx/IBMWatsonx.ts create mode 100644 packages/components/nodes/llms/IBMWatsonx/ibm.png diff --git a/packages/components/nodes/llms/IBMWatsonx/IBMWatsonx.ts b/packages/components/nodes/llms/IBMWatsonx/IBMWatsonx.ts new file mode 100644 index 000000000..8d138eb25 --- /dev/null +++ b/packages/components/nodes/llms/IBMWatsonx/IBMWatsonx.ts @@ -0,0 +1,231 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' +import { WatsonxLLM, WatsonxInputLLM } from '@langchain/community/llms/ibm' +import { WatsonxAuth } from '@langchain/community/dist/types/ibm' +import { BaseCache } from '@langchain/core/caches' + +class IBMWatsonx_LLMs implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'IBMWatsonx' + this.name = 'ibmWatsonx' + this.version = 1.0 + this.type = 'IBMWatsonx' + this.icon = 'ibm.png' + this.category = 'LLMs' + this.description = 'Wrapper around IBM watsonx.ai foundation models' + this.baseClasses = [this.type, ...getBaseClasses(WatsonxLLM)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['ibmWatsonx'] + } + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Model', + name: 'modelId', + type: 'string', + default: 'ibm/granite-13b-instruct-v2', + description: 'The name of the model to query.' + }, + { + label: 'Decoding Method', + name: 'decodingMethod', + type: 'options', + options: [ + { label: 'sample', name: 'sample' }, + { label: 'greedy', name: 'greedy' } + ], + default: 'greedy', + description: + 'Set decoding to Greedy to always select words with the highest probability. Set decoding to Sampling to customize the variability of word selection.', + optional: true, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + type: 'number', + description: + 'The topK parameter is used to limit the number of choices for the next predicted word or token. It specifies the maximum number of tokens to consider at each step, based on their probability of occurrence. This technique helps to speed up the generation process and can improve the quality of the generated text by focusing on the most likely options.', + step: 1, + default: 50, + optional: true, + additionalParams: true + }, + { + label: 'Top P', + name: 'topP', + type: 'number', + description: + 'The topP (nucleus) parameter is used to dynamically adjust the number of choices for each predicted token based on the cumulative probabilities. It specifies a probability threshold, below which all less likely tokens are filtered out. This technique helps to maintain diversity and generate more fluent and natural-sounding text.', + step: 0.1, + default: 0.7, + optional: true, + additionalParams: true + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: + 'A decimal number that determines the degree of randomness in the response. A value of 1 will always yield the same output. A temperature less than 1 favors more correctness and is appropriate for question answering or summarization. A value greater than 1 introduces more randomness in the output.', + step: 0.1, + default: 0.7, + optional: true, + additionalParams: true + }, + { + label: 'Repeat Penalty', + name: 'repetitionPenalty', + type: 'number', + description: + 'A number that controls the diversity of generated text by reducing the likelihood of repeated sequences. Higher values decrease repetition.', + step: 0.1, + default: 1, + optional: true, + additionalParams: true + }, + { + label: 'Streaming', + name: 'streaming', + type: 'boolean', + default: false, + description: 'Whether or not to stream tokens as they are generated.' + }, + { + label: 'Max New Tokens', + name: 'maxNewTokens', + type: 'number', + step: 1, + default: 100, + description: + 'The maximum number of new tokens to be generated. The maximum supported value for this field depends on the model being used.', + optional: true, + additionalParams: true + }, + { + label: 'Min New Tokens', + name: 'minNewTokens', + type: 'number', + step: 1, + default: 1, + description: 'If stop sequences are given, they are ignored until minimum tokens are generated.', + optional: true, + additionalParams: true + }, + { + label: 'Stop Sequence', + name: 'stopSequence', + type: 'string', + rows: 4, + placeholder: 'AI assistant:', + description: 'A list of tokens at which the generation should stop.', + optional: true, + additionalParams: true + }, + { + label: 'Include Stop Sequence', + name: 'includeStopSequence', + type: 'boolean', + default: false, + description: + 'Pass false to omit matched stop sequences from the end of the output text. The default is true, meaning that the output will end with the stop sequence text when matched.', + optional: true, + additionalParams: true + }, + { + label: 'Random Seed', + name: 'randomSeed', + type: 'number', + placeholder: '62345', + description: 'Random number generator seed to use in sampling mode for experimental repeatability.', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const decodingMethod = nodeData.inputs?.decodingMethod as string + const temperature = nodeData.inputs?.temperature as string + const maxNewTokens = nodeData.inputs?.maxNewTokens as string + const minNewTokens = nodeData.inputs?.minNewTokens as string + const topP = nodeData.inputs?.topP as string + const topK = nodeData.inputs?.topK as string + const repetitionPenalty = nodeData.inputs?.repetitionPenalty as string + const modelId = nodeData.inputs?.modelId as string + const stopSequence = nodeData.inputs?.stopSequence as string + const randomSeed = nodeData.inputs?.randomSeed as string + const includeStopSequence = nodeData.inputs?.includeStopSequence as boolean + const streaming = nodeData.inputs?.streaming as boolean + + const cache = nodeData.inputs?.cache as BaseCache + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const version = getCredentialParam('version', credentialData, nodeData) + const serviceUrl = getCredentialParam('serviceUrl', credentialData, nodeData) + const projectId = getCredentialParam('projectId', credentialData, nodeData) + const watsonxAIAuthType = getCredentialParam('watsonxAIAuthType', credentialData, nodeData) + const watsonxAIApikey = getCredentialParam('watsonxAIApikey', credentialData, nodeData) + const watsonxAIBearerToken = getCredentialParam('watsonxAIBearerToken', credentialData, nodeData) + + const auth = { + version, + serviceUrl, + projectId, + watsonxAIAuthType, + watsonxAIApikey, + watsonxAIBearerToken + } + + const obj: WatsonxInputLLM & WatsonxAuth = { + ...auth, + model: modelId, + streaming: streaming ?? true + } + + if (decodingMethod) obj.decodingMethod = decodingMethod + if (repetitionPenalty) obj.repetitionPenalty = parseFloat(repetitionPenalty) + if (maxNewTokens) obj.maxNewTokens = parseInt(maxNewTokens) + if (minNewTokens) obj.minNewTokens = parseInt(minNewTokens) + if (decodingMethod === 'sample') { + if (temperature) obj.temperature = parseFloat(temperature) + if (topP) obj.topP = parseFloat(topP) + if (topK) obj.topK = parseInt(topK) + } + if (stopSequence) { + obj.stopSequence = stopSequence.split(', ') || [''] + } + if (randomSeed) { + obj.randomSeed = parseInt(randomSeed) + } + if (includeStopSequence) { + obj.includeStopSequence = includeStopSequence + } + + if (cache) obj.cache = cache + + const watsonXAI = new WatsonxLLM(obj) + return watsonXAI + } +} + +module.exports = { nodeClass: IBMWatsonx_LLMs } diff --git a/packages/components/nodes/llms/IBMWatsonx/ibm.png b/packages/components/nodes/llms/IBMWatsonx/ibm.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a4be5810029b11d16789aaeca68d48e00e6649 GIT binary patch literal 24591 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 iS5i)!+20ia1^h2z2#!}zOc`?k0000