@n8n/n8n-nodes-langchain
Version:

366 lines • 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 SentimentAnalysis_node_exports = {};
__export(SentimentAnalysis_node_exports, {
SentimentAnalysis: () => SentimentAnalysis
});
module.exports = __toCommonJS(SentimentAnalysis_node_exports);
var import_messages = require("@langchain/core/messages");
var import_prompts = require("@langchain/core/prompts");
var import_output_parsers = require("@langchain/classic/output_parsers");
var import_n8n_workflow = require("n8n-workflow");
var import_zod = require("zod");
var import_sharedFields = require("../../../utils/sharedFields");
var import_tracing = require("../../../utils/tracing");
const DEFAULT_SYSTEM_PROMPT_TEMPLATE = "You are highly intelligent and accurate sentiment analyzer. Analyze the sentiment of the provided text. Categorize it into one of the following: {categories}. Use the provided formatting instructions. Only output the JSON.";
const DEFAULT_CATEGORIES = "Positive, Neutral, Negative";
const configuredOutputs = (parameters, defaultCategories) => {
const options = parameters?.options ?? {};
const categories = options?.categories ?? defaultCategories;
const categoriesArray = categories.split(",").map((cat) => cat.trim());
const ret = categoriesArray.map((cat) => ({ type: "main", displayName: cat }));
return ret;
};
class SentimentAnalysis {
constructor() {
this.description = {
displayName: "Sentiment Analysis",
name: "sentimentAnalysis",
icon: "fa:balance-scale-left",
iconColor: "black",
group: ["transform"],
version: [1, 1.1],
description: "Analyze the sentiment of your text",
codex: {
categories: ["AI"],
subcategories: {
AI: ["Chains", "Root Nodes"]
},
resources: {
primaryDocumentation: [
{
url: "https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.sentimentanalysis/"
}
]
}
},
defaults: {
name: "Sentiment Analysis"
},
inputs: [
{ displayName: "", type: import_n8n_workflow.NodeConnectionTypes.Main },
{
displayName: "Model",
maxConnections: 1,
type: import_n8n_workflow.NodeConnectionTypes.AiLanguageModel,
required: true
}
],
outputs: `={{(${configuredOutputs})($parameter, "${DEFAULT_CATEGORIES}")}}`,
properties: [
{
displayName: "Text to Analyze",
name: "inputText",
type: "string",
required: true,
default: "",
description: "Use an expression to reference data in previous nodes or enter static text",
typeOptions: {
rows: 2
}
},
{
displayName: "Sentiment scores are LLM-generated estimates, not statistically rigorous measurements. They may be inconsistent across runs and should be used as rough indicators only.",
name: "detailedResultsNotice",
type: "notice",
default: "",
displayOptions: {
show: {
"/options.includeDetailedResults": [true]
}
}
},
{
displayName: "Options",
name: "options",
type: "collection",
default: {},
placeholder: "Add Option",
options: [
{
displayName: "Sentiment Categories",
name: "categories",
type: "string",
default: DEFAULT_CATEGORIES,
description: "A comma-separated list of categories to analyze",
noDataExpression: true,
typeOptions: {
rows: 2
}
},
{
displayName: "System Prompt Template",
name: "systemPromptTemplate",
type: "string",
default: DEFAULT_SYSTEM_PROMPT_TEMPLATE,
description: "String to use directly as the system prompt template",
typeOptions: {
rows: 6
}
},
{
displayName: "Include Detailed Results",
name: "includeDetailedResults",
type: "boolean",
default: false,
description: "Whether to include sentiment strength and confidence scores in the output"
},
{
displayName: "Enable Auto-Fixing",
name: "enableAutoFixing",
type: "boolean",
default: true,
description: "Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)"
},
(0, import_sharedFields.getBatchingOptionFields)({
show: {
"@version": [{ _cnd: { gte: 1.1 } }]
}
})
]
}
]
};
}
async execute() {
const items = this.getInputData();
const llm = await this.getInputConnectionData(
import_n8n_workflow.NodeConnectionTypes.AiLanguageModel,
0
);
const returnData = [];
const batchSize = this.getNodeParameter("options.batching.batchSize", 0, 5);
const delayBetweenBatches = this.getNodeParameter(
"options.batching.delayBetweenBatches",
0,
0
);
if (this.getNode().typeVersion >= 1.1 && batchSize > 1) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchPromises = batch.map(async (_item, batchItemIndex) => {
const itemIndex = i + batchItemIndex;
const sentimentCategories = this.getNodeParameter(
"options.categories",
itemIndex,
DEFAULT_CATEGORIES
);
const categories = sentimentCategories.split(",").map((cat) => cat.trim()).filter(Boolean);
if (categories.length === 0) {
return {
result: null,
itemIndex,
error: new import_n8n_workflow.NodeOperationError(this.getNode(), "No sentiment categories provided", {
itemIndex
})
};
}
if (returnData.length === 0) {
returnData.push(...Array.from({ length: categories.length }, () => []));
}
const options = this.getNodeParameter("options", itemIndex, {});
const schema = import_zod.z.object({
sentiment: import_zod.z.enum(categories),
strength: import_zod.z.number().min(0).max(1).describe("Strength score for sentiment in relation to the category"),
confidence: import_zod.z.number().min(0).max(1)
});
const structuredParser = import_output_parsers.StructuredOutputParser.fromZodSchema(schema);
const parser = options.enableAutoFixing ? import_output_parsers.OutputFixingParser.fromLLM(llm, structuredParser) : structuredParser;
const systemPromptTemplate = import_prompts.SystemMessagePromptTemplate.fromTemplate(
`${options.systemPromptTemplate ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE}
{format_instructions}`
);
const input = this.getNodeParameter("inputText", itemIndex);
const inputPrompt = new import_messages.HumanMessage(input);
const messages = [
await systemPromptTemplate.format({
categories: sentimentCategories,
format_instructions: parser.getFormatInstructions()
}),
inputPrompt
];
const prompt = import_prompts.ChatPromptTemplate.fromMessages(messages);
const chain = prompt.pipe(llm).pipe(parser).withConfig((0, import_tracing.getTracingConfig)(this));
try {
const output = await chain.invoke(messages);
const sentimentIndex = categories.findIndex(
(s) => s.toLowerCase() === output.sentiment.toLowerCase()
);
if (sentimentIndex !== -1) {
const resultItem = { ...items[itemIndex] };
const sentimentAnalysis = {
category: output.sentiment
};
if (options.includeDetailedResults) {
sentimentAnalysis.strength = output.strength;
sentimentAnalysis.confidence = output.confidence;
}
resultItem.json = {
...resultItem.json,
sentimentAnalysis
};
return {
result: {
resultItem,
sentimentIndex
},
itemIndex
};
}
return {
result: {},
itemIndex
};
} catch (error) {
return {
result: null,
itemIndex,
error: new import_n8n_workflow.NodeOperationError(
this.getNode(),
"Error during parsing of LLM output, please check your LLM model and configuration",
{
itemIndex
}
)
};
}
});
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(({ result, itemIndex, error }) => {
if (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: itemIndex } }
);
returnData[0].push(...executionErrorData);
return;
} else {
throw error;
}
} else if (result.resultItem && result.sentimentIndex !== -1) {
const sentimentIndex = result.sentimentIndex;
const resultItem = result.resultItem;
returnData[sentimentIndex].push(resultItem);
}
});
if (i + batchSize < items.length && delayBetweenBatches > 0) {
await (0, import_n8n_workflow.sleep)(delayBetweenBatches);
}
}
} else {
for (let i = 0; i < items.length; i++) {
try {
const sentimentCategories = this.getNodeParameter(
"options.categories",
i,
DEFAULT_CATEGORIES
);
const categories = sentimentCategories.split(",").map((cat) => cat.trim()).filter(Boolean);
if (categories.length === 0) {
throw new import_n8n_workflow.NodeOperationError(this.getNode(), "No sentiment categories provided", {
itemIndex: i
});
}
if (returnData.length === 0) {
returnData.push(...Array.from({ length: categories.length }, () => []));
}
const options = this.getNodeParameter("options", i, {});
const schema = import_zod.z.object({
sentiment: import_zod.z.enum(categories),
strength: import_zod.z.number().min(0).max(1).describe("Strength score for sentiment in relation to the category"),
confidence: import_zod.z.number().min(0).max(1)
});
const structuredParser = import_output_parsers.StructuredOutputParser.fromZodSchema(schema);
const parser = options.enableAutoFixing ? import_output_parsers.OutputFixingParser.fromLLM(llm, structuredParser) : structuredParser;
const systemPromptTemplate = import_prompts.SystemMessagePromptTemplate.fromTemplate(
`${options.systemPromptTemplate ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE}
{format_instructions}`
);
const input = this.getNodeParameter("inputText", i);
const inputPrompt = new import_messages.HumanMessage(input);
const messages = [
await systemPromptTemplate.format({
categories: sentimentCategories,
format_instructions: parser.getFormatInstructions()
}),
inputPrompt
];
const prompt = import_prompts.ChatPromptTemplate.fromMessages(messages);
const chain = prompt.pipe(llm).pipe(parser).withConfig((0, import_tracing.getTracingConfig)(this));
try {
const output = await chain.invoke(messages);
const sentimentIndex = categories.findIndex(
(s) => s.toLowerCase() === output.sentiment.toLowerCase()
);
if (sentimentIndex !== -1) {
const resultItem = { ...items[i] };
const sentimentAnalysis = {
category: output.sentiment
};
if (options.includeDetailedResults) {
sentimentAnalysis.strength = output.strength;
sentimentAnalysis.confidence = output.confidence;
}
resultItem.json = {
...resultItem.json,
sentimentAnalysis
};
returnData[sentimentIndex].push(resultItem);
}
} catch (error) {
throw new import_n8n_workflow.NodeOperationError(
this.getNode(),
"Error during parsing of LLM output, please check your LLM model and configuration",
{
itemIndex: i
}
);
}
} catch (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: i } }
);
returnData[0].push(...executionErrorData);
continue;
}
throw error;
}
}
}
return returnData;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SentimentAnalysis
});
//# sourceMappingURL=SentimentAnalysis.node.js.map