UNPKG

@n8n/n8n-nodes-langchain

Version:

![Banner image](https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png)

375 lines (371 loc) 16.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Code = exports.vmResolver = void 0; exports.transformLegacyLangchainImport = transformLegacyLangchainImport; exports.createSandboxLogger = createSandboxLogger; const vm2_1 = require("vm2"); const JavaScriptSandbox_1 = require("n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox"); const Sandbox_1 = require("n8n-nodes-base/dist/nodes/Code/Sandbox"); const utils_1 = require("n8n-nodes-base/dist/nodes/Code/utils"); const n8n_workflow_1 = require("n8n-workflow"); const ai_utilities_1 = require("@n8n/ai-utilities"); const { NODE_FUNCTION_ALLOW_BUILTIN: builtIn, NODE_FUNCTION_ALLOW_EXTERNAL: external } = process.env; const connectorTypes = { [n8n_workflow_1.NodeConnectionTypes.AiChain]: 'Chain', [n8n_workflow_1.NodeConnectionTypes.AiDocument]: 'Document', [n8n_workflow_1.NodeConnectionTypes.AiEmbedding]: 'Embedding', [n8n_workflow_1.NodeConnectionTypes.AiLanguageModel]: 'Language Model', [n8n_workflow_1.NodeConnectionTypes.AiMemory]: 'Memory', [n8n_workflow_1.NodeConnectionTypes.AiOutputParser]: 'Output Parser', [n8n_workflow_1.NodeConnectionTypes.AiTextSplitter]: 'Text Splitter', [n8n_workflow_1.NodeConnectionTypes.AiTool]: 'Tool', [n8n_workflow_1.NodeConnectionTypes.AiVectorStore]: 'Vector Store', [n8n_workflow_1.NodeConnectionTypes.Main]: 'Main', }; const defaultCodeExecute = `const { PromptTemplate } = require('@langchain/core/prompts'); const query = 'Tell me a joke'; const prompt = PromptTemplate.fromTemplate(query); // If you are allowing more than one language model input connection (-1 or // anything greater than 1), getInputConnectionData returns an array, so you // will have to change the code below it to deal with that. For example, use // llm[0] in the chain definition const llm = await this.getInputConnectionData('ai_languageModel', 0); let chain = prompt.pipe(llm); const output = await chain.invoke(); return [ {json: { output } } ]; // NOTE: Old langchain imports (e.g., 'langchain/chains') are automatically // converted to '@langchain/classic' imports for backwards compatibility.`; const defaultCodeSupplyData = `const { WikipediaQueryRun } = require( '@langchain/community/tools/wikipedia_query_run'); return new WikipediaQueryRun();`; function transformLegacyLangchainImport(moduleName) { const classicModules = [ 'agents', 'callbacks', 'chains', 'chat_models/universal', 'document', 'document_loaders', 'document_transformers', 'embeddings/cache_backed', 'embeddings/fake', 'evaluation', 'experimental', 'hub', 'indexes', 'load', 'memory', 'output_parsers', 'retrievers', 'schema', 'smith', 'sql_db', 'storage', 'stores', 'text_splitter', 'tools', 'util', 'vectorstores', ]; if (moduleName.startsWith('langchain/')) { const subpath = moduleName.substring('langchain/'.length); for (const classicModule of classicModules) { if (subpath === classicModule || subpath.startsWith(classicModule + '/')) { return `@langchain/classic/${subpath}`; } } } return moduleName; } function transformLegacyLangchainCode(code) { let transformedCode = code.replace(/require\s*\(\s*['"]langchain\/([\w/_]+)['"]\s*\)/g, (match, subpath) => { const oldPath = `langchain/${subpath}`; const newPath = transformLegacyLangchainImport(oldPath); return newPath === oldPath ? match : `require('${newPath}')`; }); transformedCode = transformedCode.replace(/from\s+['"]langchain\/([\w/_]+)['"]/g, (match, subpath) => { const oldPath = `langchain/${subpath}`; const newPath = transformLegacyLangchainImport(oldPath); return newPath === oldPath ? match : `from '${newPath}'`; }); return transformedCode; } function isLoggerKey(level) { return level !== 'silent'; } function createSandboxLogger(logger) { return n8n_workflow_1.LOG_LEVELS.filter(isLoggerKey).reduce((result, level) => { result[level] = (...args) => logger[level].apply(logger, args); return result; }, {}); } const langchainModules = ['langchain', '@langchain/*']; exports.vmResolver = (0, vm2_1.makeResolverFromLegacyOptions)({ external: { modules: external ? [...langchainModules, ...external.split(',')] : [...langchainModules], transitive: false, }, resolve(moduleName, parentDirname) { if (moduleName.match(/^langchain\//) ?? moduleName.match(/^@langchain\//)) { return require.resolve(`@n8n/n8n-nodes-langchain/node_modules/${moduleName}.cjs`, { paths: [parentDirname], }); } return; }, builtin: builtIn?.split(',') ?? [], }); function getSandbox(code, options) { const itemIndex = options?.itemIndex ?? 0; const node = this.getNode(); const workflowMode = this.getMode(); const transformedCode = transformLegacyLangchainCode(code); const context = Sandbox_1.getSandboxContext.call(this, itemIndex); context.addInputData = this.addInputData.bind(this); context.addOutputData = this.addOutputData.bind(this); context.getInputConnectionData = this.getInputConnectionData.bind(this); context.getInputData = this.getInputData.bind(this); context.getNode = this.getNode.bind(this); context.getExecutionCancelSignal = this.getExecutionCancelSignal.bind(this); context.getNodeOutputs = this.getNodeOutputs.bind(this); context.executeWorkflow = this.executeWorkflow.bind(this); context.getWorkflowDataProxy = this.getWorkflowDataProxy.bind(this); context.logger = createSandboxLogger(this.logger); if (options?.addItems) { context.items = context.$input.all(); } const sandbox = new JavaScriptSandbox_1.JavaScriptSandbox(context, transformedCode, this.helpers, { resolver: exports.vmResolver, }); sandbox.on('output', workflowMode === 'manual' ? this.sendMessageToUI.bind(this) : (...args) => console.log(`[Workflow "${this.getWorkflow().id}"][Node "${node.name}"]`, ...args)); return sandbox; } class Code { constructor() { this.description = { displayName: 'LangChain Code', name: 'code', icon: 'fa:code', iconColor: 'black', group: ['transform'], version: 1, description: 'LangChain Code Node', defaults: { name: 'LangChain Code', }, codex: { categories: ['AI'], subcategories: { AI: ['Miscellaneous'], }, resources: { primaryDocumentation: [ { url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.code/', }, ], }, }, inputs: `={{ ((values) => { const connectorTypes = ${JSON.stringify(connectorTypes)}; return values.map(value => { return { type: value.type, required: value.required, maxConnections: value.maxConnections === -1 ? undefined : value.maxConnections, displayName: connectorTypes[value.type] !== 'Main' ? connectorTypes[value.type] : undefined } } ) })($parameter.inputs.input) }}`, outputs: `={{ ((values) => { const connectorTypes = ${JSON.stringify(connectorTypes)}; return values.map(value => { return { type: value.type, displayName: connectorTypes[value.type] !== 'Main' ? connectorTypes[value.type] : undefined } } ) })($parameter.outputs.output) }}`, properties: [ { displayName: 'Code', name: 'code', placeholder: 'Add Code', type: 'fixedCollection', noDataExpression: true, default: {}, options: [ { name: 'execute', displayName: 'Execute', values: [ { displayName: 'JavaScript - Execute', name: 'code', type: 'string', typeOptions: { editor: 'jsEditor', }, default: defaultCodeExecute, hint: 'This code will only run and return data if a "Main" input & output got created.', noDataExpression: true, }, ], }, { name: 'supplyData', displayName: 'Supply Data', values: [ { displayName: 'JavaScript - Supply Data', name: 'code', type: 'string', typeOptions: { editor: 'jsEditor', }, default: defaultCodeSupplyData, hint: 'This code will only run and return data if an output got created which is not "Main".', noDataExpression: true, }, ], }, ], }, { displayName: 'You can import LangChain and use all available functionality. Debug by using <code>console.log()</code> statements and viewing their output in the browser console.', name: 'notice', type: 'notice', default: '', }, { displayName: 'Inputs', name: 'inputs', placeholder: 'Add Input', type: 'fixedCollection', noDataExpression: true, typeOptions: { multipleValues: true, sortable: true, }, description: 'The input to add', default: {}, options: [ { name: 'input', displayName: 'Input', values: [ { displayName: 'Type', name: 'type', type: 'options', options: Object.keys(connectorTypes).map((key) => ({ name: connectorTypes[key], value: key, })), noDataExpression: true, default: '', required: true, description: 'The type of the input', }, { displayName: 'Max Connections', name: 'maxConnections', type: 'number', noDataExpression: true, default: -1, required: true, description: 'How many nodes of this type are allowed to be connected. Set it to -1 for unlimited.', }, { displayName: 'Required', name: 'required', type: 'boolean', noDataExpression: true, default: false, required: true, description: 'Whether the input needs a connection', }, ], }, ], }, { displayName: 'Outputs', name: 'outputs', placeholder: 'Add Output', type: 'fixedCollection', noDataExpression: true, typeOptions: { multipleValues: true, sortable: true, }, description: 'The output to add', default: {}, options: [ { name: 'output', displayName: 'Output', values: [ { displayName: 'Type', name: 'type', type: 'options', options: Object.keys(connectorTypes).map((key) => ({ name: connectorTypes[key], value: key, })), noDataExpression: true, default: '', required: true, description: 'The type of the input', }, ], }, ], }, ], }; } async execute() { const itemIndex = 0; const code = this.getNodeParameter('code', itemIndex); if (!code.execute?.code) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No code for "Execute" set on node "${this.getNode().name}`, { itemIndex, }); } const sandbox = getSandbox.call(this, code.execute.code, { addItems: true, itemIndex }); const outputs = this.getNodeOutputs(); const mainOutputs = outputs.filter((output) => output.type === n8n_workflow_1.NodeConnectionTypes.Main); const options = { multiOutput: mainOutputs.length !== 1 }; let items; try { items = await sandbox.runCodeAllItems(options); } catch (error) { if (!this.continueOnFail()) throw error; items = [{ json: { error: error.message } }]; if (options.multiOutput) { items = [items]; } } if (mainOutputs.length === 0) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The node does not have a "Main" output set. Please add one.', { itemIndex, }); } else if (!options.multiOutput) { for (const item of items) { (0, utils_1.standardizeOutput)(item.json); } return [items]; } else { items.forEach((data) => { for (const item of data) { (0, utils_1.standardizeOutput)(item.json); } }); return items; } } async supplyData(itemIndex) { const code = this.getNodeParameter('code', itemIndex); if (!code.supplyData?.code) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No code for "Supply Data" set on node "${this.getNode().name}`, { itemIndex, }); } const sandbox = getSandbox.call(this, code.supplyData.code, { itemIndex }); const response = await sandbox.runCode(); return { response: (0, ai_utilities_1.logWrapper)(response, this), }; } } exports.Code = Code; //# sourceMappingURL=Code.node.js.map