@n8n/n8n-nodes-langchain
Version:

413 lines (409 loc) • 14.7 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Code_node_exports = {};
__export(Code_node_exports, {
Code: () => Code,
transformLegacyLangchainImport: () => transformLegacyLangchainImport,
vmResolver: () => vmResolver
});
module.exports = __toCommonJS(Code_node_exports);
var import_vm2 = require("@n8n/vm2");
var import_JavaScriptSandbox = require("n8n-nodes-base/dist/nodes/Code/JavaScriptSandbox");
var import_Sandbox = require("n8n-nodes-base/dist/nodes/Code/Sandbox");
var import_utils = require("n8n-nodes-base/dist/nodes/Code/utils");
var import_n8n_workflow = require("n8n-workflow");
var import_logWrapper = require("../../utils/logWrapper");
const { NODE_FUNCTION_ALLOW_BUILTIN: builtIn, NODE_FUNCTION_ALLOW_EXTERNAL: external } = process.env;
const connectorTypes = {
[import_n8n_workflow.NodeConnectionTypes.AiChain]: "Chain",
[import_n8n_workflow.NodeConnectionTypes.AiDocument]: "Document",
[import_n8n_workflow.NodeConnectionTypes.AiEmbedding]: "Embedding",
[import_n8n_workflow.NodeConnectionTypes.AiLanguageModel]: "Language Model",
[import_n8n_workflow.NodeConnectionTypes.AiMemory]: "Memory",
[import_n8n_workflow.NodeConnectionTypes.AiOutputParser]: "Output Parser",
[import_n8n_workflow.NodeConnectionTypes.AiTextSplitter]: "Text Splitter",
[import_n8n_workflow.NodeConnectionTypes.AiTool]: "Tool",
[import_n8n_workflow.NodeConnectionTypes.AiVectorStore]: "Vector Store",
[import_n8n_workflow.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;
}
const langchainModules = ["langchain", "@langchain/*"];
const vmResolver = (0, import_vm2.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 = import_Sandbox.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 = this.logger;
if (options?.addItems) {
context.items = context.$input.all();
}
const sandbox = new import_JavaScriptSandbox.JavaScriptSandbox(context, transformedCode, this.helpers, {
resolver: 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
}
]
}
]
},
// TODO: Add links to docs which provide additional information regarding functionality
{
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 import_n8n_workflow.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 === import_n8n_workflow.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 import_n8n_workflow.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, import_utils.standardizeOutput)(item.json);
}
return [items];
} else {
items.forEach((data) => {
for (const item of data) {
(0, import_utils.standardizeOutput)(item.json);
}
});
return items;
}
}
async supplyData(itemIndex) {
const code = this.getNodeParameter("code", itemIndex);
if (!code.supplyData?.code) {
throw new import_n8n_workflow.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, import_logWrapper.logWrapper)(response, this)
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Code,
transformLegacyLangchainImport,
vmResolver
});
//# sourceMappingURL=Code.node.js.map